diff --git a/.gitignore b/.gitignore index 5ef3ce759..4d3986c67 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,7 @@ tags # Mac OS X .DS_Store files .DS_Store + +#VS is annoying about this one. +/build/win64/DF_PATH.txt +/build/win32/DF_PATH.txt diff --git a/.gitmodules b/.gitmodules index 6085402ac..d57d82200 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,30 +1,15 @@ [submodule "plugins/stonesense"] path = plugins/stonesense - url = git://github.com/DFHack/stonesense.git + url = ../../DFHack/stonesense.git [submodule "plugins/isoworld"] path = plugins/isoworld - url = git://github.com/DFHack/isoworld.git + url = ../../DFHack/isoworld.git [submodule "library/xml"] path = library/xml - url = git://github.com/DFHack/df-structures.git + url = ../../DFHack/df-structures.git [submodule "depends/clsocket"] path = depends/clsocket - url = git://github.com/DFHack/clsocket.git -[submodule "scripts/3rdparty/lethosor"] - path = scripts/3rdparty/lethosor - url = git://github.com/DFHack/lethosor-scripts -[submodule "scripts/3rdparty/roses"] - path = scripts/3rdparty/roses - url = git://github.com/DFHack/roses-scripts.git -[submodule "scripts/3rdparty/maxthyme"] - path = scripts/3rdparty/maxthyme - url = git://github.com/DFHack/maxthyme-scripts.git -[submodule "scripts/3rdparty/dscorbett"] - path = scripts/3rdparty/dscorbett - url = git://github.com/DFHack/dscorbett-scripts.git -[submodule "scripts/3rdparty/kane-t"] - path = scripts/3rdparty/kane-t - url = git://github.com/DFHack/kane-t-scripts.git -[submodule "scripts/3rdparty/maienm"] - path = scripts/3rdparty/maienm - url = git://github.com/DFHack/maienm-scripts.git + url = ../../DFHack/clsocket.git +[submodule "scripts2"] + path = scripts + url = ../../DFHack/scripts.git diff --git a/.travis.yml b/.travis.yml index ec08a8d56..ede4b132b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,21 @@ sudo: false language: cpp +cache: + pip: true + directories: + - $HOME/DF-travis + - $HOME/lua53 addons: apt: packages: &default_packages - - lua5.2 + - libsdl-image1.2-dev + - libsdl-ttf2.0-dev + - libsdl1.2-dev - libxml-libxml-perl - libxml-libxslt-perl - - zlib1g-dev:i386 + - zlib1g-dev matrix: include: - - env: GCC_VERSION=4.5 - addons: - apt: - packages: - - *default_packages - - gcc-4.5-multilib - - g++-4.5-multilib - env: GCC_VERSION=4.8 addons: apt: @@ -23,24 +23,34 @@ matrix: - ubuntu-toolchain-r-test packages: - *default_packages - - gcc-4.8-multilib - - g++-4.8-multilib + - gcc-4.8 + - g++-4.8 before_install: - pip install --user sphinx +- 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]" +- sh travis/build-lua.sh +- sh travis/download-df.sh +- echo "export DFHACK_HEADLESS=1" >> "$HOME/.dfhackrc" script: +- export PATH="$PATH:$HOME/lua53/bin" - git tag tmp-travis-build - sh travis/git-info.sh - sphinx-build -qW -j3 . docs/html - python travis/pr-check-base.py - python travis/lint.py - python travis/authors-rst.py -- python travis/script-in-readme.py -- python travis/script-syntax.py --ext=lua --cmd="luac5.2 -p" -- python travis/script-syntax.py --ext=rb --cmd="ruby -c" --path scripts/ +- python travis/script-docs.py +- python travis/script-syntax.py --ext=lua --cmd="luac5.3 -p" +- python travis/script-syntax.py --ext=rb --cmd="ruby -c" - mkdir build-travis - cd build-travis -- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON -- make -j3 +- cmake .. -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 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" notifications: email: false irc: diff --git a/CMake/DownloadFile.cmake b/CMake/DownloadFile.cmake new file mode 100644 index 000000000..5a10cca2f --- /dev/null +++ b/CMake/DownloadFile.cmake @@ -0,0 +1,71 @@ +# Helper to download files as needed + +function(file_md5_if_exists FILE VAR) + if(EXISTS "${FILE}") + file(MD5 "${FILE}" "${VAR}") + set(${VAR} "${${VAR}}" PARENT_SCOPE) + else() + set(${VAR} "" PARENT_SCOPE) + endif() +endfunction() + +function(search_downloads FILE_MD5 VAR) + set(${VAR} "" PARENT_SCOPE) + file(GLOB FILES ${CMAKE_SOURCE_DIR}/CMake/downloads/*) + foreach(FILE ${FILES}) + file(MD5 "${FILE}" CUR_MD5) + if("${CUR_MD5}" STREQUAL "${FILE_MD5}") + set(${VAR} ${FILE} PARENT_SCOPE) + return() + endif() + endforeach() +endfunction() + +function(download_file URL DEST EXPECTED_MD5) + get_filename_component(FILENAME "${URL}" NAME) + file_md5_if_exists("${DEST}" CUR_MD5) + + if(NOT "${EXPECTED_MD5}" STREQUAL "${CUR_MD5}") + search_downloads(${EXPECTED_MD5} DLPATH) + if(NOT("${DLPATH}" STREQUAL "")) + message("* Copying ${FILENAME} from ${DLPATH}") + execute_process(COMMAND "${CMAKE_COMMAND}" -E copy + "${DLPATH}" + "${DEST}") + return() + endif() + + message("* Downloading ${FILENAME}") + file(DOWNLOAD "${URL}" "${DEST}" EXPECTED_MD5 "${EXPECTED_MD5}" SHOW_PROGRESS) + endif() +endfunction() + +# Download a file and uncompress it +function(download_file_unzip URL ZIP_TYPE ZIP_DEST ZIP_MD5 UNZIP_DEST UNZIP_MD5) + get_filename_component(FILENAME "${URL}" NAME) + file_md5_if_exists("${UNZIP_DEST}" CUR_UNZIP_MD5) + + # Redownload if the MD5 of the uncompressed file doesn't match + if(NOT "${UNZIP_MD5}" STREQUAL "${CUR_UNZIP_MD5}") + download_file("${URL}" "${ZIP_DEST}" "${ZIP_MD5}") + + if(EXISTS "${ZIP_DEST}") + message("* Decompressing ${FILENAME}") + if("${ZIP_TYPE}" STREQUAL "gz") + execute_process(COMMAND + "${PERL_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/depends/gunzip.pl" + "${ZIP_DEST}" --force) + else() + message(SEND_ERROR "Unknown ZIP_TYPE: ${ZIP_TYPE}") + endif() + if(NOT EXISTS "${UNZIP_DEST}") + message(SEND_ERROR "File failed to unzip to ${UNZIP_DEST}") + else() + file(MD5 "${UNZIP_DEST}" CUR_UNZIP_MD5) + if(NOT "${UNZIP_MD5}" STREQUAL "${CUR_UNZIP_MD5}") + message(SEND_ERROR "MD5 mismatch: ${UNZIP_DEST}: expected ${UNZIP_MD5}, got ${CUR_UNZIP_MD5}") + endif() + endif() + endif() + endif() +endfunction() diff --git a/CMake/Modules/CMakeVS10FindMake.cmake b/CMake/Modules/CMakeVS10FindMake.cmake deleted file mode 100644 index 460de25e5..000000000 --- a/CMake/Modules/CMakeVS10FindMake.cmake +++ /dev/null @@ -1,28 +0,0 @@ - -#============================================================================= -# Copyright 2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distributed this file outside of CMake, substitute the full -# License text for the above reference.) - -# We use MSBuild as the build tool for VS 10 -FIND_PROGRAM(CMAKE_MAKE_PROGRAM - NAMES MSBuild - HINTS - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0\\Setup\\VS;ProductDir] - "$ENV{SYSTEMROOT}/Microsoft.NET/Framework/[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0;CLR Version]/" - "c:/WINDOWS/Microsoft.NET/Framework/[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0;CLR Version]/" - "$ENV{SYSTEMROOT}/Microsoft.NET/Framework/[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress\\10.0;CLR Version]/" - ) - -MARK_AS_ADVANCED(CMAKE_MAKE_PROGRAM) -SET(MSVC10 1) -SET(MSVC_VERSION 1600) - diff --git a/CMake/Modules/FindCurses.cmake b/CMake/Modules/FindCurses.cmake deleted file mode 100644 index 98adf9815..000000000 --- a/CMake/Modules/FindCurses.cmake +++ /dev/null @@ -1,23 +0,0 @@ -IF(Curses_FOUND) - SET(Curses_FIND_QUIETLY TRUE) -ENDIF() - -FIND_PATH(Curses_INCLUDE_PATH - NAMES ncurses.h curses.h - PATH_SUFFIXES ncurses - PATHS /usr/include/ncursesw /usr/include /usr/local/include /usr/pkg/include -) - -FIND_LIBRARY(Curses_LIBRARY - NAMES ncursesw - PATHS /lib /usr/lib /usr/local/lib /usr/pkg/lib -) - -IF (Curses_INCLUDE_PATH AND Curses_LIBRARY) - SET(Curses_FOUND TRUE) -ENDIF() - -MARK_AS_ADVANCED( - Curses_INCLUDE_PATH - Curses_LIBRARY -) diff --git a/CMake/Modules/FindDocutils.cmake b/CMake/Modules/FindDocutils.cmake deleted file mode 100644 index 8103628df..000000000 --- a/CMake/Modules/FindDocutils.cmake +++ /dev/null @@ -1,3 +0,0 @@ -FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py) -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE) \ No newline at end of file diff --git a/CMake/Modules/FindDoxygen.cmake b/CMake/Modules/FindDoxygen.cmake deleted file mode 100644 index 94d20a9ff..000000000 --- a/CMake/Modules/FindDoxygen.cmake +++ /dev/null @@ -1,185 +0,0 @@ -# - This module looks for Doxygen and the path to Graphviz's dot -# Doxygen is a documentation generation tool. Please see -# http://www.doxygen.org -# -# This module accepts the following optional variables: -# -# DOXYGEN_SKIP_DOT = If true this module will skip trying to find Dot -# (an optional component often used by Doxygen) -# -# This modules defines the following variables: -# -# DOXYGEN_EXECUTABLE = The path to the doxygen command. -# DOXYGEN_FOUND = Was Doxygen found or not? -# -# DOXYGEN_DOT_EXECUTABLE = The path to the dot program used by doxygen. -# DOXYGEN_DOT_FOUND = Was Dot found or not? -# DOXYGEN_DOT_PATH = The path to dot not including the executable -# -# - -#Copyright 2000-2009 Kitware, Inc., Insight Software Consortium -#All rights reserved. -# -#Redistribution and use in source and binary forms, with or without -#modification, are permitted provided that the following conditions -#are met: -# -#* Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -#* Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -#* Neither the names of Kitware, Inc., the Insight Software Consortium, -# nor the names of their contributors may be used to endorse or promote -# products derived from this software without specific prior written -# permission. -# -#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -#A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -#HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -#SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -#LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -#----------------------------------------------------------------------------- -# -#The above copyright and license notice applies to distributions of -#CMake in source and binary form. Some source files contain additional -#notices of original copyright by their contributors; see each source -#for details. Third-party software packages supplied with CMake under -#compatible licenses provide their own copyright notices documented in -#corresponding subdirectories. -# -#----------------------------------------------------------------------------- -# -#CMake was initially developed by Kitware with the following sponsorship: -# -# * National Library of Medicine at the National Institutes of Health -# as part of the Insight Segmentation and Registration Toolkit (ITK). -# -# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel -# Visualization Initiative. -# -# * National Alliance for Medical Image Computing (NAMIC) is funded by the -# National Institutes of Health through the NIH Roadmap for Medical Research, -# Grant U54 EB005149. -# -# * Kitware, Inc. - - -# For backwards compatibility support -IF(Doxygen_FIND_QUIETLY) - SET(DOXYGEN_FIND_QUIETLY TRUE) -ENDIF(Doxygen_FIND_QUIETLY) - -# ===== Rationale for OS X AppBundle mods below ===== -# With the OS X GUI version, Doxygen likes to be installed to /Applications and -# it contains the doxygen executable in the bundle. In the versions I've -# seen, it is located in Resources, but in general, more often binaries are -# located in MacOS. -# -# NOTE: The official Doxygen.app that is distributed for OS X uses non-standard -# conventions. Instead of the command-line "doxygen" tool being placed in -# Doxygen.app/Contents/MacOS, "Doxywizard" is placed there instead and -# "doxygen" is placed in Contents/Resources. This is most likely done -# so that something happens when people double-click on the Doxygen.app -# package. Unfortunately, CMake gets confused by this as when it sees the -# bundle it uses "Doxywizard" as the executable to use instead of -# "doxygen". Therefore to work-around this issue we temporarily disable -# the app-bundle feature, just for this CMake module: -if(APPLE) - # Save the old setting - SET(TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE}) - # Disable the App-bundle detection feature - SET(CMAKE_FIND_APPBUNDLE "NEVER") -endif() -# FYI: -# In the older versions of OS X Doxygen, dot was included with the -# Doxygen bundle. But the new versions require you to download -# Graphviz.app which contains "dot" in it's bundle. -# ============== End OSX stuff ================ - -# -# Find Doxygen... -# - -FIND_PROGRAM(DOXYGEN_EXECUTABLE - NAMES doxygen - PATHS - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\doxygen_is1;Inno Setup: App Path]/bin" - /Applications/Doxygen.app/Contents/Resources - /Applications/Doxygen.app/Contents/MacOS - DOC "Doxygen documentation generation tool (http://www.doxygen.org)" -) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Doxygen DEFAULT_MSG DOXYGEN_EXECUTABLE) - -# -# Find Dot... -# - -IF(NOT DOXYGEN_SKIP_DOT) - FIND_PROGRAM(DOXYGEN_DOT_EXECUTABLE - NAMES dot - PATHS - "$ENV{ProgramFiles}/Graphviz2.26.3/bin" - "C:/Program Files/Graphviz2.26.3/bin" - "$ENV{ProgramFiles}/Graphviz 2.21/bin" - "C:/Program Files/Graphviz 2.21/bin" - "$ENV{ProgramFiles}/ATT/Graphviz/bin" - "C:/Program Files/ATT/Graphviz/bin" - [HKEY_LOCAL_MACHINE\\SOFTWARE\\ATT\\Graphviz;InstallPath]/bin - /Applications/Graphviz.app/Contents/MacOS - /Applications/Doxygen.app/Contents/Resources - /Applications/Doxygen.app/Contents/MacOS - DOC "Graphviz Dot tool for using Doxygen" - ) - - if(DOXYGEN_DOT_EXECUTABLE) - set(DOXYGEN_DOT_FOUND TRUE) - # The Doxyfile wants the path to Dot, not the entire path and executable - get_filename_component(DOXYGEN_DOT_PATH "${DOXYGEN_DOT_EXECUTABLE}" PATH CACHE) - endif() - -endif(NOT DOXYGEN_SKIP_DOT) - -# -# Backwards compatibility... -# - -if(APPLE) - # Restore the old app-bundle setting setting - SET(CMAKE_FIND_APPBUNDLE ${TEMP_DOXYGEN_SAVE_CMAKE_FIND_APPBUNDLE}) -endif() - -# Maintain the _FOUND variables as "YES" or "NO" for backwards compatibility -# (allows people to stuff them directly into Doxyfile with configure_file()) -if(DOXYGEN_FOUND) - set(DOXYGEN_FOUND "YES") -else() - set(DOXYGEN_FOUND "NO") -endif() -if(DOXYGEN_DOT_FOUND) - set(DOXYGEN_DOT_FOUND "YES") -else() - set(DOXYGEN_DOT_FOUND "NO") -endif() - -# For backwards compatibility support -SET (DOXYGEN ${DOXYGEN_EXECUTABLE} ) -SET (DOT ${DOXYGEN_DOT_EXECUTABLE} ) - -MARK_AS_ADVANCED( - DOXYGEN_EXECUTABLE - DOXYGEN_DOT_EXECUTABLE - DOXYGEN_DOT_PATH - ) diff --git a/CMake/Modules/FindTinyXML.cmake b/CMake/Modules/FindTinyXML.cmake new file mode 100644 index 000000000..b0466fbd5 --- /dev/null +++ b/CMake/Modules/FindTinyXML.cmake @@ -0,0 +1,107 @@ +# Sourced from: +# https://raw.githubusercontent.com/ros/cmake_modules/0.4-devel/cmake/Modules/FindTinyXML.cmake +# under the following license: https://github.com/ros/cmake_modules/blob/0.4-devel/LICENSE, +# reproduced here: + +# Copyright (c) 2013, Open Source Robotics Foundation +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: + +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. + +# Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. + +# Neither the name of the {organization} nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################################## +# +# CMake script for finding TinyXML. +# +# Input variables: +# +# - TinyXML_ROOT_DIR (optional): When specified, header files and libraries will be searched for in +# ${TinyXML_ROOT_DIR}/include +# ${TinyXML_ROOT_DIR}/libs +# respectively, and the default CMake search order will be ignored. When unspecified, the default +# CMake search order is used. +# This variable can be specified either as a CMake or environment variable. If both are set, +# preference is given to the CMake variable. +# Use this variable for finding packages installed in a nonstandard location, or for enforcing +# that one of multiple package installations is picked up. +# +# +# Cache variables (not intended to be used in CMakeLists.txt files) +# +# - TinyXML_INCLUDE_DIR: Absolute path to package headers. +# - TinyXML_LIBRARY: Absolute path to library. +# +# +# Output variables: +# +# - TinyXML_FOUND: Boolean that indicates if the package was found +# - TinyXML_INCLUDE_DIRS: Paths to the necessary header files +# - TinyXML_LIBRARIES: Package libraries +# +# +# Example usage: +# +# find_package(TinyXML) +# if(NOT TinyXML_FOUND) +# # Error handling +# endif() +# ... +# include_directories(${TinyXML_INCLUDE_DIRS} ...) +# ... +# target_link_libraries(my_target ${TinyXML_LIBRARIES}) +# +################################################################################################## + +# Get package location hint from environment variable (if any) +if(NOT TinyXML_ROOT_DIR AND DEFINED ENV{TinyXML_ROOT_DIR}) + set(TinyXML_ROOT_DIR "$ENV{TinyXML_ROOT_DIR}" CACHE PATH + "TinyXML base directory location (optional, used for nonstandard installation paths)") +endif() + +# Search path for nonstandard package locations +if(TinyXML_ROOT_DIR) + set(TinyXML_INCLUDE_PATH PATHS "${TinyXML_ROOT_DIR}/include" NO_DEFAULT_PATH) + set(TinyXML_LIBRARY_PATH PATHS "${TinyXML_ROOT_DIR}/lib" NO_DEFAULT_PATH) +endif() + +# Find headers and libraries +find_path(TinyXML_INCLUDE_DIR NAMES tinyxml.h PATH_SUFFIXES "tinyxml" ${TinyXML_INCLUDE_PATH}) +find_library(TinyXML_LIBRARY NAMES tinyxml PATH_SUFFIXES "tinyxml" ${TinyXML_LIBRARY_PATH}) + +mark_as_advanced(TinyXML_INCLUDE_DIR + TinyXML_LIBRARY) + +# Output variables generation +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(TinyXML DEFAULT_MSG TinyXML_LIBRARY + TinyXML_INCLUDE_DIR) + +set(TinyXML_FOUND ${TINYXML_FOUND}) # Enforce case-correctness: Set appropriately cased variable... +unset(TINYXML_FOUND) # ...and unset uppercase variable generated by find_package_handle_standard_args + +if(TinyXML_FOUND) + set(TinyXML_INCLUDE_DIRS ${TinyXML_INCLUDE_DIR}) + set(TinyXML_LIBRARIES ${TinyXML_LIBRARY}) +endif() diff --git a/CMake/downloads/.gitignore b/CMake/downloads/.gitignore new file mode 100644 index 000000000..ff7b252fe --- /dev/null +++ b/CMake/downloads/.gitignore @@ -0,0 +1,3 @@ +* +!README.txt +!.gitignore diff --git a/CMake/downloads/README.txt b/CMake/downloads/README.txt new file mode 100644 index 000000000..40b504725 --- /dev/null +++ b/CMake/downloads/README.txt @@ -0,0 +1,3 @@ +This folder exists as an alternate location for downloaded files. Files will +ordinarily not be downloaded here, but CMake will look for them here anyway to +facilitate offline builds. diff --git a/CMakeLists.txt b/CMakeLists.txt index c34e0fedc..6e469546b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,5 @@ # 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 if(CMAKE_CONFIGURATION_TYPES) SET(CMAKE_CONFIGURATION_TYPES Release RelWithDebInfo) @@ -18,14 +13,14 @@ endif(CMAKE_CONFIGURATION_TYPES) OPTION(BUILD_DOCS "Choose whether to build the documentation (requires python and Sphinx)." OFF) ## some generic CMake magic -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) +cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(dfhack) macro(CHECK_GCC COMPILER_PATH) execute_process(COMMAND ${COMPILER_PATH} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT) string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT) - if (${GCC_VERSION_OUT} VERSION_LESS "4.5") - message(SEND_ERROR "${COMPILER_PATH} version ${GCC_VERSION_OUT} cannot be used - use GCC 4.5 or later") + if (${GCC_VERSION_OUT} VERSION_LESS "4.8") + message(SEND_ERROR "${COMPILER_PATH} version ${GCC_VERSION_OUT} cannot be used - use GCC 4.8 or later") elseif (${GCC_VERSION_OUT} VERSION_GREATER "4.9.9") # GCC 5 changes ABI name mangling to enable C++11 changes. # This must be disabled to enable linking against DF. @@ -48,21 +43,61 @@ if(UNIX) endif() if(WIN32) - if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1600)) - message(SEND_ERROR "MSVC 2010 is required") + if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1900)) + message(SEND_ERROR "MSVC 2015 is required") endif() endif() if(MSVC) # disable C4819 code-page warning add_definitions( "/wd4819" ) + +# Disable use of POSIX name warnings +add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS") + +# 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() +# Automatically detect architecture based on Visual Studio generator +IF(MSVC AND NOT DEFINED DFHACK_BUILD_ARCH) + IF(${CMAKE_GENERATOR} MATCHES "Win64") + SET(DFHACK_BUILD_ARCH "64") + ELSE() + SET(DFHACK_BUILD_ARCH "32") + ENDIF() +ELSE() + SET(DFHACK_BUILD_ARCH "32" CACHE STRING "Architecture to build ('32' or '64')") +ENDIF() + +IF("${DFHACK_BUILD_ARCH}" STREQUAL "32") + SET(DFHACK_BUILD_32 1) + SET(DFHACK_BUILD_64 0) + set(DFHACK_SETARCH "i386") +ELSEIF("${DFHACK_BUILD_ARCH}" STREQUAL "64") + SET(DFHACK_BUILD_32 0) + SET(DFHACK_BUILD_64 1) + set(DFHACK_SETARCH "x86_64") + ADD_DEFINITIONS(-DDFHACK64) +ELSE() + MESSAGE(SEND_ERROR "Invalid build architecture (should be 32/64): ${DFHACK_BUILD_ARCH}") +ENDIF() + IF(CMAKE_CROSSCOMPILING) SET(DFHACK_NATIVE_BUILD_DIR "DFHACK_NATIVE_BUILD_DIR-NOTFOUND" CACHE FILEPATH "Path to a native build directory") INCLUDE("${DFHACK_NATIVE_BUILD_DIR}/ImportExecutables.cmake") ENDIF() +find_package(Perl REQUIRED) + # set up folder structures for IDE solutions # MSVC Express won't load solutions that use this. It also doesn't include MFC supported # Check for MFC! @@ -105,12 +140,14 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.43.02") -SET(DFHACK_RELEASE "alpha0") -SET(DFHACK_PRERELEASE TRUE) +set(DF_VERSION "0.44.10") +set(DFHACK_RELEASE "r1") +set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") +set(DFHACK_ABI_VERSION 1) + ## where to install things (after the build is done, classic 'make install' or package structure) # the dfhack libraries will be installed here: IF(UNIX) @@ -144,6 +181,7 @@ SET(DFHACK_DEVDOC_DESTINATION hack) OPTION(BUILD_LIBRARY "Build the library that goes into DF." ON) OPTION(BUILD_PLUGINS "Build the plugins." ON) +SET(CMAKE_POSITION_INDEPENDENT_CODE TRUE) IF(UNIX) ## flags for GCC # default to hidden symbols @@ -151,13 +189,24 @@ IF(UNIX) # ensure compatibility with older CPUs # enable C++11 features 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 "${CMAKE_CXX_FLAGS} -fvisibility=hidden -m32 -march=i686 -mtune=generic -std=c++0x") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -m32 -march=i686 -mtune=generic") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -mtune=generic -std=c++0x") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -mtune=generic") + IF(DFHACK_BUILD_64) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -mno-avx") + ELSE() + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32 -march=i686") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") + ENDIF() + STRING(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") ELSEIF(MSVC) # 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_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}") ENDIF() # use shared libraries for protobuf @@ -173,20 +222,142 @@ elseif(WIN32) add_definitions(-DWIN32) endif() +#### download depends #### + +include(CMake/DownloadFile.cmake) + +if(WIN32) + # Download zlib on Windows + set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH}) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-zlib.lib" + ${ZLIB_DOWNLOAD_DIR}/zlib.lib + "a3b2fc6b68efafa89b0882e354fc8418") + else() + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-zlib.lib" + ${ZLIB_DOWNLOAD_DIR}/zlib.lib + "f4ebaa21d9de28566e88b1edfcdff901") + endif() + + # Move zlib to the build folder so possible 32 and 64-bit builds + # in the same source tree don't conflict + file(COPY ${CMAKE_SOURCE_DIR}/depends/zlib + DESTINATION ${CMAKE_BINARY_DIR}/depends/) + file(COPY ${ZLIB_DOWNLOAD_DIR}/zlib.lib + DESTINATION ${CMAKE_BINARY_DIR}/depends/zlib/lib/) + + + # Do the same for SDLreal.dll + # (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}) + if(${DFHACK_BUILD_ARCH} STREQUAL "64") + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-SDL.dll" + ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + "1ae242c4b94cb03756a1288122a66faf") + else() + download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-SDL.dll" + ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + "5a09604daca6b2b5ce049d79af935d6a") + endif() +endif() + +if(APPLE) + # libstdc++ (GCC 4.8.5 for OS X 10.6) + # fixes crash-on-unwind bug in DF's libstdc++ + 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(${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" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "cf26ed588be8e83c8e3a49919793b416" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "16dc6dbd4ecde7f9b95bb6dc91f07404") + else() + # 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" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz + "40f3d83871b114f0279240626311621b" + ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + "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() + + install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + DESTINATION ./hack/) + +endif() + #### expose depends #### +if(UNIX) + # Rescan for pthread and zlib if the build arch changed + if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_BUILD_ARCH_PREV}") + unset(ZLIB_LIBRARY CACHE) + unset(CMAKE_HAVE_PTHREAD_H CACHE) + endif() +endif() + # find and make available libz -if(NOT UNIX) - SET(ZLIB_ROOT depends/zlib/) +if(NOT UNIX) # Windows + # zlib is in here so 32-bit and 64-bit builds in the same source tree are possible + set(ZLIB_ROOT ${CMAKE_BINARY_DIR}/depends/zlib/) else() - set(ZLIB_ROOT /usr/lib/i386-linux-gnu) + if(NOT APPLE AND DFHACK_BUILD_32) + # 32-bit Linux + set(ZLIB_ROOT /usr/lib/i386-linux-gnu) + endif() endif() + find_package(ZLIB REQUIRED) include_directories(depends/protobuf) include_directories(depends/lua/include) include_directories(depends/md5) include_directories(depends/jsoncpp) -include_directories(depends/tinyxml) + +# Support linking against external tinyxml +# If we find an external tinyxml, set the DFHACK_TINYXML variable to "tinyxml" +# Otherwise, set it to "dfhack-tinyxml" +option(EXTERNAL_TINYXML "Choose to link against external TinyXML" OFF) +if(EXTERNAL_TINYXML) + find_package(TinyXML REQUIRED) + if(NOT TinyXML_FOUND) + message(SEND_ERROR "Could not find an external TinyXML, consider setting EXTERNAL_TINYXML to OFF.") + endif() + SET(DFHACK_TINYXML "tinyxml") +else() + include_directories(depends/tinyxml) + SET(DFHACK_TINYXML "dfhack-tinyxml") +endif() + include_directories(depends/tthread) include_directories(${ZLIB_INCLUDE_DIRS}) include_directories(depends/clsocket/src) @@ -200,9 +371,12 @@ endif() # build the lib itself IF(BUILD_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() +file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH}) +install(FILES "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" DESTINATION "${DFHACK_DATA_DESTINATION}") + install(DIRECTORY dfhack-config/ DESTINATION dfhack-config/default) #build the plugins @@ -220,6 +394,8 @@ if (BUILD_DOCS) file(GLOB SPHINX_DEPS "${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/styles/*" "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" @@ -232,7 +408,6 @@ if (BUILD_DOCS) ) set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" - "${CMAKE_CURRENT_SOURCE_DIR}/NEWS.rst" "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" ) @@ -260,6 +435,7 @@ if (BUILD_DOCS) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/ 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}") endif() @@ -269,11 +445,7 @@ IF(UNIX) execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) string(STRIP ${GCC_VERSION} GCC_VERSION) SET(DFHACK_PACKAGE_SUFFIX "-gcc-${GCC_VERSION}") - if(APPLE) - SET(CPACK_GENERATOR "ZIP;TBZ2") - else() - SET(CPACK_GENERATOR "TBZ2") - endif() + SET(CPACK_GENERATOR "TBZ2") ELSEIF(WIN32) SET(CPACK_GENERATOR "ZIP") ENDIF() @@ -283,7 +455,10 @@ IF(APPLE) ELSE() set(DFHACK_PACKAGE_PLATFORM_NAME ${CMAKE_SYSTEM_NAME}) ENDIF() -set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}${DFHACK_PACKAGE_SUFFIX}") +set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}-${DFHACK_BUILD_ARCH}${DFHACK_PACKAGE_SUFFIX}") INCLUDE(CPack) #INCLUDE(FindSphinx.cmake) + +# Store old build arch +SET(DFHACK_BUILD_ARCH_PREV "${DFHACK_BUILD_ARCH}" CACHE STRING "Previous build architecture" FORCE) diff --git a/docs/Contributing.rst b/Contributing.rst similarity index 89% rename from docs/Contributing.rst rename to Contributing.rst index d29c4acef..e358e66b3 100644 --- a/docs/Contributing.rst +++ b/Contributing.rst @@ -32,7 +32,8 @@ How to get new code into DFHack (i.e. not the master or develop branch of your fork). * If possible, compile on multiple platforms when changing anything that compiles * 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 * Submit ideas and bug reports as :issue:`issues on GitHub <>`. 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 :) 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 -* 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) -* IDA Pro 5.0 running under Wine * Some of the tools residing in the ``legacy`` dfhack branch. Using the library as a developer @@ -148,29 +149,32 @@ as well as getting help, you'll be providing valuable feedback that makes it easier for future readers! Scripts can use a custom autodoc function, based on the Sphinx ``include`` -directive and Ruby docstring conventions - any lines between ``=begin`` and -``=end`` are copied into the appropriate scripts documentation page. -They **must** have a heading which exactly matches the command, underlined +directive - anything between the tokens is copied into the appropriate scripts +documentation page. For Ruby, we follow the built-in docstring convention +(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]`` +- ordinary multi-line strings. It is highly encouraged to reuse this string +as the in-console documentation by (eg.) printing it when a ``-help`` argument +is given. + +The docs **must** have a heading which exactly matches the command, underlined with ``=====`` to the same length. For example, a lua file would have:: - --[[=begin + local helpstr = [====[ add-thought =========== Adds a thought or emotion to the selected unit. Can be used by other scripts, or the gui invoked by running ``add-thought gui`` with a unit selected. - =end]] + ]====] -Ruby scripts use the same syntax, but obviously omit the leading ``--[[`` and -trailing ``]]`` which denote a multiline comment in lua. -``=begin`` and ``=end`` are native syntax (and matched in lua for convenience). Where the heading for a section is also the name of a command, the spelling and case should exactly match the command to enter in the DFHack command line. -Try to keep lines within 80-100 characters, so it's readable in plain text - -Sphinx (our documentation system) will make sure paragraphs flow. +Try to keep lines within 80-100 characters, so it's readable in plain text +in the terminal - Sphinx (our documentation system) will make sure +paragraphs flow. If there aren't many options or examples to show, they can go in a paragraph of text. Use double-backticks to put commands in monospaced font, like this:: @@ -208,8 +212,7 @@ section like this:: ========= Add link targets if you need them, but otherwise plain headings are preferred. -Scripts using the in-source docs option, which should be all of them, have -link targets created automatically. +Scripts have link targets created automatically. Other ways to help ================== diff --git a/NEWS.rst b/NEWS.rst deleted file mode 100644 index 7571ec45a..000000000 --- a/NEWS.rst +++ /dev/null @@ -1,518 +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. - - Sections for each release are added as required, and consist solely of the - following in order as subheadings:: - - Internals - Lua - New [Internal Commands | Plugins | Scripts | Features] - Fixes - Misc Improvements - Removed - - 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 -============= - -Lua ---- -- Label widgets can now easily register handlers for mouse clicks - -New Features ------------- -- `gui/gm-editor` it's now possible to insert default types to containers. For primitive types leave the type entry empty, for references use ``*``. - -Fixes ------ -- `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 - -Misc Improvements ------------------ -- `fix/diplomats`: replaces ``fixdiplomats`` -- `fix/merchants`: replaces ``fixmerchants`` - -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 ` 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 ` 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 ` 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 `: Fixes the in-game minimum FPS setting -- `shift-8-scroll `: Gives Shift+8 (or ``*``) priority when scrolling menus, instead of scrolling the map -- `tradereq-pet-gender `: 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 `: 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. diff --git a/README.md b/README.md index 50769e0c5..9ea1ccb04 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ -#DFHack Readme +# DFHack Readme -[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)] -(https://travis-ci.org/DFHack/dfhack) -[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)] -(https://dfhack.readthedocs.org) -[![License](https://img.shields.io/badge/license-ZLib-blue.svg)] -(https://en.wikipedia.org/wiki/Zlib_License) -[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)] -(https://github.com/DFHack/dfhack/issues) -[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)] -(https://github.com/DFHack/dfhack/pulls) +[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack) +[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org) +[![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License) +[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)](https://github.com/DFHack/dfhack/issues) +[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)](https://github.com/DFHack/dfhack/pulls) DFHack is a Dwarf Fortress memory access library, distributed with scripts and plugins implementing a wide variety of useful functions and tools. @@ -19,6 +14,6 @@ from the README.html page in the DFHack distribution, or as raw text in the `./d If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. -If that's unclear or you need more help, try [the Bay12 forums thread] -(http://www.bay12forums.com/smf/index.php?topic=139553) or the #dfhack IRC -channel on freenode. +If that's unclear or you need more help, try +[the Bay12 forums thread](http://www.bay12forums.com/smf/index.php?topic=164123) +or the #dfhack IRC channel on freenode. diff --git a/build/.gitignore b/build/.gitignore index 10192a42e..3ee46f78e 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,4 +1,6 @@ VC2010 +VC2015 +VC2015_32 DF_PATH.txt _CPack_Packages *.tar.* diff --git a/build/generate-MSVC-all-breakfast.bat b/build/generate-MSVC-all-breakfast.bat deleted file mode 100644 index 1921fba6c..000000000 --- a/build/generate-MSVC-all-breakfast.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -IF EXIST DF_PATH.txt SET /P _DF_PATH= 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) diff --git a/build/build-debug.bat b/build/win32/build-debug.bat similarity index 63% rename from build/build-debug.bat rename to build/win32/build-debug.bat index a9492de13..b75676ff4 100644 --- a/build/build-debug.bat +++ b/build/win32/build-debug.bat @@ -1,4 +1,4 @@ -call "%VS100COMNTOOLS%vsvars32.bat" -cd VC2010 +call "%VS140COMNTOOLS%vsvars32.bat" +cd VC2015_32 msbuild /m /p:Platform=Win32 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj cd .. \ No newline at end of file diff --git a/build/build-release.bat b/build/win32/build-release.bat similarity index 62% rename from build/build-release.bat rename to build/win32/build-release.bat index e1ad315e5..0b7a2a407 100644 --- a/build/build-release.bat +++ b/build/win32/build-release.bat @@ -1,5 +1,5 @@ -call "%VS100COMNTOOLS%vsvars32.bat" -cd VC2010 +call "%VS140COMNTOOLS%vsvars32.bat" +cd VC2015_32 msbuild /m /p:Platform=Win32 /p:Configuration=Release ALL_BUILD.vcxproj cd .. pause \ No newline at end of file diff --git a/build/win32/generate-MSVC-all.bat b/build/win32/generate-MSVC-all.bat new file mode 100755 index 000000000..5c113c7b6 --- /dev/null +++ b/build/win32/generate-MSVC-all.bat @@ -0,0 +1,6 @@ +IF EXIST DF_PATH.txt SET /P _DF_PATH= 0 Then + Set spoFile = fso.CreateTextFile("DF_PATH.txt", True) + spoFile.WriteLine(objF.Self.Path) + End If +End If + +Function IsValue(obj) + ' Check whether the value has been returned. + Dim tmp + On Error Resume Next + tmp = " " & obj + If Err <> 0 Then + IsValue = False + Else + IsValue = True + End If + On Error GoTo 0 +End Function \ No newline at end of file diff --git a/conf.py b/conf.py index 59a220e26..bfd69d828 100644 --- a/conf.py +++ b/conf.py @@ -1,23 +1,85 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# -# DFHack documentation build configuration file -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import fnmatch +""" +DFHack documentation build configuration file + +This file is execfile()d with the current directory set to its +containing dir. + +Note that not all possible configuration values are present in this +autogenerated file. + +All configuration values have a default; values that are commented out +serve to show the default. +""" + +# pylint:disable=redefined-builtin + from io import open import os -import shlex +import re +import shlex # pylint:disable=unused-import 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` ------------------------------------ +# this is a custom directive that pulls info from dfhack.init-example + +from docutils import nodes +from docutils.parsers.rst import roles + + +def get_keybinds(): + """Get the implemented keybinds, and return a dict of + {tool: [(full_command, keybinding, context), ...]}. + """ + with open('dfhack.init-example') as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + keybindings = dict() + for k in lines: + first, command = k.split(' ', 1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + return keybindings + +KEYBINDS = get_keybinds() + + +# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments +def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, + options={}, content=[]): + """Custom role parser for DFHack default keybinds.""" + roles.set_classes(options) + if text not in KEYBINDS: + msg = inliner.reporter.error( + 'no keybinding for {} in dfhack.init-example'.format(text), + line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + newnode = nodes.paragraph() + for cmd, key, ctx in KEYBINDS[text]: + n = nodes.paragraph() + newnode += n + n += nodes.strong('Keybinding: ', 'Keybinding: ') + for k in key: + n += nodes.inline(k, k, classes=['kbd']) + if cmd != text: + n += nodes.inline(' -> ', ' -> ') + n += nodes.literal(cmd, cmd, classes=['guilabel']) + if ctx: + n += nodes.inline(' in ', ' in ') + n += nodes.literal(ctx, ctx) + return [newnode], [] + + +roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) # -- Autodoc for DFhack scripts ------------------------------------------- @@ -29,43 +91,60 @@ def doc_dir(dirname, files): continue with open(os.path.join(dirname, f), 'r', encoding='utf8') as fstream: text = [l.rstrip() for l in fstream.readlines() if l.strip()] + # Some legacy lua files use the ruby tokens (in 3rdparty scripts) + tokens = ('=begin', '=end') + if f[-4:] == '.lua' and any('[====[' in line for line in text): + tokens = ('[====[', ']====]') command = None for line in text: if command and line == len(line) * '=': - yield command, sdir + '/' + f + yield command, sdir + '/' + f, tokens[0], tokens[1] break command = line +def doc_all_dirs(): + """Collect the commands and paths to include in our docs.""" + scripts = [] + for root, _, files in os.walk('scripts'): + scripts.extend(doc_dir(root, files)) + return tuple(scripts) + +DOC_ALL_DIRS = doc_all_dirs() + + def document_scripts(): """Autodoc for files with the magic script documentation marker strings. - Creates a file for eack kind of script (base/devel/fix/gui/modtools) - with all the ".. include::" directives to pull out docs between the - magic strings. + Returns a dict of script-kinds to lists of .rst include directives. """ - # First, we collect the commands and paths to include in our docs - scripts = [] - for root, _, files in os.walk('scripts'): - scripts.extend(doc_dir(root, files)) # Next we split by type and create include directives sorted by command kinds = {'base': [], 'devel': [], 'fix': [], 'gui': [], 'modtools': []} - for s in scripts: + for s in DOC_ALL_DIRS: k_fname = s[0].split('/', 1) if len(k_fname) == 1: kinds['base'].append(s) else: - kinds.get(k_fname[0], []).append(s) - template = ('.. _{}:\n\n' - '.. include:: /{}\n' - ' :start-after: =begin\n' - ' :end-before: =end\n') - for key, value in kinds.items(): - kinds[key] = [template.format(x[0], x[1]) - for x in sorted(value, key=lambda x: x[0])] - # Finally, we write our _auto/* files for each kind of script - if not os.path.isdir('docs/_auto'): - os.mkdir('docs/_auto') + kinds[k_fname[0]].append(s) + + def template(arg): + tmp = '.. _{}:\n\n.. include:: /{}\n' +\ + ' :start-after: {}\n :end-before: {}\n' + if arg[0] in KEYBINDS: + tmp += '\n:dfhack-keybind:`{}`\n'.format(arg[0]) + return tmp.format(*arg) + + return {key: '\n\n'.join(map(template, sorted(value))) + for key, value in kinds.items()} + + +def write_script_docs(): + """ + Creates a file for eack kind of script (base/devel/fix/gui/modtools) + with all the ".. include::" directives to pull out docs between the + magic strings. + """ + kinds = document_scripts() head = { 'base': 'Basic Scripts', 'devel': 'Development Scripts', @@ -76,17 +155,34 @@ def document_scripts(): title = ('.. _{k}:\n\n{l}\n{t}\n{l}\n\n' '.. include:: /scripts/{a}about.txt\n\n' '.. contents::\n\n').format( - k=k, t=head[k], l=len(head[k])*'#', a=('' if k=='base' else k+'/')) + k=k, t=head[k], + l=len(head[k])*'#', + a=('' if k == 'base' else k + '/') + ) mode = 'w' if sys.version_info.major > 2 else 'wb' with open('docs/_auto/{}.rst'.format(k), mode) as outfile: outfile.write(title) - outfile.write('\n\n'.join(kinds[k])) + outfile.write(kinds[k]) -# Actually call the docs generator -document_scripts() +def all_keybinds_documented(): + """Check that all keybindings are documented with the :dfhack-keybind: + directive somewhere.""" + configured_binds = set(KEYBINDS) + script_commands = set(i[0] for i in DOC_ALL_DIRS) + with open('./docs/Plugins.rst') as f: + plugin_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) + undocumented_binds = configured_binds - script_commands - plugin_binds + if undocumented_binds: + raise ValueError('The following DFHack commands have undocumented' + 'keybindings: {}'.format(sorted(undocumented_binds))) +# Actually call the docs generator and run test +generate_changelog() +write_script_docs() +all_keybinds_documented() + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -135,14 +231,16 @@ author = 'The DFHack Team' def get_version(): """Return the DFHack version string, from CMakeLists.txt""" - version = release = '' + version = release = '' #pylint:disable=redefined-outer-name + pattern = re.compile(r'set\((df_version|dfhack_release)\s+"(.+?)"\)') try: with open('CMakeLists.txt') as f: for s in f.readlines(): - if fnmatch.fnmatch(s.upper(), 'SET(DF_VERSION "?.??.??")\n'): - version = s.upper().replace('SET(DF_VERSION "', '') - elif fnmatch.fnmatch(s.upper(), 'SET(DFHACK_RELEASE "r*")\n'): - release = s.upper().replace('SET(DFHACK_RELEASE "', '').lower() + for match in pattern.findall(s.lower()): + if match[0] == 'df_version': + version = match[1] + elif match[0] == 'dfhack_release': + release = match[1] return (version + '-' + release).replace('")\n', '') except IOError: return 'unknown' @@ -157,11 +255,8 @@ version = release = get_version() # Usually you set "language" from the command line for these cases. language = None -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# strftime format for |today| and 'Last updated on:' timestamp at page bottom +today_fmt = html_last_updated_fmt = '%Y-%m-%d' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -169,8 +264,9 @@ exclude_patterns = [ 'README.md', 'docs/html*', 'depends/*', - 'scripts/3rdparty/*', 'build*', + 'docs/_auto/news*', + 'docs/_changelogs/', ] # The reST default role (used for this markup: `text`) to use for all @@ -195,16 +291,13 @@ html_style = 'dfhack.css' # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - #'logo': 'logo.png', + 'logo': 'dfhack-logo.png', 'github_user': 'DFHack', 'github_repo': 'dfhack', 'github_button': False, 'travis_button': False, } -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None @@ -226,19 +319,6 @@ html_favicon = 'docs/styles/dfhack-icon.ico' # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['docs/styles'] -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%Y-%m-%d' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - # Custom sidebar templates, maps document names to template names. html_sidebars = { '**': [ @@ -249,113 +329,18 @@ html_sidebars = { ] } -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. html_use_index = False -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'DFHackdoc' - # -- Options for LaTeX output --------------------------------------------- -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'DFHack.tex', 'DFHack Documentation', - 'The DFHack Team', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'dfhack', 'DFHack Documentation', - [author], 1) + (master_doc, 'DFHack.tex', 'DFHack Documentation', + 'The DFHack Team', 'manual'), ] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'DFHack', 'DFHack Documentation', - author, 'DFHack', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index bf0345bfb..d8442b12a 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -2,7 +2,12 @@ add_subdirectory(lua) add_subdirectory(md5) add_subdirectory(protobuf) -add_subdirectory(tinyxml) + +# Don't build tinyxml if it's being externally linked against. +if(NOT TinyXML_FOUND) + add_subdirectory(tinyxml) +endif() + add_subdirectory(tthread) add_subdirectory(jsoncpp) # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. diff --git a/depends/clsocket b/depends/clsocket index dc7ff5dad..6a9153d05 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit dc7ff5dadf7b1bf96a82149b201d3674c97631d9 +Subproject commit 6a9153d053a250be34996b3fd86ac1166c3e17cb diff --git a/depends/copy-if-different.pl b/depends/copy-if-different.pl new file mode 100755 index 000000000..2ba08a80e --- /dev/null +++ b/depends/copy-if-different.pl @@ -0,0 +1,30 @@ +#!/usr/bin/perl + +# A replacement for "cmake -E copy_if_different" that supports multiple files, +# which old cmake versions do not support + +# Usage: copy-if-different.pl src-file [src-file...] dest-dir + +use strict; +use warnings; + +use Digest::SHA; +use File::Basename; +use File::Copy; + +sub sha_file { + my $filename = shift; + my $sha = Digest::SHA->new(256); + $sha->addfile($filename); + return $sha->hexdigest; +} + +my $dest_dir = pop @ARGV or die "no destination dir"; +-d $dest_dir or die "not a directory: $dest_dir"; +my @src_files = @ARGV or die "no source files"; + +foreach my $file (@src_files) { + my $dest = "$dest_dir/" . basename($file); + next if -f $dest && sha_file($file) eq sha_file($dest); + copy($file, $dest); +} diff --git a/depends/gunzip.pl b/depends/gunzip.pl new file mode 100755 index 000000000..caabad5a8 --- /dev/null +++ b/depends/gunzip.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use IO::Uncompress::Gunzip qw(gunzip $GunzipError); + +my %args = map { $_ => 1 } @ARGV; + +my $in_file = $ARGV[0] or die "no input file"; +# remove extension +(my $out_file = $in_file) =~ s{\.[^.]+$}{}; + +if (! -f $in_file) { + die "input file does not exist: \"$in_file\"\n"; +} +if (-f $out_file and !exists($args{'--force'})) { + die "output file exists, not overwriting: \"$out_file\""; +} + +gunzip $in_file => $out_file, BinModeOut => 1 or die "gunzip failed: $GunzipError\n"; diff --git a/depends/jsoncpp/jsoncpp.cpp b/depends/jsoncpp/jsoncpp.cpp index eb38217ef..009d04e7a 100644 --- a/depends/jsoncpp/jsoncpp.cpp +++ b/depends/jsoncpp/jsoncpp.cpp @@ -3922,8 +3922,10 @@ Value& Path::make(Value& root) const { #define isfinite finite #else #include +#ifndef isfinite // fix isfinite on Ubuntu 18.04 #define isfinite std::isfinite #endif +#endif #if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below #define snprintf _snprintf diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index b77bce6f7..7dcae8068 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -3,16 +3,23 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_APICHECK") +# Make bit32 library available (for things like bit32.extract()) +ADD_DEFINITIONS(-DLUA_COMPAT_BITLIB) + IF(WIN32) - ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) + ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE /wd4334 ) ELSE() ADD_DEFINITIONS ( -DLUA_USE_POSIX -DLUA_USE_DLOPEN ) SET ( LIBS m dl ) ENDIF() IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(CMAKE_C_FLAGS "-m32") + ADD_DEFINITIONS(-DLINUX_BUILD) + IF(DFHACK_BUILD_64) + SET(CMAKE_C_FLAGS "-m64 -mno-avx") + ELSE() + SET(CMAKE_C_FLAGS "-m32") + ENDIF() ENDIF() SET (HDR_LIBLUA @@ -76,6 +83,7 @@ src/ltable.c src/ltablib.c src/ltm.c src/lundump.c +src/lutf8lib.c src/lvm.c src/lzio.c ) diff --git a/depends/lua/include/lapi.h b/depends/lua/include/lapi.h index c7d34ad84..6d36dee3f 100644 --- a/depends/lua/include/lapi.h +++ b/depends/lua/include/lapi.h @@ -1,5 +1,5 @@ /* -** $Id: lapi.h,v 2.7.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lapi.h,v 2.9 2015/03/06 19:49:50 roberto Exp $ ** Auxiliary functions from Lua API ** See Copyright Notice in lua.h */ diff --git a/depends/lua/include/lauxlib.h b/depends/lua/include/lauxlib.h index 0fb023b8e..ddb7c2283 100644 --- a/depends/lua/include/lauxlib.h +++ b/depends/lua/include/lauxlib.h @@ -1,5 +1,5 @@ /* -** $Id: lauxlib.h,v 1.120.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lauxlib.h,v 1.129 2015/11/23 11:29:43 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ @@ -16,7 +16,7 @@ -/* extra error code for `luaL_load' */ +/* extra error code for 'luaL_load' */ #define LUA_ERRFILE (LUA_ERRERR+1) @@ -26,30 +26,30 @@ typedef struct luaL_Reg { } luaL_Reg; -LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver); -#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM) +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); -LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); -LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, size_t *l); -LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, const char *def, size_t *l); -LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); -LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); -LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); -LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, lua_Integer def); -LUALIB_API lua_Unsigned (luaL_checkunsigned) (lua_State *L, int numArg); -LUALIB_API lua_Unsigned (luaL_optunsigned) (lua_State *L, int numArg, - lua_Unsigned def); LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); -LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); -LUALIB_API void (luaL_checkany) (lua_State *L, int narg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); @@ -59,13 +59,13 @@ LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); LUALIB_API void (luaL_where) (lua_State *L, int lvl); LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); -LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, const char *const lst[]); LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); LUALIB_API int (luaL_execresult) (lua_State *L, int stat); -/* pre-defined references */ +/* predefined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) @@ -83,7 +83,7 @@ LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); LUALIB_API lua_State *(luaL_newstate) (void); -LUALIB_API int (luaL_len) (lua_State *L, int idx); +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, const char *r); @@ -108,16 +108,13 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, #define luaL_newlibtable(L,l) \ lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) -#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) -#define luaL_argcheck(L, cond,numarg,extramsg) \ - ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) #define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) #define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) -#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) -#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) -#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) -#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) #define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) @@ -207,6 +204,53 @@ LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, #endif +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + #endif diff --git a/depends/lua/include/lcode.h b/depends/lua/include/lcode.h index 6a1424cf5..cd306d573 100644 --- a/depends/lua/include/lcode.h +++ b/depends/lua/include/lcode.h @@ -1,5 +1,5 @@ /* -** $Id: lcode.h,v 1.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lcode.h,v 1.64 2016/01/05 16:22:37 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ @@ -24,7 +24,11 @@ ** grep "ORDER OPR" if you change these enums (ORDER OP) */ typedef enum BinOpr { - OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, + OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW, + OPR_DIV, + OPR_IDIV, + OPR_BAND, OPR_BOR, OPR_BXOR, + OPR_SHL, OPR_SHR, OPR_CONCAT, OPR_EQ, OPR_LT, OPR_LE, OPR_NE, OPR_GT, OPR_GE, @@ -33,10 +37,11 @@ typedef enum BinOpr { } BinOpr; -typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; +typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; -#define getcode(fs,e) ((fs)->f->code[(e)->u.info]) +/* get (pointer to) instruction of given 'expdesc' */ +#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info]) #define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) @@ -52,7 +57,7 @@ LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); -LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r); +LUAI_FUNC int luaK_intK (FuncState *fs, lua_Integer n); LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); diff --git a/depends/lua/include/lctype.h b/depends/lua/include/lctype.h index b09b21a33..99c7d1223 100644 --- a/depends/lua/include/lctype.h +++ b/depends/lua/include/lctype.h @@ -1,5 +1,5 @@ /* -** $Id: lctype.h,v 1.12.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lctype.h,v 1.12 2011/07/15 12:50:29 roberto Exp $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ diff --git a/depends/lua/include/ldebug.h b/depends/lua/include/ldebug.h index 6445c763e..0e31546b1 100644 --- a/depends/lua/include/ldebug.h +++ b/depends/lua/include/ldebug.h @@ -1,5 +1,5 @@ /* -** $Id: ldebug.h,v 2.7.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldebug.h,v 2.14 2015/05/22 17:45:56 roberto Exp $ ** Auxiliary functions from Debug Interface module ** See Copyright Notice in lua.h */ @@ -13,22 +13,27 @@ #define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) -#define getfuncline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0) +#define getfuncline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : -1) #define resethookcount(L) (L->hookcount = L->basehookcount) -/* Active Lua function (given call info) */ -#define ci_func(ci) (clLvalue((ci)->func)) - LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *opname); -LUAI_FUNC l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2); -LUAI_FUNC l_noret luaG_aritherror (lua_State *L, const TValue *p1, +LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, + const char *msg); +LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); +LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, + TString *src, int line); LUAI_FUNC l_noret luaG_errormsg (lua_State *L); +LUAI_FUNC void luaG_traceexec (lua_State *L); + #endif diff --git a/depends/lua/include/ldo.h b/depends/lua/include/ldo.h index d3d3082c9..4f5d51c3c 100644 --- a/depends/lua/include/ldo.h +++ b/depends/lua/include/ldo.h @@ -1,5 +1,5 @@ /* -** $Id: ldo.h,v 2.20.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldo.h,v 2.29 2015/12/21 13:02:14 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ @@ -13,31 +13,43 @@ #include "lzio.h" -#define luaD_checkstack(L,n) if (L->stack_last - L->top <= (n)) \ - luaD_growstack(L, n); else condmovestack(L); +/* +** Macro to check stack size and grow stack if needed. Parameters +** 'pre'/'pos' allow the macro to preserve a pointer into the +** stack across reallocations, doing the work only when needed. +** 'condmovestack' is used in heavy tests to force a stack reallocation +** at every check. +*/ +#define luaD_checkstackaux(L,n,pre,pos) \ + if (L->stack_last - L->top <= (n)) \ + { pre; luaD_growstack(L, n); pos; } else { condmovestack(L,pre,pos); } + +/* In general, 'pre'/'pos' are empty (nothing to save) */ +#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0) -#define incr_top(L) {L->top++; luaD_checkstack(L,0);} #define savestack(L,p) ((char *)(p) - (char *)L->stack) #define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) -/* type of protected functions, to be ran by `runprotected' */ +/* type of protected functions, to be ran by 'runprotected' */ typedef void (*Pfunc) (lua_State *L, void *ud); LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, const char *mode); LUAI_FUNC void luaD_hook (lua_State *L, int event, int line); LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); -LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults, - int allowyield); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); -LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); +LUAI_FUNC int luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult, + int nres); LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); LUAI_FUNC void luaD_growstack (lua_State *L, int n); LUAI_FUNC void luaD_shrinkstack (lua_State *L); +LUAI_FUNC void luaD_inctop (lua_State *L); LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); diff --git a/depends/lua/include/lfunc.h b/depends/lua/include/lfunc.h index ca0d3a3e0..2eeb0d5a4 100644 --- a/depends/lua/include/lfunc.h +++ b/depends/lua/include/lfunc.h @@ -1,5 +1,5 @@ /* -** $Id: lfunc.h,v 2.8.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lfunc.h,v 2.15 2015/01/13 15:49:11 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ @@ -18,14 +18,42 @@ cast(int, sizeof(TValue *)*((n)-1))) +/* test whether thread is in 'twups' list */ +#define isintwups(L) (L->twups != L) + + +/* +** maximum number of upvalues in a closure (both C and Lua). (Value +** must fit in a VM register.) +*/ +#define MAXUPVAL 255 + + +/* +** Upvalues for Lua closures +*/ +struct UpVal { + TValue *v; /* points to stack or to its own value */ + lu_mem refcount; /* reference counter */ + union { + struct { /* (when open) */ + UpVal *next; /* linked list */ + int touched; /* mark to avoid cycles with dead threads */ + } open; + TValue value; /* the value (when closed) */ + } u; +}; + +#define upisopen(up) ((up)->v != &(up)->u.value) + + LUAI_FUNC Proto *luaF_newproto (lua_State *L); -LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems); -LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems); -LUAI_FUNC UpVal *luaF_newupval (lua_State *L); +LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems); +LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); +LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); LUAI_FUNC void luaF_close (lua_State *L, StkId level); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); -LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, int pc); diff --git a/depends/lua/include/lgc.h b/depends/lua/include/lgc.h index 84bb1cdf9..aed3e18a5 100644 --- a/depends/lua/include/lgc.h +++ b/depends/lua/include/lgc.h @@ -1,5 +1,5 @@ /* -** $Id: lgc.h,v 2.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lgc.h,v 2.91 2015/12/21 13:02:14 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ @@ -38,36 +38,27 @@ */ #define GCSpropagate 0 #define GCSatomic 1 -#define GCSsweepstring 2 -#define GCSsweepudata 3 -#define GCSsweep 4 -#define GCSpause 5 +#define GCSswpallgc 2 +#define GCSswpfinobj 3 +#define GCSswptobefnz 4 +#define GCSswpend 5 +#define GCScallfin 6 +#define GCSpause 7 #define issweepphase(g) \ - (GCSsweepstring <= (g)->gcstate && (g)->gcstate <= GCSsweep) + (GCSswpallgc <= (g)->gcstate && (g)->gcstate <= GCSswpend) -#define isgenerational(g) ((g)->gckind == KGC_GEN) /* -** macros to tell when main invariant (white objects cannot point to black -** ones) must be kept. During a non-generational collection, the sweep +** macro to tell when main invariant (white objects cannot point to black +** ones) must be kept. During a collection, the sweep ** phase may break the invariant, as objects turned white may point to ** still-black objects. The invariant is restored when sweep ends and -** all objects are white again. During a generational collection, the -** invariant must be kept all times. +** all objects are white again. */ -#define keepinvariant(g) (isgenerational(g) || g->gcstate <= GCSatomic) - - -/* -** Outside the collector, the state in generational mode is kept in -** 'propagate', so 'keepinvariant' is always true. -*/ -#define keepinvariantout(g) \ - check_exp(g->gcstate == GCSpropagate || !isgenerational(g), \ - g->gcstate <= GCSatomic) +#define keepinvariant(g) ((g)->gcstate <= GCSatomic) /* @@ -83,75 +74,74 @@ #define testbit(x,b) testbits(x, bitmask(b)) -/* Layout for bit use in `marked' field: */ +/* Layout for bit use in 'marked' field: */ #define WHITE0BIT 0 /* object is white (type 0) */ #define WHITE1BIT 1 /* object is white (type 1) */ #define BLACKBIT 2 /* object is black */ -#define FINALIZEDBIT 3 /* object has been separated for finalization */ -#define SEPARATED 4 /* object is in 'finobj' list or in 'tobefnz' */ -#define FIXEDBIT 5 /* object is fixed (should not be collected) */ -#define OLDBIT 6 /* object is old (only in generational mode) */ +#define FINALIZEDBIT 3 /* object has been marked for finalization */ /* bit 7 is currently used by tests (luaL_checkmemory) */ #define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) -#define iswhite(x) testbits((x)->gch.marked, WHITEBITS) -#define isblack(x) testbit((x)->gch.marked, BLACKBIT) +#define iswhite(x) testbits((x)->marked, WHITEBITS) +#define isblack(x) testbit((x)->marked, BLACKBIT) #define isgray(x) /* neither white nor black */ \ - (!testbits((x)->gch.marked, WHITEBITS | bitmask(BLACKBIT))) - -#define isold(x) testbit((x)->gch.marked, OLDBIT) + (!testbits((x)->marked, WHITEBITS | bitmask(BLACKBIT))) -/* MOVE OLD rule: whenever an object is moved to the beginning of - a GC list, its old bit must be cleared */ -#define resetoldbit(o) resetbit((o)->gch.marked, OLDBIT) +#define tofinalize(x) testbit((x)->marked, FINALIZEDBIT) -#define otherwhite(g) (g->currentwhite ^ WHITEBITS) +#define otherwhite(g) ((g)->currentwhite ^ WHITEBITS) #define isdeadm(ow,m) (!(((m) ^ WHITEBITS) & (ow))) -#define isdead(g,v) isdeadm(otherwhite(g), (v)->gch.marked) +#define isdead(g,v) isdeadm(otherwhite(g), (v)->marked) -#define changewhite(x) ((x)->gch.marked ^= WHITEBITS) -#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) - -#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) +#define changewhite(x) ((x)->marked ^= WHITEBITS) +#define gray2black(x) l_setbit((x)->marked, BLACKBIT) #define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) -#define luaC_condGC(L,c) \ - {if (G(L)->GCdebt > 0) {c;}; condchangemem(L);} -#define luaC_checkGC(L) luaC_condGC(L, luaC_step(L);) +/* +** Does one step of collection when debt becomes positive. 'pre'/'pos' +** allows some adjustments to be done only when needed. macro +** 'condchangemem' is used only for heavy tests (forcing a full +** GC cycle on every opportunity) +*/ +#define luaC_condGC(L,pre,pos) \ + { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \ + condchangemem(L,pre,pos); } +/* more often than not, 'pre'/'pos' are empty */ +#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) -#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ - luaC_barrier_(L,obj2gco(p),gcvalue(v)); } -#define luaC_barrierback(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ - luaC_barrierback_(L,p); } +#define luaC_barrier(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrier_(L,obj2gco(p),gcvalue(v)) : cast_void(0)) -#define luaC_objbarrier(L,p,o) \ - { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ - luaC_barrier_(L,obj2gco(p),obj2gco(o)); } +#define luaC_barrierback(L,p,v) ( \ + (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) ? \ + luaC_barrierback_(L,p) : cast_void(0)) -#define luaC_objbarrierback(L,p,o) \ - { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) luaC_barrierback_(L,p); } +#define luaC_objbarrier(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) -#define luaC_barrierproto(L,p,c) \ - { if (isblack(obj2gco(p))) luaC_barrierproto_(L,p,c); } +#define luaC_upvalbarrier(L,uv) ( \ + (iscollectable((uv)->v) && !upisopen(uv)) ? \ + luaC_upvalbarrier_(L,uv) : cast_void(0)) +LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); LUAI_FUNC void luaC_freeallobjects (lua_State *L); LUAI_FUNC void luaC_step (lua_State *L); -LUAI_FUNC void luaC_forcestep (lua_State *L); LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); -LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, - GCObject **list, int offset); +LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); -LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); -LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c); +LUAI_FUNC void luaC_barrierback_ (lua_State *L, Table *o); +LUAI_FUNC void luaC_upvalbarrier_ (lua_State *L, UpVal *uv); LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); -LUAI_FUNC void luaC_checkupvalcolor (global_State *g, UpVal *uv); -LUAI_FUNC void luaC_changemode (lua_State *L, int mode); +LUAI_FUNC void luaC_upvdeccount (lua_State *L, UpVal *uv); + #endif diff --git a/depends/lua/include/llex.h b/depends/lua/include/llex.h index a4acdd302..2363d87e4 100644 --- a/depends/lua/include/llex.h +++ b/depends/lua/include/llex.h @@ -1,5 +1,5 @@ /* -** $Id: llex.h,v 1.72.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: llex.h,v 1.79 2016/05/02 14:02:12 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ @@ -14,6 +14,10 @@ #define FIRST_RESERVED 257 +#if !defined(LUA_ENV) +#define LUA_ENV "_ENV" +#endif + /* * WARNING: if you change the order of this enumeration, @@ -26,8 +30,10 @@ enum RESERVED { TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* other terminal symbols */ - TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_DBCOLON, TK_EOS, - TK_NUMBER, TK_NAME, TK_STRING + TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, + TK_SHL, TK_SHR, + TK_DBCOLON, TK_EOS, + TK_FLT, TK_INT, TK_NAME, TK_STRING }; /* number of reserved words */ @@ -36,6 +42,7 @@ enum RESERVED { typedef union { lua_Number r; + lua_Integer i; TString *ts; } SemInfo; /* semantics information */ @@ -51,17 +58,17 @@ typedef struct Token { typedef struct LexState { int current; /* current character (charint) */ int linenumber; /* input line counter */ - int lastline; /* line of last token `consumed' */ + int lastline; /* line of last token 'consumed' */ Token t; /* current token */ Token lookahead; /* look ahead token */ struct FuncState *fs; /* current function (parser) */ struct lua_State *L; ZIO *z; /* input stream */ Mbuffer *buff; /* buffer for tokens */ + Table *h; /* to avoid collection/reuse strings */ struct Dyndata *dyd; /* dynamic structures used by the parser */ TString *source; /* current source name */ TString *envn; /* environment variable name */ - char decpoint; /* locale decimal point */ } LexState; diff --git a/depends/lua/include/llimits.h b/depends/lua/include/llimits.h index 152dd0551..f21377fef 100644 --- a/depends/lua/include/llimits.h +++ b/depends/lua/include/llimits.h @@ -1,6 +1,6 @@ /* -** $Id: llimits.h,v 1.103.1.1 2013/04/12 18:48:47 roberto Exp $ -** Limits, basic types, and some other `installation-dependent' definitions +** $Id: llimits.h,v 1.141 2015/11/19 19:16:22 roberto Exp $ +** Limits, basic types, and some other 'installation-dependent' definitions ** See Copyright Notice in lua.h */ @@ -14,54 +14,77 @@ #include "lua.h" - -typedef unsigned LUA_INT32 lu_int32; - +/* +** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count +** the total memory used by Lua (in bytes). Usually, 'size_t' and +** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines. +*/ +#if defined(LUAI_MEM) /* { external definitions? */ typedef LUAI_UMEM lu_mem; - typedef LUAI_MEM l_mem; +#elif LUAI_BITSINT >= 32 /* }{ */ +typedef size_t lu_mem; +typedef ptrdiff_t l_mem; +#else /* 16-bit ints */ /* }{ */ +typedef unsigned long lu_mem; +typedef long l_mem; +#endif /* } */ - -/* chars used as small naturals (so that `char' is reserved for characters) */ +/* chars used as small naturals (so that 'char' is reserved for characters) */ typedef unsigned char lu_byte; -#define MAX_SIZET ((size_t)(~(size_t)0)-2) +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +/* maximum size visible for Lua (must be representable in a lua_Integer */ +#define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ + : (size_t)(LUA_MAXINTEGER)) + -#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)) -#define MAX_LMEM ((l_mem) ((MAX_LUMEM >> 1) - 2)) +#define MAX_LMEM ((l_mem)(MAX_LUMEM >> 1)) -#define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ +#define MAX_INT INT_MAX /* maximum value of an int */ + /* -** conversion of pointer to integer +** conversion of pointer to unsigned integer: ** this is for hashing only; there is no problem if the integer ** cannot hold the whole pointer value */ -#define IntPoint(p) ((unsigned int)(lu_mem)(p)) +#define point2uint(p) ((unsigned int)((size_t)(p) & UINT_MAX)) /* type to ensure maximum alignment */ -#if !defined(LUAI_USER_ALIGNMENT_T) -#define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; } +#if defined(LUAI_USER_ALIGNMENT_T) +typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; +#else +typedef union { + lua_Number n; + double u; + void *s; + lua_Integer i; + long l; +} L_Umaxalign; #endif -typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; -/* result of a `usual argument conversion' over lua_Number */ +/* types of 'usual argument conversions' for lua_Number and lua_Integer */ typedef LUAI_UACNUMBER l_uacNumber; +typedef LUAI_UACINT l_uacInt; /* internal assertions for in-house debugging */ #if defined(lua_assert) #define check_exp(c,e) (lua_assert(c), (e)) /* to avoid problems with conditions too long */ -#define lua_longassert(c) { if (!(c)) lua_assert(0); } +#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0)) #else #define lua_assert(c) ((void)0) #define check_exp(c,e) (e) @@ -72,38 +95,49 @@ typedef LUAI_UACNUMBER l_uacNumber; ** assertion for checking API calls */ #if !defined(luai_apicheck) - -#if defined(LUA_USE_APICHECK) -#include -#define luai_apicheck(L,e) assert(e) -#else -#define luai_apicheck(L,e) lua_assert(e) -#endif - +#define luai_apicheck(l,e) lua_assert(e) #endif #define api_check(l,e,msg) luai_apicheck(l,(e) && msg) +/* macro to avoid warnings about unused variables */ #if !defined(UNUSED) -#define UNUSED(x) ((void)(x)) /* to avoid warnings */ +#define UNUSED(x) ((void)(x)) #endif +/* type casts (a macro highlights casts in the code) */ #define cast(t, exp) ((t)(exp)) +#define cast_void(i) cast(void, (i)) #define cast_byte(i) cast(lu_byte, (i)) #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) #define cast_uchar(i) cast(unsigned char, (i)) +/* cast a signed lua_Integer to lua_Unsigned */ +#if !defined(l_castS2U) +#define l_castS2U(i) ((lua_Unsigned)(i)) +#endif + +/* +** cast a lua_Unsigned to a signed lua_Integer; this cast is +** not strict ISO C, but two-complement architectures should +** work fine. +*/ +#if !defined(l_castU2S) +#define l_castU2S(i) ((lua_Integer)(i)) +#endif + + /* ** non-return type */ #if defined(__GNUC__) #define l_noret void __attribute__((noreturn)) -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && _MSC_VER >= 1200 #define l_noret void __declspec(noreturn) #else #define l_noret void @@ -119,29 +153,50 @@ typedef LUAI_UACNUMBER l_uacNumber; #define LUAI_MAXCCALLS 200 #endif -/* -** maximum number of upvalues in a closure (both C and Lua). (Value -** must fit in an unsigned char.) -*/ -#define MAXUPVAL UCHAR_MAX /* -** type for virtual-machine instructions +** type for virtual-machine instructions; ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) */ -typedef lu_int32 Instruction; - +#if LUAI_BITSINT >= 32 +typedef unsigned int Instruction; +#else +typedef unsigned long Instruction; +#endif -/* maximum stack for a Lua function */ -#define MAXSTACK 250 +/* +** Maximum length for short strings, that is, strings that are +** internalized. (Cannot be smaller than reserved words or tags for +** metamethods, as these strings must be internalized; +** #("function") = 8, #("__newindex") = 10.) +*/ +#if !defined(LUAI_MAXSHORTLEN) +#define LUAI_MAXSHORTLEN 40 +#endif -/* minimum size for the string table (must be power of 2) */ +/* +** Initial size for the string table (must be power of 2). +** The Lua core alone registers ~50 strings (reserved words + +** metaevent keys + a few others). Libraries would typically add +** a few dozens more. +*/ #if !defined(MINSTRTABSIZE) -#define MINSTRTABSIZE 32 +#define MINSTRTABSIZE 128 +#endif + + +/* +** Size of cache for strings in the API. 'N' is the number of +** sets (better be a prime) and "M" is the size of each set (M == 1 +** makes a direct cache.) +*/ +#if !defined(STRCACHE_N) +#define STRCACHE_N 53 +#define STRCACHE_M 2 #endif @@ -151,13 +206,21 @@ typedef lu_int32 Instruction; #endif +/* +** macros that are executed whenever program enters the Lua core +** ('lua_lock') and leaves the core ('lua_unlock') +*/ #if !defined(lua_lock) -#define lua_lock(L) ((void) 0) -#define lua_unlock(L) ((void) 0) +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) #endif +/* +** macro executed during Lua functions at points where the +** function can yield. +*/ #if !defined(luai_threadyield) -#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} #endif @@ -183,127 +246,78 @@ typedef lu_int32 Instruction; #endif #if !defined(luai_userstateresume) -#define luai_userstateresume(L,n) ((void)L) +#define luai_userstateresume(L,n) ((void)L) #endif #if !defined(luai_userstateyield) -#define luai_userstateyield(L,n) ((void)L) +#define luai_userstateyield(L,n) ((void)L) #endif -/* -** lua_number2int is a macro to convert lua_Number to int. -** lua_number2integer is a macro to convert lua_Number to lua_Integer. -** lua_number2unsigned is a macro to convert a lua_Number to a lua_Unsigned. -** lua_unsigned2number is a macro to convert a lua_Unsigned to a lua_Number. -** luai_hashnum is a macro to hash a lua_Number value into an integer. -** The hash must be deterministic and give reasonable values for -** both small and large values (outside the range of integers). -*/ - -#if defined(MS_ASMTRICK) || defined(LUA_MSASMTRICK) /* { */ -/* trick with Microsoft assembler for X86 */ - -#define lua_number2int(i,n) __asm {__asm fld n __asm fistp i} -#define lua_number2integer(i,n) lua_number2int(i, n) -#define lua_number2unsigned(i,n) \ - {__int64 l; __asm {__asm fld n __asm fistp l} i = (unsigned int)l;} - - -#elif defined(LUA_IEEE754TRICK) /* }{ */ -/* the next trick should work on any machine using IEEE754 with - a 32-bit int type */ - -union luai_Cast { double l_d; LUA_INT32 l_p[2]; }; - -#if !defined(LUA_IEEEENDIAN) /* { */ -#define LUAI_EXTRAIEEE \ - static const union luai_Cast ieeeendian = {-(33.0 + 6755399441055744.0)}; -#define LUA_IEEEENDIANLOC (ieeeendian.l_p[1] == 33) -#else -#define LUA_IEEEENDIANLOC LUA_IEEEENDIAN -#define LUAI_EXTRAIEEE /* empty */ -#endif /* } */ -#define lua_number2int32(i,n,t) \ - { LUAI_EXTRAIEEE \ - volatile union luai_Cast u; u.l_d = (n) + 6755399441055744.0; \ - (i) = (t)u.l_p[LUA_IEEEENDIANLOC]; } -#define luai_hashnum(i,n) \ - { volatile union luai_Cast u; u.l_d = (n) + 1.0; /* avoid -0 */ \ - (i) = u.l_p[0]; (i) += u.l_p[1]; } /* add double bits for his hash */ - -#define lua_number2int(i,n) lua_number2int32(i, n, int) -#define lua_number2unsigned(i,n) lua_number2int32(i, n, lua_Unsigned) +/* +** The luai_num* macros define the primitive operations over numbers. +*/ -/* the trick can be expanded to lua_Integer when it is a 32-bit value */ -#if defined(LUA_IEEELL) -#define lua_number2integer(i,n) lua_number2int32(i, n, lua_Integer) +/* floor division (defined as 'floor(a/b)') */ +#if !defined(luai_numidiv) +#define luai_numidiv(L,a,b) ((void)L, l_floor(luai_numdiv(L,a,b))) #endif -#endif /* } */ - - -/* the following definitions always work, but may be slow */ - -#if !defined(lua_number2int) -#define lua_number2int(i,n) ((i)=(int)(n)) +/* float division */ +#if !defined(luai_numdiv) +#define luai_numdiv(L,a,b) ((a)/(b)) #endif -#if !defined(lua_number2integer) -#define lua_number2integer(i,n) ((i)=(lua_Integer)(n)) +/* +** modulo: defined as 'a - floor(a/b)*b'; this definition gives NaN when +** 'b' is huge, but the result should be 'a'. 'fmod' gives the result of +** 'a - trunc(a/b)*b', and therefore must be corrected when 'trunc(a/b) +** ~= floor(a/b)'. That happens when the division has a non-integer +** negative result, which is equivalent to the test below. +*/ +#if !defined(luai_nummod) +#define luai_nummod(L,a,b,m) \ + { (m) = l_mathop(fmod)(a,b); if ((m)*(b) < 0) (m) += (b); } #endif -#if !defined(lua_number2unsigned) /* { */ -/* the following definition assures proper modulo behavior */ -#if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) -#include -#define SUPUNSIGNED ((lua_Number)(~(lua_Unsigned)0) + 1) -#define lua_number2unsigned(i,n) \ - ((i)=(lua_Unsigned)((n) - floor((n)/SUPUNSIGNED)*SUPUNSIGNED)) -#else -#define lua_number2unsigned(i,n) ((i)=(lua_Unsigned)(n)) +/* exponentiation */ +#if !defined(luai_numpow) +#define luai_numpow(L,a,b) ((void)L, l_mathop(pow)(a,b)) #endif -#endif /* } */ - -#if !defined(lua_unsigned2number) -/* on several machines, coercion from unsigned to double is slow, - so it may be worth to avoid */ -#define lua_unsigned2number(u) \ - (((u) <= (lua_Unsigned)INT_MAX) ? (lua_Number)(int)(u) : (lua_Number)(u)) +/* the others are quite standard operations */ +#if !defined(luai_numadd) +#define luai_numadd(L,a,b) ((a)+(b)) +#define luai_numsub(L,a,b) ((a)-(b)) +#define luai_nummul(L,a,b) ((a)*(b)) +#define luai_numunm(L,a) (-(a)) +#define luai_numeq(a,b) ((a)==(b)) +#define luai_numlt(a,b) ((a)<(b)) +#define luai_numle(a,b) ((a)<=(b)) +#define luai_numisnan(a) (!luai_numeq((a), (a))) #endif -#if defined(ltable_c) && !defined(luai_hashnum) - -#include -#include - -#define luai_hashnum(i,n) { int e; \ - n = l_mathop(frexp)(n, &e) * (lua_Number)(INT_MAX - DBL_MAX_EXP); \ - lua_number2int(i, n); i += e; } - -#endif - /* ** macro to control inclusion of some hard tests on stack reallocation */ #if !defined(HARDSTACKTESTS) -#define condmovestack(L) ((void)0) +#define condmovestack(L,pre,pos) ((void)0) #else /* realloc stack keeping its size */ -#define condmovestack(L) luaD_reallocstack((L), (L)->stacksize) +#define condmovestack(L,pre,pos) \ + { int sz_ = (L)->stacksize; pre; luaD_reallocstack((L), sz_); pos; } #endif #if !defined(HARDMEMTESTS) -#define condchangemem(L) condmovestack(L) +#define condchangemem(L,pre,pos) ((void)0) #else -#define condchangemem(L) \ - ((void)(!(G(L)->gcrunning) || (luaC_fullgc(L, 0), 1))) +#define condchangemem(L,pre,pos) \ + { if (G(L)->gcrunning) { pre; luaC_fullgc(L, 0); pos; } } #endif #endif diff --git a/depends/lua/include/lmem.h b/depends/lua/include/lmem.h index bd4f4e072..30f484895 100644 --- a/depends/lua/include/lmem.h +++ b/depends/lua/include/lmem.h @@ -1,5 +1,5 @@ /* -** $Id: lmem.h,v 1.40.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lmem.h,v 1.43 2014/12/19 17:26:14 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ @@ -15,20 +15,32 @@ /* -** This macro avoids the runtime division MAX_SIZET/(e), as 'e' is -** always constant. -** The macro is somewhat complex to avoid warnings: -** +1 avoids warnings of "comparison has constant result"; -** cast to 'void' avoids warnings of "value unused". +** This macro reallocs a vector 'b' from 'on' to 'n' elements, where +** each element has size 'e'. In case of arithmetic overflow of the +** product 'n'*'e', it raises an error (calling 'luaM_toobig'). Because +** 'e' is always constant, it avoids the runtime division MAX_SIZET/(e). +** +** (The macro is somewhat complex to avoid warnings: The 'sizeof' +** comparison avoids a runtime comparison when overflow cannot occur. +** The compiler should be able to optimize the real test by itself, but +** when it does it, it may give a warning about "comparison is always +** false due to limited range of data type"; the +1 tricks the compiler, +** avoiding this warning but also this optimization.) */ #define luaM_reallocv(L,b,on,n,e) \ - (cast(void, \ - (cast(size_t, (n)+1) > MAX_SIZET/(e)) ? (luaM_toobig(L), 0) : 0), \ + (((sizeof(n) >= sizeof(size_t) && cast(size_t, (n)) + 1 > MAX_SIZET/(e)) \ + ? luaM_toobig(L) : cast_void(0)) , \ luaM_realloc_(L, (b), (on)*(e), (n)*(e))) +/* +** Arrays of chars do not need any test +*/ +#define luaM_reallocvchar(L,b,on,n) \ + cast(char *, luaM_realloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char))) + #define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) #define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) -#define luaM_freearray(L, b, n) luaM_reallocv(L, (b), n, 0, sizeof((b)[0])) +#define luaM_freearray(L, b, n) luaM_realloc_(L, (b), (n)*sizeof(*(b)), 0) #define luaM_malloc(L,s) luaM_realloc_(L, NULL, 0, (s)) #define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) diff --git a/depends/lua/include/lobject.h b/depends/lua/include/lobject.h index 3a630b944..2d52b4159 100644 --- a/depends/lua/include/lobject.h +++ b/depends/lua/include/lobject.h @@ -1,5 +1,5 @@ /* -** $Id: lobject.h,v 2.71.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lobject.h,v 2.116 2015/11/03 18:33:10 roberto Exp $ ** Type definitions for Lua objects ** See Copyright Notice in lua.h */ @@ -19,14 +19,13 @@ /* ** Extra tags for non-values */ -#define LUA_TPROTO LUA_NUMTAGS -#define LUA_TUPVAL (LUA_NUMTAGS+1) -#define LUA_TDEADKEY (LUA_NUMTAGS+2) +#define LUA_TPROTO LUA_NUMTAGS /* function prototypes */ +#define LUA_TDEADKEY (LUA_NUMTAGS+1) /* removed keys in tables */ /* ** number of all possible tags (including LUA_TNONE but excluding DEADKEY) */ -#define LUA_TOTALTAGS (LUA_TUPVAL+2) +#define LUA_TOTALTAGS (LUA_TPROTO + 2) /* @@ -36,8 +35,6 @@ ** bit 6: whether value is collectable */ -#define VARBITS (3 << 4) - /* ** LUA_TFUNCTION variants: @@ -57,6 +54,11 @@ #define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */ +/* Variant tags for numbers */ +#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) /* float numbers */ +#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) /* integer numbers */ + + /* Bit mark for collectable types */ #define BIT_ISCOLLECTABLE (1 << 6) @@ -65,9 +67,9 @@ /* -** Union of all collectable objects +** Common type for all collectable objects */ -typedef union GCObject GCObject; +typedef struct GCObject GCObject; /* @@ -78,21 +80,12 @@ typedef union GCObject GCObject; /* -** Common header in struct form +** Common type has only the common header */ -typedef struct GCheader { +struct GCObject { CommonHeader; -} GCheader; - - - -/* -** Union of all Lua values -*/ -typedef union Value Value; - +}; -#define numfield lua_Number n; /* numbers */ @@ -101,9 +94,26 @@ typedef union Value Value; ** an actual value plus a tag with its type. */ +/* +** Union of all Lua values +*/ +typedef union Value { + GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + int b; /* booleans */ + lua_CFunction f; /* light C functions */ + lua_Integer i; /* integer numbers */ + lua_Number n; /* float numbers */ +} Value; + + #define TValuefields Value value_; int tt_ -typedef struct lua_TValue TValue; + +typedef struct lua_TValue { + TValuefields; +} TValue; + /* macro defining a nil value */ @@ -111,7 +121,6 @@ typedef struct lua_TValue TValue; #define val_(o) ((o)->value_) -#define num_(o) (val_(o).n) /* raw type tag of a TValue */ @@ -124,13 +133,15 @@ typedef struct lua_TValue TValue; #define ttype(o) (rttype(o) & 0x3F) /* type tag of a TValue with no variants (bits 0-3) */ -#define ttypenv(o) (novariant(rttype(o))) +#define ttnov(o) (novariant(rttype(o))) /* Macros to test type */ #define checktag(o,t) (rttype(o) == (t)) -#define checktype(o,t) (ttypenv(o) == (t)) -#define ttisnumber(o) checktag((o), LUA_TNUMBER) +#define checktype(o,t) (ttnov(o) == (t)) +#define ttisnumber(o) checktype((o), LUA_TNUMBER) +#define ttisfloat(o) checktag((o), LUA_TNUMFLT) +#define ttisinteger(o) checktag((o), LUA_TNUMINT) #define ttisnil(o) checktag((o), LUA_TNIL) #define ttisboolean(o) checktag((o), LUA_TBOOLEAN) #define ttislightuserdata(o) checktag((o), LUA_TLIGHTUSERDATA) @@ -143,27 +154,27 @@ typedef struct lua_TValue TValue; #define ttisCclosure(o) checktag((o), ctb(LUA_TCCL)) #define ttisLclosure(o) checktag((o), ctb(LUA_TLCL)) #define ttislcf(o) checktag((o), LUA_TLCF) -#define ttisuserdata(o) checktag((o), ctb(LUA_TUSERDATA)) +#define ttisfulluserdata(o) checktag((o), ctb(LUA_TUSERDATA)) #define ttisthread(o) checktag((o), ctb(LUA_TTHREAD)) #define ttisdeadkey(o) checktag((o), LUA_TDEADKEY) -#define ttisequal(o1,o2) (rttype(o1) == rttype(o2)) /* Macros to access values */ -#define nvalue(o) check_exp(ttisnumber(o), num_(o)) +#define ivalue(o) check_exp(ttisinteger(o), val_(o).i) +#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n) +#define nvalue(o) check_exp(ttisnumber(o), \ + (ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o))) #define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) #define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) -#define rawtsvalue(o) check_exp(ttisstring(o), &val_(o).gc->ts) -#define tsvalue(o) (&rawtsvalue(o)->tsv) -#define rawuvalue(o) check_exp(ttisuserdata(o), &val_(o).gc->u) -#define uvalue(o) (&rawuvalue(o)->uv) -#define clvalue(o) check_exp(ttisclosure(o), &val_(o).gc->cl) -#define clLvalue(o) check_exp(ttisLclosure(o), &val_(o).gc->cl.l) -#define clCvalue(o) check_exp(ttisCclosure(o), &val_(o).gc->cl.c) +#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc)) +#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc)) +#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc)) +#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc)) +#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc)) #define fvalue(o) check_exp(ttislcf(o), val_(o).f) -#define hvalue(o) check_exp(ttistable(o), &val_(o).gc->h) +#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc)) #define bvalue(o) check_exp(ttisboolean(o), val_(o).b) -#define thvalue(o) check_exp(ttisthread(o), &val_(o).gc->th) +#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc)) /* a dead value may get the 'gc' field, but cannot access its contents */ #define deadvalue(o) check_exp(ttisdeadkey(o), cast(void *, val_(o).gc)) @@ -174,18 +185,27 @@ typedef struct lua_TValue TValue; /* Macros for internal tests */ -#define righttt(obj) (ttype(obj) == gcvalue(obj)->gch.tt) +#define righttt(obj) (ttype(obj) == gcvalue(obj)->tt) -#define checkliveness(g,obj) \ +#define checkliveness(L,obj) \ lua_longassert(!iscollectable(obj) || \ - (righttt(obj) && !isdead(g,gcvalue(obj)))) + (righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj))))) /* Macros to set values */ #define settt_(o,t) ((o)->tt_=(t)) -#define setnvalue(obj,x) \ - { TValue *io=(obj); num_(io)=(x); settt_(io, LUA_TNUMBER); } +#define setfltvalue(obj,x) \ + { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_TNUMFLT); } + +#define chgfltvalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); } + +#define setivalue(obj,x) \ + { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_TNUMINT); } + +#define chgivalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); } #define setnilvalue(obj) settt_(obj, LUA_TNIL) @@ -199,48 +219,46 @@ typedef struct lua_TValue TValue; { TValue *io=(obj); val_(io).b=(x); settt_(io, LUA_TBOOLEAN); } #define setgcovalue(L,obj,x) \ - { TValue *io=(obj); GCObject *i_g=(x); \ - val_(io).gc=i_g; settt_(io, ctb(gch(i_g)->tt)); } + { TValue *io = (obj); GCObject *i_g=(x); \ + val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); } #define setsvalue(L,obj,x) \ - { TValue *io=(obj); \ - TString *x_ = (x); \ - val_(io).gc=cast(GCObject *, x_); settt_(io, ctb(x_->tsv.tt)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); TString *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ + checkliveness(L,io); } #define setuvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TUSERDATA)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); Udata *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TUSERDATA)); \ + checkliveness(L,io); } #define setthvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TTHREAD)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); lua_State *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTHREAD)); \ + checkliveness(L,io); } #define setclLvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TLCL)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); LClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TLCL)); \ + checkliveness(L,io); } #define setclCvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TCCL)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); CClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TCCL)); \ + checkliveness(L,io); } #define sethvalue(L,obj,x) \ - { TValue *io=(obj); \ - val_(io).gc=cast(GCObject *, (x)); settt_(io, ctb(LUA_TTABLE)); \ - checkliveness(G(L),io); } + { TValue *io = (obj); Table *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTABLE)); \ + checkliveness(L,io); } #define setdeadvalue(obj) settt_(obj, LUA_TDEADKEY) #define setobj(L,obj1,obj2) \ - { const TValue *io2=(obj2); TValue *io1=(obj1); \ - io1->value_ = io2->value_; io1->tt_ = io2->tt_; \ - checkliveness(G(L),io1); } + { TValue *io1=(obj1); *io1 = *(obj2); \ + (void)L; checkliveness(L,io1); } /* @@ -256,188 +274,110 @@ typedef struct lua_TValue TValue; #define setptvalue2s setptvalue /* from table to same table */ #define setobjt2t setobj -/* to table */ -#define setobj2t setobj /* to new object */ #define setobj2n setobj #define setsvalue2n setsvalue +/* to table (define it as an expression to be used in macros) */ +#define setobj2t(L,o1,o2) ((void)L, *(o1)=*(o2), checkliveness(L,(o1))) + -/* check whether a number is valid (useful only for NaN trick) */ -#define luai_checknum(L,o,c) { /* empty */ } /* ** {====================================================== -** NaN Trick +** types and prototypes ** ======================================================= */ -#if defined(LUA_NANTRICK) - -/* -** numbers are represented in the 'd_' field. All other values have the -** value (NNMARK | tag) in 'tt__'. A number with such pattern would be -** a "signaled NaN", which is never generated by regular operations by -** the CPU (nor by 'strtod') -*/ - -/* allows for external implementation for part of the trick */ -#if !defined(NNMARK) /* { */ - - -#if !defined(LUA_IEEEENDIAN) -#error option 'LUA_NANTRICK' needs 'LUA_IEEEENDIAN' -#endif - - -#define NNMARK 0x7FF7A500 -#define NNMASK 0x7FFFFF00 - -#undef TValuefields -#undef NILCONSTANT - -#if (LUA_IEEEENDIAN == 0) /* { */ - -/* little endian */ -#define TValuefields \ - union { struct { Value v__; int tt__; } i; double d__; } u -#define NILCONSTANT {{{NULL}, tag2tt(LUA_TNIL)}} -/* field-access macros */ -#define v_(o) ((o)->u.i.v__) -#define d_(o) ((o)->u.d__) -#define tt_(o) ((o)->u.i.tt__) - -#else /* }{ */ - -/* big endian */ -#define TValuefields \ - union { struct { int tt__; Value v__; } i; double d__; } u -#define NILCONSTANT {{tag2tt(LUA_TNIL), {NULL}}} -/* field-access macros */ -#define v_(o) ((o)->u.i.v__) -#define d_(o) ((o)->u.d__) -#define tt_(o) ((o)->u.i.tt__) - -#endif /* } */ - -#endif /* } */ -/* correspondence with standard representation */ -#undef val_ -#define val_(o) v_(o) -#undef num_ -#define num_(o) d_(o) - - -#undef numfield -#define numfield /* no such field; numbers are the entire struct */ - -/* basic check to distinguish numbers from non-numbers */ -#undef ttisnumber -#define ttisnumber(o) ((tt_(o) & NNMASK) != NNMARK) - -#define tag2tt(t) (NNMARK | (t)) - -#undef rttype -#define rttype(o) (ttisnumber(o) ? LUA_TNUMBER : tt_(o) & 0xff) - -#undef settt_ -#define settt_(o,t) (tt_(o) = tag2tt(t)) +typedef TValue *StkId; /* index to stack elements */ -#undef setnvalue -#define setnvalue(obj,x) \ - { TValue *io_=(obj); num_(io_)=(x); lua_assert(ttisnumber(io_)); } -#undef setobj -#define setobj(L,obj1,obj2) \ - { const TValue *o2_=(obj2); TValue *o1_=(obj1); \ - o1_->u = o2_->u; \ - checkliveness(G(L),o1_); } /* -** these redefinitions are not mandatory, but these forms are more efficient +** Header for string value; string bytes follow the end of this structure +** (aligned according to 'UTString'; see next). */ - -#undef checktag -#undef checktype -#define checktag(o,t) (tt_(o) == tag2tt(t)) -#define checktype(o,t) (ctb(tt_(o) | VARBITS) == ctb(tag2tt(t) | VARBITS)) - -#undef ttisequal -#define ttisequal(o1,o2) \ - (ttisnumber(o1) ? ttisnumber(o2) : (tt_(o1) == tt_(o2))) - - -#undef luai_checknum -#define luai_checknum(L,o,c) { if (!ttisnumber(o)) c; } - -#endif -/* }====================================================== */ - +typedef struct TString { + CommonHeader; + lu_byte extra; /* reserved words for short strings; "has hash" for longs */ + lu_byte shrlen; /* length for short strings */ + unsigned int hash; + union { + size_t lnglen; /* length for long strings */ + struct TString *hnext; /* linked list for hash table */ + } u; +} TString; /* -** {====================================================== -** types and prototypes -** ======================================================= +** Ensures that address after this type is always fully aligned. */ +typedef union UTString { + L_Umaxalign dummy; /* ensures maximum alignment for strings */ + TString tsv; +} UTString; -union Value { - GCObject *gc; /* collectable objects */ - void *p; /* light userdata */ - int b; /* booleans */ - lua_CFunction f; /* light C functions */ - numfield /* numbers */ -}; - - -struct lua_TValue { - TValuefields; -}; +/* +** Get the actual string (array of bytes) from a 'TString'. +** (Access to 'extra' ensures that value is really a 'TString'.) +*/ +#define getstr(ts) \ + check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString)) -typedef TValue *StkId; /* index to stack elements */ +/* get the actual string (array of bytes) from a Lua value */ +#define svalue(o) getstr(tsvalue(o)) +/* get string length from 'TString *s' */ +#define tsslen(s) ((s)->tt == LUA_TSHRSTR ? (s)->shrlen : (s)->u.lnglen) +/* get string length from 'TValue *o' */ +#define vslen(o) tsslen(tsvalue(o)) /* -** Header for string value; string bytes follow the end of this structure +** Header for userdata; memory area follows the end of this structure +** (aligned according to 'UUdata'; see next). */ -typedef union TString { - L_Umaxalign dummy; /* ensures maximum alignment for strings */ - struct { - CommonHeader; - lu_byte extra; /* reserved words for short strings; "has hash" for longs */ - unsigned int hash; - size_t len; /* number of characters in string */ - } tsv; -} TString; - +typedef struct Udata { + CommonHeader; + lu_byte ttuv_; /* user value's tag */ + struct Table *metatable; + size_t len; /* number of bytes */ + union Value user_; /* user value */ +} Udata; -/* get the actual string (array of bytes) from a TString */ -#define getstr(ts) cast(const char *, (ts) + 1) -/* get the actual string (array of bytes) from a Lua value */ -#define svalue(o) getstr(rawtsvalue(o)) +/* +** Ensures that address after this type is always fully aligned. +*/ +typedef union UUdata { + L_Umaxalign dummy; /* ensures maximum alignment for 'local' udata */ + Udata uv; +} UUdata; /* -** Header for userdata; memory area follows the end of this structure +** Get the address of memory block inside 'Udata'. +** (Access to 'ttuv_' ensures that value is really a 'Udata'.) */ -typedef union Udata { - L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ - struct { - CommonHeader; - struct Table *metatable; - struct Table *env; - size_t len; /* number of bytes */ - } uv; -} Udata; +#define getudatamem(u) \ + check_exp(sizeof((u)->ttuv_), (cast(char*, (u)) + sizeof(UUdata))) + +#define setuservalue(L,u,o) \ + { const TValue *io=(o); Udata *iu = (u); \ + iu->user_ = io->value_; iu->ttuv_ = rttype(io); \ + checkliveness(L,io); } + +#define getuservalue(L,u,o) \ + { TValue *io=(o); const Udata *iu = (u); \ + io->value_ = iu->user_; settt_(io, iu->ttuv_); \ + checkliveness(L,io); } /* @@ -445,7 +385,7 @@ typedef union Udata { */ typedef struct Upvaldesc { TString *name; /* upvalue name (for debug information) */ - lu_byte instack; /* whether it is in stack */ + lu_byte instack; /* whether it is in stack (register) */ lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ } Upvaldesc; @@ -466,26 +406,26 @@ typedef struct LocVar { */ typedef struct Proto { CommonHeader; + lu_byte numparams; /* number of fixed parameters */ + lu_byte is_vararg; /* 2: declared vararg; 1: uses vararg */ + lu_byte maxstacksize; /* number of registers needed by this function */ + int sizeupvalues; /* size of 'upvalues' */ + int sizek; /* size of 'k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of 'p' */ + int sizelocvars; + int linedefined; /* debug information */ + int lastlinedefined; /* debug information */ TValue *k; /* constants used by the function */ - Instruction *code; + Instruction *code; /* opcodes */ struct Proto **p; /* functions defined inside the function */ int *lineinfo; /* map from opcodes to source lines (debug information) */ LocVar *locvars; /* information about local variables (debug information) */ Upvaldesc *upvalues; /* upvalue information */ - union Closure *cache; /* last created closure with this prototype */ + struct LClosure *cache; /* last-created closure with this prototype */ TString *source; /* used for debug information */ - int sizeupvalues; /* size of 'upvalues' */ - int sizek; /* size of `k' */ - int sizecode; - int sizelineinfo; - int sizep; /* size of `p' */ - int sizelocvars; - int linedefined; - int lastlinedefined; GCObject *gclist; - lu_byte numparams; /* number of fixed parameters */ - lu_byte is_vararg; - lu_byte maxstacksize; /* maximum stack used by this function */ } Proto; @@ -493,17 +433,7 @@ typedef struct Proto { /* ** Lua Upvalues */ -typedef struct UpVal { - CommonHeader; - TValue *v; /* points to stack or to its own value */ - union { - TValue value; /* the value (when closed) */ - struct { /* double linked list (when open) */ - struct UpVal *prev; - struct UpVal *next; - } l; - } u; -} UpVal; +typedef struct UpVal UpVal; /* @@ -545,12 +475,19 @@ typedef union Closure { typedef union TKey { struct { TValuefields; - struct Node *next; /* for chaining */ + int next; /* for chaining (offset for next node) */ } nk; TValue tvk; } TKey; +/* copy a value into a key without messing up field 'next' */ +#define setnodekey(L,key,obj) \ + { TKey *k_=(key); const TValue *io_=(obj); \ + k_->nk.value_ = io_->value_; k_->nk.tt_ = io_->tt_; \ + (void)L; checkliveness(L,io_); } + + typedef struct Node { TValue i_val; TKey i_key; @@ -560,19 +497,19 @@ typedef struct Node { typedef struct Table { CommonHeader; lu_byte flags; /* 1<

>1) /* `sBx' is signed */ +#define MAXARG_sBx (MAXARG_Bx>>1) /* 'sBx' is signed */ #else #define MAXARG_Bx MAX_INT #define MAXARG_sBx MAX_INT @@ -76,10 +76,10 @@ enum OpMode {iABC, iABx, iAsBx, iAx}; /* basic instruction format */ #define MAXARG_C ((1<> RK(C) */ OP_UNM,/* A B R(A) := -R(B) */ +OP_BNOT,/* A B R(A) := ~R(B) */ OP_NOT,/* A B R(A) := not R(B) */ OP_LEN,/* A B R(A) := length of R(B) */ OP_CONCAT,/* A B C R(A) := R(B).. ... ..R(C) */ -OP_JMP,/* A sBx pc+=sBx; if (A) close all upvalues >= R(A) + 1 */ +OP_JMP,/* A sBx pc+=sBx; if (A) close all upvalues >= R(A - 1) */ OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ @@ -231,16 +238,16 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ /*=========================================================================== Notes: - (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then `top' is + (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then 'top' is set to last_result+1, so next open instruction (OP_CALL, OP_RETURN, - OP_SETLIST) may use `top'. + OP_SETLIST) may use 'top'. (*) In OP_VARARG, if (B == 0) then use actual number of varargs and set top (like in OP_CALL with C == 0). - (*) In OP_RETURN, if (B == 0) then return up to `top'. + (*) In OP_RETURN, if (B == 0) then return up to 'top'. - (*) In OP_SETLIST, if (B == 0) then B = `top'; if (C == 0) then next + (*) In OP_SETLIST, if (B == 0) then B = 'top'; if (C == 0) then next 'instruction' is EXTRAARG(real C). (*) In OP_LOADKX, the next 'instruction' is always EXTRAARG. @@ -248,7 +255,7 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ (*) For comparisons, A specifies what condition the test should accept (true or false). - (*) All `skips' (pc++) assume that next instruction is a jump. + (*) All 'skips' (pc++) assume that next instruction is a jump. ===========================================================================*/ diff --git a/depends/lua/include/lparser.h b/depends/lua/include/lparser.h index 0346e3c41..02e9b03ae 100644 --- a/depends/lua/include/lparser.h +++ b/depends/lua/include/lparser.h @@ -1,5 +1,5 @@ /* -** $Id: lparser.h,v 1.70.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lparser.h,v 1.76 2015/12/30 18:16:13 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ @@ -13,24 +13,38 @@ /* -** Expression descriptor +** Expression and variable descriptor. +** Code generation for variables and expressions can be delayed to allow +** optimizations; An 'expdesc' structure describes a potentially-delayed +** variable/expression. It has a description of its "main" value plus a +** list of conditional jumps that can also produce its value (generated +** by short-circuit operators 'and'/'or'). */ +/* kinds of variables/expressions */ typedef enum { - VVOID, /* no value */ - VNIL, - VTRUE, - VFALSE, - VK, /* info = index of constant in `k' */ - VKNUM, /* nval = numerical value */ - VNONRELOC, /* info = result register */ - VLOCAL, /* info = local register */ - VUPVAL, /* info = index of upvalue in 'upvalues' */ - VINDEXED, /* t = table register/upvalue; idx = index R/K */ - VJMP, /* info = instruction pc */ - VRELOCABLE, /* info = instruction pc */ - VCALL, /* info = instruction pc */ - VVARARG /* info = instruction pc */ + VVOID, /* when 'expdesc' describes the last expression a list, + this kind means an empty list (so, no expression) */ + VNIL, /* constant nil */ + VTRUE, /* constant true */ + VFALSE, /* constant false */ + VK, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT, /* floating constant; nval = numerical float value */ + VKINT, /* integer constant; nval = numerical integer value */ + VNONRELOC, /* expression has its value in a fixed register; + info = result register */ + VLOCAL, /* local variable; info = local register */ + VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VINDEXED, /* indexed variable; + ind.vt = whether 't' is register or upvalue; + ind.t = table register or upvalue; + ind.idx = key's R/K index */ + VJMP, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOCABLE, /* expression can put result in any register; + info = instruction pc */ + VCALL, /* expression is a function call; info = instruction pc */ + VVARARG /* vararg expression; info = instruction pc */ } expkind; @@ -40,16 +54,17 @@ typedef enum { typedef struct expdesc { expkind k; union { + lua_Integer ival; /* for VKINT */ + lua_Number nval; /* for VKFLT */ + int info; /* for generic use */ struct { /* for indexed variables (VINDEXED) */ short idx; /* index (R/K) */ lu_byte t; /* table (register or upvalue) */ lu_byte vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ } ind; - int info; /* for generic use */ - lua_Number nval; /* for VKNUM */ } u; - int t; /* patch list of `exit when true' */ - int f; /* patch list of `exit when false' */ + int t; /* patch list of 'exit when true' */ + int f; /* patch list of 'exit when false' */ } expdesc; @@ -95,15 +110,14 @@ struct BlockCnt; /* defined in lparser.c */ /* state needed to generate code for a given function */ typedef struct FuncState { Proto *f; /* current function header */ - Table *h; /* table to find (and reuse) elements in `k' */ struct FuncState *prev; /* enclosing function */ struct LexState *ls; /* lexical state */ struct BlockCnt *bl; /* chain of current blocks */ - int pc; /* next position to code (equivalent to `ncode') */ + int pc; /* next position to code (equivalent to 'ncode') */ int lasttarget; /* 'label' of last 'jump label' */ - int jpc; /* list of pending jumps to `pc' */ - int nk; /* number of elements in `k' */ - int np; /* number of elements in `p' */ + int jpc; /* list of pending jumps to 'pc' */ + int nk; /* number of elements in 'k' */ + int np; /* number of elements in 'p' */ int firstlocal; /* index of first local var (in Dyndata array) */ short nlocvars; /* number of elements in 'f->locvars' */ lu_byte nactvar; /* number of active local variables */ @@ -112,8 +126,8 @@ typedef struct FuncState { } FuncState; -LUAI_FUNC Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, - Dyndata *dyd, const char *name, int firstchar); +LUAI_FUNC LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar); #endif diff --git a/depends/lua/include/lprefix.h b/depends/lua/include/lprefix.h new file mode 100644 index 000000000..02daa837f --- /dev/null +++ b/depends/lua/include/lprefix.h @@ -0,0 +1,45 @@ +/* +** $Id: lprefix.h,v 1.2 2014/12/29 16:54:13 roberto Exp $ +** Definitions for Lua code that must come before any other header file +** See Copyright Notice in lua.h +*/ + +#ifndef lprefix_h +#define lprefix_h + + +/* +** Allows POSIX/XSI stuff +*/ +#if !defined(LUA_USE_C89) /* { */ + +#if !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 600 +#elif _XOPEN_SOURCE == 0 +#undef _XOPEN_SOURCE /* use -D_XOPEN_SOURCE=0 to undefine it */ +#endif + +/* +** Allows manipulation of large files in gcc and some other compilers +*/ +#if !defined(LUA_32BITS) && !defined(_FILE_OFFSET_BITS) +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#endif + +#endif /* } */ + + +/* +** Windows stuff +*/ +#if defined(_WIN32) /* { */ + +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS /* avoid warnings about ISO C functions */ +#endif + +#endif /* } */ + +#endif + diff --git a/depends/lua/include/lstate.h b/depends/lua/include/lstate.h index daffd9aac..b3033bee5 100644 --- a/depends/lua/include/lstate.h +++ b/depends/lua/include/lstate.h @@ -1,5 +1,5 @@ /* -** $Id: lstate.h,v 2.82.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstate.h,v 2.130 2015/12/16 16:39:38 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ @@ -16,25 +16,16 @@ /* -** Some notes about garbage-collected objects: All objects in Lua must -** be kept somehow accessible until being freed. +** Some notes about garbage-collected objects: All objects in Lua must +** be kept somehow accessible until being freed, so all objects always +** belong to one (and only one) of these lists, using field 'next' of +** the 'CommonHeader' for the link: ** -** Lua keeps most objects linked in list g->allgc. The link uses field -** 'next' of the CommonHeader. -** -** Strings are kept in several lists headed by the array g->strt.hash. -** -** Open upvalues are not subject to independent garbage collection. They -** are collected together with their respective threads. Lua keeps a -** double-linked list with all open upvalues (g->uvhead) so that it can -** mark objects referred by them. (They are always gray, so they must -** be remarked in the atomic step. Usually their contents would be marked -** when traversing the respective threads, but the thread may already be -** dead, while the upvalue is still accessible through closures.) -** -** Objects with finalizers are kept in the list g->finobj. -** -** The list g->tobefnz links all objects being finalized. +** 'allgc': all objects not marked for finalization; +** 'finobj': all objects marked for finalization; +** 'tobefnz': all objects ready to be finalized; +** 'fixedgc': all objects that are not to be collected (currently +** only small strings, such as reserved words). */ @@ -42,6 +33,15 @@ struct lua_longjmp; /* defined in ldo.c */ +/* +** Atomic type (relative to signals) to better ensure that 'lua_sethook' +** is thread safe +*/ +#if !defined(l_signalT) +#include +#define l_signalT sig_atomic_t +#endif + /* extra stack space to handle TM calls and some other extras */ #define EXTRA_STACK 5 @@ -53,66 +53,72 @@ struct lua_longjmp; /* defined in ldo.c */ /* kinds of Garbage Collection */ #define KGC_NORMAL 0 #define KGC_EMERGENCY 1 /* gc was forced by an allocation failure */ -#define KGC_GEN 2 /* generational collection */ typedef struct stringtable { - GCObject **hash; - lu_int32 nuse; /* number of elements */ + TString **hash; + int nuse; /* number of elements */ int size; } stringtable; /* -** information about a call +** Information about a call. +** When a thread yields, 'func' is adjusted to pretend that the +** top function has only the yielded values in its stack; in that +** case, the actual 'func' value is saved in field 'extra'. +** When a function calls another with a continuation, 'extra' keeps +** the function index so that, in case of errors, the continuation +** function can be called with the correct top. */ typedef struct CallInfo { StkId func; /* function index in the stack */ StkId top; /* top for this function */ struct CallInfo *previous, *next; /* dynamic call link */ - short nresults; /* expected number of results from this function */ - lu_byte callstatus; - ptrdiff_t extra; union { struct { /* only for Lua functions */ StkId base; /* base for this function */ const Instruction *savedpc; } l; struct { /* only for C functions */ - int ctx; /* context info. in case of yields */ - lua_CFunction k; /* continuation in case of yields */ + lua_KFunction k; /* continuation in case of yields */ ptrdiff_t old_errfunc; - lu_byte old_allowhook; - lu_byte status; + lua_KContext ctx; /* context info. in case of yields */ } c; } u; + ptrdiff_t extra; + short nresults; /* expected number of results from this function */ + lu_byte callstatus; } CallInfo; /* ** Bits in CallInfo status */ -#define CIST_LUA (1<<0) /* call is running a Lua function */ -#define CIST_HOOKED (1<<1) /* call is running a debug hook */ -#define CIST_REENTRY (1<<2) /* call is running on same invocation of - luaV_execute of previous call */ -#define CIST_YIELDED (1<<3) /* call reentered after suspension */ +#define CIST_OAH (1<<0) /* original value of 'allowhook' */ +#define CIST_LUA (1<<1) /* call is running a Lua function */ +#define CIST_HOOKED (1<<2) /* call is running a debug hook */ +#define CIST_FRESH (1<<3) /* call is running on a fresh invocation + of luaV_execute */ #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ -#define CIST_STAT (1<<5) /* call has an error status (pcall) */ -#define CIST_TAIL (1<<6) /* call was tail called */ -#define CIST_HOOKYIELD (1<<7) /* last hook called yielded */ - +#define CIST_TAIL (1<<5) /* call was tail called */ +#define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ +#define CIST_LEQ (1<<7) /* using __lt for __le */ #define isLua(ci) ((ci)->callstatus & CIST_LUA) +/* assume that CIST_OAH has offset 0 and that 'v' is strictly 0/1 */ +#define setoah(st,v) ((st) = ((st) & ~CIST_OAH) | (v)) +#define getoah(st) ((st) & CIST_OAH) + /* -** `global state', shared by all threads of this state +** 'global state', shared by all threads of this state */ typedef struct global_State { lua_Alloc frealloc; /* function to reallocate memory */ - void *ud; /* auxiliary data to `frealloc' */ - lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + void *ud; /* auxiliary data to 'frealloc' */ + l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ lu_mem GCmemtrav; /* memory traversed by the GC */ lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ @@ -123,36 +129,36 @@ typedef struct global_State { lu_byte gcstate; /* state of garbage collector */ lu_byte gckind; /* kind of GC running */ lu_byte gcrunning; /* true if GC is running */ - int sweepstrgc; /* position of sweep in `strt' */ GCObject *allgc; /* list of all collectable objects */ + GCObject **sweepgc; /* current position of sweep in list */ GCObject *finobj; /* list of collectable objects with finalizers */ - GCObject **sweepgc; /* current position of sweep in list 'allgc' */ - GCObject **sweepfin; /* current position of sweep in list 'finobj' */ GCObject *gray; /* list of gray objects */ GCObject *grayagain; /* list of objects to be traversed atomically */ GCObject *weak; /* list of tables with weak values */ GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ GCObject *allweak; /* list of all-weak tables */ GCObject *tobefnz; /* list of userdata to be GC */ - UpVal uvhead; /* head of double-linked list of all open upvalues */ - Mbuffer buff; /* temporary buffer for string concatenation */ + GCObject *fixedgc; /* list of objects not to be collected */ + struct lua_State *twups; /* list of threads with open upvalues */ + unsigned int gcfinnum; /* number of finalizers to call in each GC step */ int gcpause; /* size of pause between successive GCs */ - int gcmajorinc; /* pause between major collections (only in gen. mode) */ - int gcstepmul; /* GC `granularity' */ + int gcstepmul; /* GC 'granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ struct lua_State *mainthread; const lua_Number *version; /* pointer to version number */ TString *memerrmsg; /* memory-error message */ TString *tmname[TM_N]; /* array with tag-method names */ struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ + TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ } global_State; /* -** `per thread' state +** 'per thread' state */ struct lua_State { CommonHeader; + unsigned short nci; /* number of items in 'ci' list */ lu_byte status; StkId top; /* first free slot in the stack */ global_State *l_G; @@ -160,19 +166,20 @@ struct lua_State { const Instruction *oldpc; /* last pc traced */ StkId stack_last; /* last free slot in the stack */ StkId stack; /* stack base */ + UpVal *openupval; /* list of open upvalues in this stack */ + GCObject *gclist; + struct lua_State *twups; /* list of threads with open upvalues */ + struct lua_longjmp *errorJmp; /* current error recover point */ + CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ + volatile lua_Hook hook; + ptrdiff_t errfunc; /* current error handling function (stack index) */ int stacksize; + int basehookcount; + int hookcount; unsigned short nny; /* number of non-yieldable calls in stack */ unsigned short nCcalls; /* number of nested C calls */ - lu_byte hookmask; + l_signalT hookmask; lu_byte allowhook; - int basehookcount; - int hookcount; - lua_Hook hook; - GCObject *openupval; /* list of open upvalues in this stack */ - GCObject *gclist; - struct lua_longjmp *errorJmp; /* current error recover point */ - ptrdiff_t errfunc; /* current error handling function (stack index) */ - CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ }; @@ -180,48 +187,47 @@ struct lua_State { /* -** Union of all collectable objects +** Union of all collectable objects (only for conversions) */ -union GCObject { - GCheader gch; /* common header */ - union TString ts; - union Udata u; +union GCUnion { + GCObject gc; /* common header */ + struct TString ts; + struct Udata u; union Closure cl; struct Table h; struct Proto p; - struct UpVal uv; struct lua_State th; /* thread */ }; -#define gch(o) (&(o)->gch) +#define cast_u(o) cast(union GCUnion *, (o)) /* macros to convert a GCObject into a specific value */ -#define rawgco2ts(o) \ - check_exp(novariant((o)->gch.tt) == LUA_TSTRING, &((o)->ts)) -#define gco2ts(o) (&rawgco2ts(o)->tsv) -#define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) -#define gco2u(o) (&rawgco2u(o)->uv) -#define gco2lcl(o) check_exp((o)->gch.tt == LUA_TLCL, &((o)->cl.l)) -#define gco2ccl(o) check_exp((o)->gch.tt == LUA_TCCL, &((o)->cl.c)) +#define gco2ts(o) \ + check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts)) +#define gco2u(o) check_exp((o)->tt == LUA_TUSERDATA, &((cast_u(o))->u)) +#define gco2lcl(o) check_exp((o)->tt == LUA_TLCL, &((cast_u(o))->cl.l)) +#define gco2ccl(o) check_exp((o)->tt == LUA_TCCL, &((cast_u(o))->cl.c)) #define gco2cl(o) \ - check_exp(novariant((o)->gch.tt) == LUA_TFUNCTION, &((o)->cl)) -#define gco2t(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) -#define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) -#define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) -#define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) + check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) +#define gco2t(o) check_exp((o)->tt == LUA_TTABLE, &((cast_u(o))->h)) +#define gco2p(o) check_exp((o)->tt == LUA_TPROTO, &((cast_u(o))->p)) +#define gco2th(o) check_exp((o)->tt == LUA_TTHREAD, &((cast_u(o))->th)) + -/* macro to convert any Lua object into a GCObject */ -#define obj2gco(v) (cast(GCObject *, (v))) +/* macro to convert a Lua object into a GCObject */ +#define obj2gco(v) \ + check_exp(novariant((v)->tt) < LUA_TDEADKEY, (&(cast_u(v)->gc))) /* actual number of total bytes allocated */ -#define gettotalbytes(g) ((g)->totalbytes + (g)->GCdebt) +#define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); LUAI_FUNC void luaE_freeCI (lua_State *L); +LUAI_FUNC void luaE_shrinkCI (lua_State *L); #endif diff --git a/depends/lua/include/lstring.h b/depends/lua/include/lstring.h index 260e7f169..27efd2077 100644 --- a/depends/lua/include/lstring.h +++ b/depends/lua/include/lstring.h @@ -1,5 +1,5 @@ /* -** $Id: lstring.h,v 1.49.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstring.h,v 1.61 2015/11/03 15:36:01 roberto Exp $ ** String table (keep all strings handled by Lua) ** See Copyright Notice in lua.h */ @@ -12,35 +12,38 @@ #include "lstate.h" -#define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) +#define sizelstring(l) (sizeof(union UTString) + ((l) + 1) * sizeof(char)) -#define sizeudata(u) (sizeof(union Udata)+(u)->len) +#define sizeludata(l) (sizeof(union UUdata) + (l)) +#define sizeudata(u) sizeludata((u)->len) #define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ (sizeof(s)/sizeof(char))-1)) -#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) - /* ** test whether a string is a reserved word */ -#define isreserved(s) ((s)->tsv.tt == LUA_TSHRSTR && (s)->tsv.extra > 0) +#define isreserved(s) ((s)->tt == LUA_TSHRSTR && (s)->extra > 0) /* ** equality for short strings, which are always internalized */ -#define eqshrstr(a,b) check_exp((a)->tsv.tt == LUA_TSHRSTR, (a) == (b)) +#define eqshrstr(a,b) check_exp((a)->tt == LUA_TSHRSTR, (a) == (b)) LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC unsigned int luaS_hashlongstr (TString *ts); LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); -LUAI_FUNC int luaS_eqstr (TString *a, TString *b); LUAI_FUNC void luaS_resize (lua_State *L, int newsize); -LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); +LUAI_FUNC void luaS_clearcache (global_State *g); +LUAI_FUNC void luaS_init (lua_State *L); +LUAI_FUNC void luaS_remove (lua_State *L, TString *ts); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s); LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); +LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); #endif diff --git a/depends/lua/include/ltable.h b/depends/lua/include/ltable.h index d69449b2b..213cc1398 100644 --- a/depends/lua/include/ltable.h +++ b/depends/lua/include/ltable.h @@ -1,5 +1,5 @@ /* -** $Id: ltable.h,v 2.16.1.2 2013/08/30 15:49:41 roberto Exp $ +** $Id: ltable.h,v 2.21 2015/11/03 15:47:30 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ @@ -11,26 +11,39 @@ #define gnode(t,i) (&(t)->node[i]) -#define gkey(n) (&(n)->i_key.tvk) #define gval(n) (&(n)->i_val) #define gnext(n) ((n)->i_key.nk.next) + +/* 'const' to avoid wrong writings that can mess up field 'next' */ +#define gkey(n) cast(const TValue*, (&(n)->i_key.tvk)) + +/* +** writable version of 'gkey'; allows updates to individual fields, +** but not to the whole (which has incompatible type) +*/ +#define wgkey(n) (&(n)->i_key.nk) + #define invalidateTMcache(t) ((t)->flags = 0) + /* returns the key, given the value of a table entry */ #define keyfromval(v) \ (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, i_val)))) -LUAI_FUNC const TValue *luaH_getint (Table *t, int key); -LUAI_FUNC void luaH_setint (lua_State *L, Table *t, int key, TValue *value); +LUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key); +LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, + TValue *value); +LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); LUAI_FUNC TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key); LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); LUAI_FUNC Table *luaH_new (lua_State *L); -LUAI_FUNC void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize); -LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize); +LUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize, + unsigned int nhsize); +LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); LUAI_FUNC int luaH_getn (Table *t); diff --git a/depends/lua/include/ltm.h b/depends/lua/include/ltm.h index 7f89c841f..63db7269b 100644 --- a/depends/lua/include/ltm.h +++ b/depends/lua/include/ltm.h @@ -1,5 +1,5 @@ /* -** $Id: ltm.h,v 2.11.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltm.h,v 2.22 2016/02/26 19:20:15 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ @@ -13,7 +13,7 @@ /* * WARNING: if you change the order of this enumeration, -* grep "ORDER TM" +* grep "ORDER TM" and "ORDER OP" */ typedef enum { TM_INDEX, @@ -21,14 +21,21 @@ typedef enum { TM_GC, TM_MODE, TM_LEN, - TM_EQ, /* last tag method with `fast' access */ + TM_EQ, /* last tag method with fast access */ TM_ADD, TM_SUB, TM_MUL, - TM_DIV, TM_MOD, TM_POW, + TM_DIV, + TM_IDIV, + TM_BAND, + TM_BOR, + TM_BXOR, + TM_SHL, + TM_SHR, TM_UNM, + TM_BNOT, TM_LT, TM_LE, TM_CONCAT, @@ -44,14 +51,26 @@ typedef enum { #define fasttm(l,et,e) gfasttm(G(l), et, e) #define ttypename(x) luaT_typenames_[(x) + 1] -#define objtypename(x) ttypename(ttypenv(x)) LUAI_DDEC const char *const luaT_typenames_[LUA_TOTALTAGS]; +LUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o); + LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event); LUAI_FUNC void luaT_init (lua_State *L); +LUAI_FUNC void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, TValue *p3, int hasres); +LUAI_FUNC int luaT_callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event); +LUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event); +LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1, + const TValue *p2, TMS event); + + + #endif diff --git a/depends/lua/include/lua.h b/depends/lua/include/lua.h index 149a2c37b..f78899fc5 100644 --- a/depends/lua/include/lua.h +++ b/depends/lua/include/lua.h @@ -1,5 +1,5 @@ /* -** $Id: lua.h,v 1.285.1.2 2013/11/11 12:09:16 roberto Exp $ +** $Id: lua.h,v 1.331 2016/05/30 15:53:28 roberto Exp $ ** Lua - A Scripting Language ** Lua.org, PUC-Rio, Brazil (http://www.lua.org) ** See Copyright Notice at the end of this file @@ -17,27 +17,29 @@ #define LUA_VERSION_MAJOR "5" -#define LUA_VERSION_MINOR "2" -#define LUA_VERSION_NUM 502 +#define LUA_VERSION_MINOR "3" +#define LUA_VERSION_NUM 503 #define LUA_VERSION_RELEASE "3" #define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2013 Lua.org, PUC-Rio" +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2016 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" /* mark for precompiled code ('Lua') */ -#define LUA_SIGNATURE "\033Lua" +#define LUA_SIGNATURE "\x1bLua" /* option for multiple returns in 'lua_pcall' and 'lua_call' */ #define LUA_MULTRET (-1) /* -** pseudo-indices +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) */ -#define LUA_REGISTRYINDEX LUAI_FIRSTPSEUDOIDX +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) #define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) @@ -53,22 +55,6 @@ typedef struct lua_State lua_State; -typedef int (*lua_CFunction) (lua_State *L); - - -/* -** functions that read/write blocks when loading/dumping Lua chunks -*/ -typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); - -typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); - - -/* -** prototype for memory-allocation functions -*/ -typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); - /* ** basic types @@ -109,6 +95,34 @@ typedef LUA_INTEGER lua_Integer; /* unsigned integer type */ typedef LUA_UNSIGNED lua_Unsigned; +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + /* @@ -145,11 +159,9 @@ LUA_API int (lua_absindex) (lua_State *L, int idx); LUA_API int (lua_gettop) (lua_State *L); LUA_API void (lua_settop) (lua_State *L, int idx); LUA_API void (lua_pushvalue) (lua_State *L, int idx); -LUA_API void (lua_remove) (lua_State *L, int idx); -LUA_API void (lua_insert) (lua_State *L, int idx); -LUA_API void (lua_replace) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); -LUA_API int (lua_checkstack) (lua_State *L, int sz); +LUA_API int (lua_checkstack) (lua_State *L, int n); LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); @@ -161,13 +173,13 @@ LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); LUA_API int (lua_isnumber) (lua_State *L, int idx); LUA_API int (lua_isstring) (lua_State *L, int idx); LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); LUA_API int (lua_isuserdata) (lua_State *L, int idx); LUA_API int (lua_type) (lua_State *L, int idx); LUA_API const char *(lua_typename) (lua_State *L, int tp); LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); -LUA_API lua_Unsigned (lua_tounsignedx) (lua_State *L, int idx, int *isnum); LUA_API int (lua_toboolean) (lua_State *L, int idx); LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); LUA_API size_t (lua_rawlen) (lua_State *L, int idx); @@ -181,13 +193,20 @@ LUA_API const void *(lua_topointer) (lua_State *L, int idx); ** Comparison and arithmetic functions */ -#define LUA_OPADD 0 /* ORDER TM */ +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ #define LUA_OPSUB 1 #define LUA_OPMUL 2 -#define LUA_OPDIV 3 -#define LUA_OPMOD 4 -#define LUA_OPPOW 5 -#define LUA_OPUNM 6 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 LUA_API void (lua_arith) (lua_State *L, int op); @@ -205,8 +224,7 @@ LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); LUA_API void (lua_pushnil) (lua_State *L); LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); -LUA_API void (lua_pushunsigned) (lua_State *L, lua_Unsigned n); -LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t l); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp); @@ -220,26 +238,29 @@ LUA_API int (lua_pushthread) (lua_State *L); /* ** get functions (Lua -> stack) */ -LUA_API void (lua_getglobal) (lua_State *L, const char *var); -LUA_API void (lua_gettable) (lua_State *L, int idx); -LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); -LUA_API void (lua_rawget) (lua_State *L, int idx); -LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); -LUA_API void (lua_rawgetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); -LUA_API void (lua_getuservalue) (lua_State *L, int idx); +LUA_API int (lua_getuservalue) (lua_State *L, int idx); /* ** set functions (stack -> Lua) */ -LUA_API void (lua_setglobal) (lua_State *L, const char *var); +LUA_API void (lua_setglobal) (lua_State *L, const char *name); LUA_API void (lua_settable) (lua_State *L, int idx); LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); LUA_API void (lua_rawset) (lua_State *L, int idx); -LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); LUA_API int (lua_setmetatable) (lua_State *L, int objindex); LUA_API void (lua_setuservalue) (lua_State *L, int idx); @@ -248,31 +269,31 @@ LUA_API void (lua_setuservalue) (lua_State *L, int idx); /* ** 'load' and 'call' functions (load and run Lua code) */ -LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, int ctx, - lua_CFunction k); +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); #define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) -LUA_API int (lua_getctx) (lua_State *L, int *ctx); - LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, - int ctx, lua_CFunction k); + lua_KContext ctx, lua_KFunction k); #define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, - const char *chunkname, - const char *mode); + const char *chunkname, const char *mode); -LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); /* ** coroutine functions */ -LUA_API int (lua_yieldk) (lua_State *L, int nresults, int ctx, - lua_CFunction k); +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + #define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) -LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); -LUA_API int (lua_status) (lua_State *L); + /* ** garbage-collection function and options @@ -286,10 +307,7 @@ LUA_API int (lua_status) (lua_State *L); #define LUA_GCSTEP 5 #define LUA_GCSETPAUSE 6 #define LUA_GCSETSTEPMUL 7 -#define LUA_GCSETMAJORINC 8 #define LUA_GCISRUNNING 9 -#define LUA_GCGEN 10 -#define LUA_GCINC 11 LUA_API int (lua_gc) (lua_State *L, int what, int data); @@ -305,20 +323,23 @@ LUA_API int (lua_next) (lua_State *L, int idx); LUA_API void (lua_concat) (lua_State *L, int n); LUA_API void (lua_len) (lua_State *L, int idx); +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); /* -** =============================================================== +** {============================================================== ** some useful macros ** =============================================================== */ -#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL) -#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL) -#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL) +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) #define lua_pop(L,n) lua_settop(L, -(n)-1) @@ -337,15 +358,36 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); #define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) -#define lua_pushliteral(L, s) \ - lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) #define lua_pushglobaltable(L) \ - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL) +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros for unsigned conversions +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif +/* }============================================================== */ /* ** {====================================================================== @@ -390,7 +432,7 @@ LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, int fidx2, int n2); -LUA_API int (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); LUA_API lua_Hook (lua_gethook) (lua_State *L); LUA_API int (lua_gethookmask) (lua_State *L); LUA_API int (lua_gethookcount) (lua_State *L); @@ -418,7 +460,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2013 Lua.org, PUC-Rio. +* Copyright (C) 1994-2016 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index af09ffb94..051d2a944 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -1,85 +1,179 @@ /* -** $Id: luaconf.h,v 1.176.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: luaconf.h,v 1.255 2016/05/01 20:06:09 roberto Exp $ ** Configuration file for Lua ** See Copyright Notice in lua.h */ -#ifndef lconfig_h -#define lconfig_h +#ifndef luaconf_h +#define luaconf_h #include #include +#define LUA_COMPAT_APIINTCASTS +#define LUA_COMPAT_IPAIRS + +// Patch for old glibc versions +#if !defined(LLONG_MAX) && defined(__LONG_LONG_MAX__) +#define LLONG_MAX __LONG_LONG_MAX__ +#endif + +#if !defined(LLONG_MIN) && defined(__LONG_LONG_MAX__) +#define LLONG_MIN (-__LONG_LONG_MAX__ - 1LL) +#endif /* -** ================================================================== +** =================================================================== ** Search for "@@" to find all configurable definitions. ** =================================================================== */ /* -@@ LUA_ANSI controls the use of non-ansi features. -** CHANGE it (define it) if you want Lua to avoid the use of any -** non-ansi feature or library. +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance compiling it with 32-bit numbers or +** restricting it to C89. +** ===================================================================== */ -#if !defined(LUA_ANSI) && defined(__STRICT_ANSI__) -#define LUA_ANSI -#endif + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You +** can also define LUA_32BITS in the make file, but changing here you +** ensure that all software connected to Lua will be compiled with the +** same configuration. +*/ +/* #define LUA_32BITS */ -#if !defined(LUA_ANSI) && defined(_WIN32) && !defined(_WIN32_WCE) -#define LUA_WIN /* enable goodies for regular Windows platforms */ -#endif +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ -#if defined(LUA_WIN) -#define LUA_DL_DLL -#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ #endif +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + #if defined(LUA_USE_LINUX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* needs an extra library: -ldl */ #define LUA_USE_READLINE /* needs some extra libraries */ -#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */ -#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ -#define LUA_USE_LONGLONG /* assume support for long long */ #endif + #if defined(LUA_USE_MACOSX) #define LUA_USE_POSIX -#define LUA_USE_DLOPEN /* does not need -ldl */ +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ #define LUA_USE_READLINE /* needs an extra library: -lreadline */ -#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */ -#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */ -#define LUA_USE_LONGLONG /* assume support for long long */ #endif +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS +#endif + + + +/* +@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'. +*/ +/* avoid undefined shifts */ +#if ((INT_MAX >> 15) >> 15) >= 1 +#define LUAI_BITSINT 32 +#else +/* 'int' always must have at least 16 bits */ +#define LUAI_BITSINT 16 +#endif + + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options (if supported +** by your C compiler). The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +#if defined(LUA_32BITS) /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_BITSINT >= 32 /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT +#elif defined(LUA_C89_NUMBERS) /* }{ */ /* -@@ LUA_USE_POSIX includes all functionality listed as X/Open System -@* Interfaces Extension (XSI). -** CHANGE it (define it) if your system is XSI compatible. +** largest types available for C89 ('long' and 'double') */ -#if defined(LUA_USE_POSIX) -#define LUA_USE_MKSTEMP -#define LUA_USE_ISATTY -#define LUA_USE_POPEN -#define LUA_USE_ULONGJMP -#define LUA_USE_GMTIME_R +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#endif /* } */ + + +/* +** default configuration for 64-bit Lua ('long long' and 'double') +*/ +#if !defined(LUA_INT_TYPE) +#define LUA_INT_TYPE LUA_INT_LONGLONG +#endif + +#if !defined(LUA_FLOAT_TYPE) +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE #endif +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ /* @@ LUA_PATH_DEFAULT is the default path that Lua uses to look for -@* Lua libraries. +** Lua libraries. @@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for -@* C libraries. +** C libraries. ** CHANGE them if your machine has a non-conventional directory ** hierarchy or if you want to install your libraries in ** non-conventional directories. @@ -106,8 +200,8 @@ LUA_CDIR"?.so;" "./?.so" #endif /* } */ -#define LUA_PATH "DFHACK_LUA_PATH" -#define LUA_CPATH "DFHACK_LUA_CPATH" +#define LUA_PATH_VAR "DFHACK_LUA_PATH" +#define LUA_CPATH_VAR "DFHACK_LUA_CPATH" /* @@ LUA_DIRSEP is the directory separator (for submodules). @@ -120,14 +214,14 @@ #define LUA_DIRSEP "/" #endif +/* }================================================================== */ + /* -@@ LUA_ENV is the name of the variable that holds the current -@@ environment, used to access global names. -** CHANGE it if you do not like this name. +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== */ -#define LUA_ENV "_ENV" - /* @@ LUA_API is a mark for all core API functions. @@ -164,10 +258,10 @@ /* @@ LUAI_FUNC is a mark for all extern functions that are not to be -@* exported to outside modules. +** exported to outside modules. @@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables -@* that are not to be exported to outside modules (LUAI_DDEF for -@* definitions and LUAI_DDEC for declarations). +** that are not to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). ** CHANGE them if you need to mark them in some special way. Elf/gcc ** (versions 3.2 and later) mark them as "hidden" to optimize access ** when Lua is compiled as a shared library. Not all elf targets support @@ -179,74 +273,61 @@ #if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ defined(__ELF__) /* { */ #define LUAI_FUNC __attribute__((visibility("hidden"))) extern -#define LUAI_DDEC LUAI_FUNC -#define LUAI_DDEF /* empty */ - #else /* }{ */ #define LUAI_FUNC extern -#define LUAI_DDEC extern -#define LUAI_DDEF /* empty */ #endif /* } */ +#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ /* -@@ LUA_QL describes how error messages quote program elements. -** CHANGE it if you want a different appearance. +** {================================================================== +** Compatibility with previous versions +** =================================================================== */ -#define LUA_QL(x) "'" x "'" -#define LUA_QS LUA_QL("%s") - /* -@@ LUA_IDSIZE gives the maximum size for the description of the source -@* of a function in debug information. -** CHANGE it if you want a different size. +@@ LUA_COMPAT_5_2 controls other macros for compatibility with Lua 5.2. +@@ LUA_COMPAT_5_1 controls other macros for compatibility with Lua 5.1. +** You can define it to get all options, or change specific options +** to fit your specific needs. */ -#define LUA_IDSIZE 60 - +#if defined(LUA_COMPAT_5_2) /* { */ /* -@@ luai_writestring/luai_writeline define how 'print' prints its results. -** They are only used in libraries and the stand-alone program. (The #if -** avoids including 'stdio.h' everywhere.) +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. */ -#if defined(LUA_LIB) || defined(lua_c) -#include -#define luai_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) -#define luai_writeline() (luai_writestring("\n", 1), fflush(stdout)) -#endif +#define LUA_COMPAT_MATHLIB /* -@@ luai_writestringerror defines how to print error messages. -** (A format string with one argument is enough for Lua...) +@@ LUA_COMPAT_BITLIB controls the presence of library 'bit32'. */ -#define luai_writestringerror(s,p) \ - (fprintf(stderr, (s), (p)), fflush(stderr)) +#define LUA_COMPAT_BITLIB +/* +@@ LUA_COMPAT_IPAIRS controls the effectiveness of the __ipairs metamethod. +*/ +#define LUA_COMPAT_IPAIRS /* -@@ LUAI_MAXSHORTLEN is the maximum length for short strings, that is, -** strings that are internalized. (Cannot be smaller than reserved words -** or tags for metamethods, as these strings must be internalized; -** #("function") = 8, #("__newindex") = 10.) +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) */ -#define LUAI_MAXSHORTLEN 40 +#define LUA_COMPAT_APIINTCASTS +#endif /* } */ -/* -** {================================================================== -** Compatibility with previous versions -** =================================================================== -*/ +#if defined(LUA_COMPAT_5_1) /* { */ -/* -@@ LUA_COMPAT_ALL controls all compatibility options. -** You can define it to get all options, or change specific options -** to fit your specific needs. -*/ -#if defined(LUA_COMPAT_ALL) /* { */ +/* Incompatibilities from 5.2 -> 5.3 */ +#define LUA_COMPAT_MATHLIB +#define LUA_COMPAT_APIINTCASTS /* @@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. @@ -307,237 +388,375 @@ #endif /* } */ + +/* +@@ LUA_COMPAT_FLOATSTRING makes Lua format integral floats without a +@@ a float mark ('.0'). +** This macro is not on by default even in compatibility mode, +** because this is not really an incompatibility. +*/ +/* #define LUA_COMPAT_FLOATSTRING */ + /* }================================================================== */ /* -@@ LUAI_BITSINT defines the number of bits in an int. -** CHANGE here if Lua cannot automatically detect the number of bits of -** your machine. Probably you do not need to change this. +** {================================================================== +** Configuration for Numbers. +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== */ -/* avoid overflows in comparison */ -#if INT_MAX-20 < 32760 /* { */ -#define LUAI_BITSINT 16 -#elif INT_MAX > 2147483640L /* }{ */ -/* int has at least 32 bits */ -#define LUAI_BITSINT 32 -#else /* }{ */ -#error "you must define LUA_BITSINT with number of bits in an integer" -#endif /* } */ +/* +@@ LUA_NUMBER is the floating-point type used by Lua. +@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@@ over a floating number. +@@ l_mathlim(x) corrects limit name 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeric string to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) l_sprintf((s), sz, LUA_NUMBER_FMT, (n)) /* -@@ LUA_INT32 is an signed integer with exactly 32 bits. -@@ LUAI_UMEM is an unsigned integer big enough to count the total -@* memory used by Lua. -@@ LUAI_MEM is a signed integer big enough to count the total memory -@* used by Lua. -** CHANGE here if for some weird reason the default definitions are not -** good enough for your machine. Probably you do not need to change -** this. +@@ lua_numbertointeger converts a float number to an integer, or +** returns 0 if float is not within the range of a lua_Integer. +** (The range comparisons are tricky because of rounding. The tests +** here assume a two-complement representation, where MININTEGER always +** has an exact representation as a float; MAXINTEGER may not have one, +** and therefore its conversion to float may have an ill-defined value.) */ -#if LUAI_BITSINT >= 32 /* { */ -#define LUA_INT32 int -#define LUAI_UMEM size_t -#define LUAI_MEM ptrdiff_t -#else /* }{ */ -/* 16-bit ints */ -#define LUA_INT32 long -#define LUAI_UMEM unsigned long -#define LUAI_MEM long -#endif /* } */ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_mathlim(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_mathlim(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_mathlim(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + /* -@@ LUAI_MAXSTACK limits the size of the Lua stack. -** CHANGE it if you need a different limit. This limit is arbitrary; -** its only purpose is to stop Lua to consume unlimited stack -** space (and to reserve some numbers for pseudo-indices). +@@ LUA_INTEGER is the integer type used by Lua. +** +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +** +@@ LUAI_UACINT is the result of an 'usual argument conversion' +@@ over a lUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ lua_integer2str converts an integer to a string. */ -#if LUAI_BITSINT >= 32 -#define LUAI_MAXSTACK 1000000 -#else -#define LUAI_MAXSTACK 15000 -#endif -/* reserve some space for error handling */ -#define LUAI_FIRSTPSEUDOIDX (-LUAI_MAXSTACK - 1000) +/* The following definitions are good for most cases here */ +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" +#define lua_integer2str(s,sz,n) l_sprintf((s), sz, LUA_INTEGER_FMT, (n)) +#define LUAI_UACINT LUA_INTEGER /* -@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. -** CHANGE it if it uses too much C-stack space. +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) */ -#define LUAL_BUFFERSIZE BUFSIZ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ /* ** {================================================================== -@@ LUA_NUMBER is the type of numbers in Lua. -** CHANGE the following definitions only if you want to build Lua -** with a number type different from double. You may also need to -** change lua_number2int & lua_number2integer. +** Dependencies with C99 and other C details ** =================================================================== */ -#define LUA_NUMBER_DOUBLE -#define LUA_NUMBER double - /* -@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' -@* over a number. +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) */ -#define LUAI_UACNUMBER double +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif /* -@@ LUA_NUMBER_SCAN is the format for reading numbers. -@@ LUA_NUMBER_FMT is the format for writing numbers. -@@ lua_number2str converts a number to a string. -@@ LUAI_MAXNUMBER2STR is maximum size of previous conversion. +@@ lua_strx2number converts an hexadecimal numeric string to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. */ -#define LUA_NUMBER_SCAN "%lf" -#define LUA_NUMBER_FMT "%.14g" -#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) -#define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif /* -@@ l_mathop allows the addition of an 'l' or 'f' to all math operations +@@ lua_number2strx converts a float to an hexadecimal numeric string. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. */ -#define l_mathop(x) (x) +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) ((void)L, l_sprintf(b,sz,f,n)) +#endif /* -@@ lua_str2number converts a decimal numeric string to a number. -@@ lua_strx2number converts an hexadecimal numeric string to a number. -** In C99, 'strtod' does both conversions. C89, however, has no function -** to convert floating hexadecimal strings to numbers. For these -** systems, you can leave 'lua_strx2number' undefined and Lua will -** provide its own implementation. +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) */ -#define lua_str2number(s,p) strtod((s), (p)) - -#if defined(LUA_USE_STRTODHEX) -#define lua_strx2number(s,p) strtod((s), (p)) +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) #endif /* -@@ The luai_num* macros define the primitive operations over numbers. +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) */ +#define LUA_KCONTEXT ptrdiff_t -/* the following operations need the math library */ -#if defined(lobject_c) || defined(lvm_c) -#include -#define luai_nummod(L,a,b) ((a) - l_mathop(floor)((a)/(b))*(b)) -#define luai_numpow(L,a,b) (l_mathop(pow)(a,b)) +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t #endif - -/* these are quite standard operations */ -#if defined(LUA_CORE) -#define luai_numadd(L,a,b) ((a)+(b)) -#define luai_numsub(L,a,b) ((a)-(b)) -#define luai_nummul(L,a,b) ((a)*(b)) -#define luai_numdiv(L,a,b) ((a)/(b)) -#define luai_numunm(L,a) (-(a)) -#define luai_numeq(a,b) ((a)==(b)) -#define luai_numlt(L,a,b) ((a)<(b)) -#define luai_numle(L,a,b) ((a)<=(b)) -#define luai_numisnan(L,a) (!luai_numeq((a), (a))) #endif - -/* -@@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger. -** CHANGE that if ptrdiff_t is not adequate on your machine. (On most -** machines, ptrdiff_t gives a good choice between int or long.) -*/ -#define LUA_INTEGER ptrdiff_t - /* -@@ LUA_UNSIGNED is the integral type used by lua_pushunsigned/lua_tounsigned. -** It must have at least 32 bits. +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include header 'locale.h'.) */ -#define LUA_UNSIGNED unsigned LUA_INT32 +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif +/* }================================================================== */ /* -** Some tricks with doubles +** {================================================================== +** Language Variations +** ===================================================================== */ -#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) /* { */ /* -** The next definitions activate some tricks to speed up the -** conversion from doubles to integer types, mainly to LUA_UNSIGNED. -** -@@ LUA_MSASMTRICK uses Microsoft assembler to avoid clashes with a -** DirectX idiosyncrasy. -** -@@ LUA_IEEE754TRICK uses a trick that should work on any machine -** using IEEE754 with a 32-bit integer type. -** -@@ LUA_IEEELL extends the trick to LUA_INTEGER; should only be -** defined when LUA_INTEGER is a 32-bit integer. -** -@@ LUA_IEEEENDIAN is the endianness of doubles in your machine -** (0 for little endian, 1 for big endian); if not defined, Lua will -** check it dynamically for LUA_IEEE754TRICK (but not for LUA_NANTRICK). -** -@@ LUA_NANTRICK controls the use of a trick to pack all types into -** a single double value, using NaN values to represent non-number -** values. The trick only works on 32-bit machines (ints and pointers -** are 32-bit values) with numbers represented as IEEE 754-2008 doubles -** with conventional endianess (12345678 or 87654321), in CPUs that do -** not produce signaling NaN values (all NaNs are quiet). +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. */ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ -/* Microsoft compiler on a Pentium (32 bit) ? */ -#if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86) /* { */ - -#define LUA_MSASMTRICK -#define LUA_IEEEENDIAN 0 -#define LUA_NANTRICK +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif -/* pentium 32 bits? */ -#elif defined(__i386__) || defined(__i386) || defined(__X86__) /* }{ */ +/* }================================================================== */ -#define LUA_IEEE754TRICK -#define LUA_IEEELL -#define LUA_IEEEENDIAN 0 -#define LUA_NANTRICK -/* pentium 64 bits? */ -#elif defined(__x86_64) /* }{ */ +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). You probably do not want/need to change them. +** ===================================================================== +*/ -#define LUA_IEEE754TRICK -#define LUA_IEEEENDIAN 0 +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif -#elif defined(__POWERPC__) || defined(__ppc__) /* }{ */ -#define LUA_IEEE754TRICK -#define LUA_IEEEENDIAN 1 +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) -#else /* }{ */ -/* assume IEEE754 and a 32-bit integer type */ -#define LUA_IEEE754TRICK +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 -#endif /* } */ -#endif /* } */ +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +** CHANGE it if it uses too much C-stack space. (For long double, +** 'string.format("%.99f", 1e4932)' needs ~5030 bytes, so a +** smaller buffer would force a memory allocation for each call to +** 'string.format'.) +*/ +#if defined(LUA_FLOAT_LONGDOUBLE) +#define LUAL_BUFFERSIZE 8192 +#else +#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) +#endif /* }================================================================== */ +/* +@@ LUA_QL describes how error messages quote program elements. +** Lua does not use these macros anymore; they are here for +** compatibility only. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + /* =================================================================== */ @@ -549,5 +768,7 @@ + + #endif diff --git a/depends/lua/include/lualib.h b/depends/lua/include/lualib.h index da82005c9..5165c0fb3 100644 --- a/depends/lua/include/lualib.h +++ b/depends/lua/include/lualib.h @@ -1,5 +1,5 @@ /* -** $Id: lualib.h,v 1.43.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lualib.h,v 1.44 2014/02/06 17:32:33 roberto Exp $ ** Lua standard libraries ** See Copyright Notice in lua.h */ @@ -29,6 +29,9 @@ LUAMOD_API int (luaopen_os) (lua_State *L); #define LUA_STRLIBNAME "string" LUAMOD_API int (luaopen_string) (lua_State *L); +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + #define LUA_BITLIBNAME "bit32" LUAMOD_API int (luaopen_bit32) (lua_State *L); diff --git a/depends/lua/include/lundump.h b/depends/lua/include/lundump.h index 5255db259..aa5cc82f1 100644 --- a/depends/lua/include/lundump.h +++ b/depends/lua/include/lundump.h @@ -1,5 +1,5 @@ /* -** $Id: lundump.h,v 1.39.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lundump.h,v 1.45 2015/09/08 15:41:05 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -7,22 +7,26 @@ #ifndef lundump_h #define lundump_h +#include "llimits.h" #include "lobject.h" #include "lzio.h" -/* load one chunk; from lundump.c */ -LUAI_FUNC Closure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name); -/* make header; from lundump.c */ -LUAI_FUNC void luaU_header (lu_byte* h); +/* data to catch conversion errors */ +#define LUAC_DATA "\x19\x93\r\n\x1a\n" -/* dump one chunk; from ldump.c */ -LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip); +#define LUAC_INT 0x5678 +#define LUAC_NUM cast_num(370.5) -/* data to catch conversion errors */ -#define LUAC_TAIL "\x19\x93\r\n\x1a\n" +#define MYINT(s) (s[0]-'0') +#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR)) +#define LUAC_FORMAT 0 /* this is the official format */ -/* size in bytes of header of binary files */ -#define LUAC_HEADERSIZE (sizeof(LUA_SIGNATURE)-sizeof(char)+2+6+sizeof(LUAC_TAIL)-sizeof(char)) +/* load one chunk; from lundump.c */ +LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name); + +/* dump one chunk; from ldump.c */ +LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, + void* data, int strip); #endif diff --git a/depends/lua/include/lvm.h b/depends/lua/include/lvm.h index 5380270da..bcf52d20a 100644 --- a/depends/lua/include/lvm.h +++ b/depends/lua/include/lvm.h @@ -1,5 +1,5 @@ /* -** $Id: lvm.h,v 2.18.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lvm.h,v 2.40 2016/01/05 16:07:21 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ @@ -13,32 +13,101 @@ #include "ltm.h" -#define tostring(L,o) (ttisstring(o) || (luaV_tostring(L, o))) +#if !defined(LUA_NOCVTN2S) +#define cvt2str(o) ttisnumber(o) +#else +#define cvt2str(o) 0 /* no conversion from numbers to strings */ +#endif + + +#if !defined(LUA_NOCVTS2N) +#define cvt2num(o) ttisstring(o) +#else +#define cvt2num(o) 0 /* no conversion from strings to numbers */ +#endif + + +/* +** You can define LUA_FLOORN2I if you want to convert floats to integers +** by flooring them (instead of raising an error if they are not +** integral values) +*/ +#if !defined(LUA_FLOORN2I) +#define LUA_FLOORN2I 0 +#endif + + +#define tonumber(o,n) \ + (ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n)) -#define tonumber(o,n) (ttisnumber(o) || (((o) = luaV_tonumber(o,n)) != NULL)) +#define tointeger(o,i) \ + (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I)) -#define equalobj(L,o1,o2) (ttisequal(o1, o2) && luaV_equalobj_(L, o1, o2)) +#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) -#define luaV_rawequalobj(o1,o2) equalobj(NULL,o1,o2) +#define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) + + +/* +** fast track for 'gettable': if 't' is a table and 't[k]' is not nil, +** return 1 with 'slot' pointing to 't[k]' (final result). Otherwise, +** return 0 (meaning it will have to check metamethod) with 'slot' +** pointing to a nil 't[k]' (if 't' is a table) or NULL (otherwise). +** 'f' is the raw get function to use. +*/ +#define luaV_fastget(L,t,k,slot,f) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = f(hvalue(t), k), /* else, do raw access */ \ + !ttisnil(slot))) /* result not nil? */ + +/* +** standard implementation for 'gettable' +*/ +#define luaV_gettable(L,t,k,v) { const TValue *slot; \ + if (luaV_fastget(L,t,k,slot,luaH_get)) { setobj2s(L, v, slot); } \ + else luaV_finishget(L,t,k,v,slot); } + + +/* +** Fast track for set table. If 't' is a table and 't[k]' is not nil, +** call GC barrier, do a raw 't[k]=v', and return true; otherwise, +** return false with 'slot' equal to NULL (if 't' is not a table) or +** 'nil'. (This is needed by 'luaV_finishget'.) Note that, if the macro +** returns true, there is no need to 'invalidateTMcache', because the +** call is not creating a new entry. +*/ +#define luaV_fastset(L,t,k,slot,f,v) \ + (!ttistable(t) \ + ? (slot = NULL, 0) \ + : (slot = f(hvalue(t), k), \ + ttisnil(slot) ? 0 \ + : (luaC_barrierback(L, hvalue(t), v), \ + setobj2t(L, cast(TValue *,slot), v), \ + 1))) -/* not to called directly */ -LUAI_FUNC int luaV_equalobj_ (lua_State *L, const TValue *t1, const TValue *t2); +#define luaV_settable(L,t,k,v) { const TValue *slot; \ + if (!luaV_fastset(L,t,k,slot,luaH_get,v)) \ + luaV_finishset(L,t,k,v,slot); } + +LUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2); LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); -LUAI_FUNC const TValue *luaV_tonumber (const TValue *obj, TValue *n); -LUAI_FUNC int luaV_tostring (lua_State *L, StkId obj); -LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, - StkId val); -LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, - StkId val); +LUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n); +LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode); +LUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); +LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); LUAI_FUNC void luaV_finishOp (lua_State *L); LUAI_FUNC void luaV_execute (lua_State *L); LUAI_FUNC void luaV_concat (lua_State *L, int total); -LUAI_FUNC void luaV_arith (lua_State *L, StkId ra, const TValue *rb, - const TValue *rc, TMS op); +LUAI_FUNC lua_Integer luaV_div (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y); LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb); #endif diff --git a/depends/lua/include/lzio.h b/depends/lua/include/lzio.h index 441f7479c..e7b6f34b1 100644 --- a/depends/lua/include/lzio.h +++ b/depends/lua/include/lzio.h @@ -1,5 +1,5 @@ /* -** $Id: lzio.h,v 1.26.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lzio.h,v 1.31 2015/09/08 15:41:05 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ @@ -32,20 +32,21 @@ typedef struct Mbuffer { #define luaZ_sizebuffer(buff) ((buff)->buffsize) #define luaZ_bufflen(buff) ((buff)->n) +#define luaZ_buffremove(buff,i) ((buff)->n -= (i)) #define luaZ_resetbuffer(buff) ((buff)->n = 0) #define luaZ_resizebuffer(L, buff, size) \ - (luaM_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ + ((buff)->buffer = luaM_reallocvchar(L, (buff)->buffer, \ + (buff)->buffsize, size), \ (buff)->buffsize = size) #define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) -LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data); -LUAI_FUNC size_t luaZ_read (ZIO* z, void* b, size_t n); /* read next n bytes */ +LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n); /* read next n bytes */ @@ -55,7 +56,7 @@ struct Zio { size_t n; /* bytes still unread */ const char *p; /* current position in buffer */ lua_Reader reader; /* reader function */ - void* data; /* additional data */ + void *data; /* additional data */ lua_State *L; /* Lua state (for reader) */ }; diff --git a/depends/lua/src/lapi.c b/depends/lua/src/lapi.c index d011431ea..c9455a5d8 100644 --- a/depends/lua/src/lapi.c +++ b/depends/lua/src/lapi.c @@ -1,16 +1,18 @@ /* -** $Id: lapi.c,v 2.171.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lapi.c,v 2.259 2016/02/29 14:27:14 roberto Exp $ ** Lua API ** See Copyright Notice in lua.h */ +#define lapi_c +#define LUA_CORE + +#include "lprefix.h" + #include #include -#define lapi_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -43,13 +45,16 @@ const char lua_ident[] = /* test for pseudo index */ #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) +/* test for upvalue */ +#define isupvalue(i) ((i) < LUA_REGISTRYINDEX) + /* test for valid but not pseudo index */ #define isstackindex(i, o) (isvalid(o) && !ispseudo(i)) -#define api_checkvalidindex(L, o) api_check(L, isvalid(o), "invalid index") +#define api_checkvalidindex(l,o) api_check(l, isvalid(o), "invalid index") -#define api_checkstackindex(L, i, o) \ - api_check(L, isstackindex(i, o), "index not in the stack") +#define api_checkstackindex(l, i, o) \ + api_check(l, isstackindex(i, o), "index not in the stack") static TValue *index2addr (lua_State *L, int idx) { @@ -89,21 +94,22 @@ static void growstack (lua_State *L, void *ud) { } -LUA_API int lua_checkstack (lua_State *L, int size) { +LUA_API int lua_checkstack (lua_State *L, int n) { int res; CallInfo *ci = L->ci; lua_lock(L); - if (L->stack_last - L->top > size) /* stack large enough? */ + api_check(L, n >= 0, "negative 'n'"); + if (L->stack_last - L->top > n) /* stack large enough? */ res = 1; /* yes; check is OK */ else { /* no; need to grow stack */ int inuse = cast_int(L->top - L->stack) + EXTRA_STACK; - if (inuse > LUAI_MAXSTACK - size) /* can grow without overflow? */ + if (inuse > LUAI_MAXSTACK - n) /* can grow without overflow? */ res = 0; /* no */ else /* try to grow stack */ - res = (luaD_rawrunprotected(L, &growstack, &size) == LUA_OK); + res = (luaD_rawrunprotected(L, &growstack, &n) == LUA_OK); } - if (res && ci->top < L->top + size) - ci->top = L->top + size; /* adjust frame top */ + if (res && ci->top < L->top + n) + ci->top = L->top + n; /* adjust frame top */ lua_unlock(L); return res; } @@ -115,10 +121,11 @@ LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { lua_lock(to); api_checknelems(from, n); api_check(from, G(from) == G(to), "moving among independent states"); - api_check(from, to->ci->top - to->top >= n, "not enough elements to move"); + api_check(from, to->ci->top - to->top >= n, "stack overflow"); from->top -= n; for (i = 0; i < n; i++) { - setobj2s(to, to->top++, from->top + i); + setobj2s(to, to->top, from->top + i); + to->top++; /* stack already checked by previous 'api_check' */ } lua_unlock(to); } @@ -153,7 +160,7 @@ LUA_API const lua_Number *lua_version (lua_State *L) { LUA_API int lua_absindex (lua_State *L, int idx) { return (idx > 0 || ispseudo(idx)) ? idx - : cast_int(L->top - L->ci->func + idx); + : cast_int(L->top - L->ci->func) + idx; } @@ -173,61 +180,56 @@ LUA_API void lua_settop (lua_State *L, int idx) { } else { api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top"); - L->top += idx+1; /* `subtract' index (index is negative) */ + L->top += idx+1; /* 'subtract' index (index is negative) */ } lua_unlock(L); } -LUA_API void lua_remove (lua_State *L, int idx) { - StkId p; - lua_lock(L); - p = index2addr(L, idx); - api_checkstackindex(L, idx, p); - while (++p < L->top) setobjs2s(L, p-1, p); - L->top--; - lua_unlock(L); +/* +** Reverse the stack segment from 'from' to 'to' +** (auxiliary to 'lua_rotate') +*/ +static void reverse (lua_State *L, StkId from, StkId to) { + for (; from < to; from++, to--) { + TValue temp; + setobj(L, &temp, from); + setobjs2s(L, from, to); + setobj2s(L, to, &temp); + } } -LUA_API void lua_insert (lua_State *L, int idx) { - StkId p; - StkId q; +/* +** Let x = AB, where A is a prefix of length 'n'. Then, +** rotate x n == BA. But BA == (A^r . B^r)^r. +*/ +LUA_API void lua_rotate (lua_State *L, int idx, int n) { + StkId p, t, m; lua_lock(L); - p = index2addr(L, idx); + t = L->top - 1; /* end of stack segment being rotated */ + p = index2addr(L, idx); /* start of segment */ api_checkstackindex(L, idx, p); - for (q = L->top; q > p; q--) /* use L->top as a temporary */ - setobjs2s(L, q, q - 1); - setobjs2s(L, p, L->top); + api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); + m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ + reverse(L, p, m); /* reverse the prefix with length 'n' */ + reverse(L, m + 1, t); /* reverse the suffix */ + reverse(L, p, t); /* reverse the entire segment */ lua_unlock(L); } -static void moveto (lua_State *L, TValue *fr, int idx) { - TValue *to = index2addr(L, idx); +LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { + TValue *fr, *to; + lua_lock(L); + fr = index2addr(L, fromidx); + to = index2addr(L, toidx); api_checkvalidindex(L, to); setobj(L, to, fr); - if (idx < LUA_REGISTRYINDEX) /* function upvalue? */ + if (isupvalue(toidx)) /* function upvalue? */ luaC_barrier(L, clCvalue(L->ci->func), fr); /* LUA_REGISTRYINDEX does not need gc barrier (collector revisits it before finishing collection) */ -} - - -LUA_API void lua_replace (lua_State *L, int idx) { - lua_lock(L); - api_checknelems(L, 1); - moveto(L, L->top - 1, idx); - L->top--; - lua_unlock(L); -} - - -LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { - TValue *fr; - lua_lock(L); - fr = index2addr(L, fromidx); - moveto(L, fr, toidx); lua_unlock(L); } @@ -248,12 +250,13 @@ LUA_API void lua_pushvalue (lua_State *L, int idx) { LUA_API int lua_type (lua_State *L, int idx) { StkId o = index2addr(L, idx); - return (isvalid(o) ? ttypenv(o) : LUA_TNONE); + return (isvalid(o) ? ttnov(o) : LUA_TNONE); } LUA_API const char *lua_typename (lua_State *L, int t) { UNUSED(L); + api_check(L, LUA_TNONE <= t && t < LUA_NUMTAGS, "invalid tag"); return ttypename(t); } @@ -264,22 +267,28 @@ LUA_API int lua_iscfunction (lua_State *L, int idx) { } +LUA_API int lua_isinteger (lua_State *L, int idx) { + StkId o = index2addr(L, idx); + return ttisinteger(o); +} + + LUA_API int lua_isnumber (lua_State *L, int idx) { - TValue n; + lua_Number n; const TValue *o = index2addr(L, idx); return tonumber(o, &n); } LUA_API int lua_isstring (lua_State *L, int idx) { - int t = lua_type(L, idx); - return (t == LUA_TSTRING || t == LUA_TNUMBER); + const TValue *o = index2addr(L, idx); + return (ttisstring(o) || cvt2str(o)); } LUA_API int lua_isuserdata (lua_State *L, int idx) { const TValue *o = index2addr(L, idx); - return (ttisuserdata(o) || ttislightuserdata(o)); + return (ttisfulluserdata(o) || ttislightuserdata(o)); } @@ -291,24 +300,17 @@ LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { LUA_API void lua_arith (lua_State *L, int op) { - StkId o1; /* 1st operand */ - StkId o2; /* 2nd operand */ lua_lock(L); - if (op != LUA_OPUNM) /* all other operations expect two operands */ - api_checknelems(L, 2); - else { /* for unary minus, add fake 2nd operand */ + if (op != LUA_OPUNM && op != LUA_OPBNOT) + api_checknelems(L, 2); /* all other operations expect two operands */ + else { /* for unary operations, add fake 2nd operand */ api_checknelems(L, 1); setobjs2s(L, L->top, L->top - 1); - L->top++; - } - o1 = L->top - 2; - o2 = L->top - 1; - if (ttisnumber(o1) && ttisnumber(o2)) { - setnvalue(o1, luaO_arith(op, nvalue(o1), nvalue(o2))); + api_incr_top(L); } - else - luaV_arith(L, o1, o1, o2, cast(TMS, op - LUA_OPADD + TM_ADD)); - L->top--; + /* first operand at top - 2, second at top - 1; result go to top - 2 */ + luaO_arith(L, op, L->top - 2, L->top - 1, L->top - 2); + L->top--; /* remove second operand */ lua_unlock(L); } @@ -321,7 +323,7 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { o2 = index2addr(L, index2); if (isvalid(o1) && isvalid(o2)) { switch (op) { - case LUA_OPEQ: i = equalobj(L, o1, o2); break; + case LUA_OPEQ: i = luaV_equalobj(L, o1, o2); break; case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break; case LUA_OPLE: i = luaV_lessequal(L, o1, o2); break; default: api_check(L, 0, "invalid option"); @@ -332,51 +334,33 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { } -LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum) { - TValue n; - const TValue *o = index2addr(L, idx); - if (tonumber(o, &n)) { - if (isnum) *isnum = 1; - return nvalue(o); - } - else { - if (isnum) *isnum = 0; - return 0; - } +LUA_API size_t lua_stringtonumber (lua_State *L, const char *s) { + size_t sz = luaO_str2num(s, L->top); + if (sz != 0) + api_incr_top(L); + return sz; } -LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum) { - TValue n; +LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum) { + lua_Number n; const TValue *o = index2addr(L, idx); - if (tonumber(o, &n)) { - lua_Integer res; - lua_Number num = nvalue(o); - lua_number2integer(res, num); - if (isnum) *isnum = 1; - return res; - } - else { - if (isnum) *isnum = 0; - return 0; - } + int isnum = tonumber(o, &n); + if (!isnum) + n = 0; /* call to 'tonumber' may change 'n' even if it fails */ + if (pisnum) *pisnum = isnum; + return n; } -LUA_API lua_Unsigned lua_tounsignedx (lua_State *L, int idx, int *isnum) { - TValue n; +LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) { + lua_Integer res; const TValue *o = index2addr(L, idx); - if (tonumber(o, &n)) { - lua_Unsigned res; - lua_Number num = nvalue(o); - lua_number2unsigned(res, num); - if (isnum) *isnum = 1; - return res; - } - else { - if (isnum) *isnum = 0; - return 0; - } + int isnum = tointeger(o, &res); + if (!isnum) + res = 0; /* call to 'tointeger' may change 'n' even if it fails */ + if (pisnum) *pisnum = isnum; + return res; } @@ -389,25 +373,27 @@ LUA_API int lua_toboolean (lua_State *L, int idx) { LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { StkId o = index2addr(L, idx); if (!ttisstring(o)) { - lua_lock(L); /* `luaV_tostring' may create a new string */ - if (!luaV_tostring(L, o)) { /* conversion failed? */ + if (!cvt2str(o)) { /* not convertible? */ if (len != NULL) *len = 0; - lua_unlock(L); return NULL; } + lua_lock(L); /* 'luaO_tostring' may create a new string */ + luaO_tostring(L, o); luaC_checkGC(L); o = index2addr(L, idx); /* previous call may reallocate the stack */ lua_unlock(L); } - if (len != NULL) *len = tsvalue(o)->len; + if (len != NULL) + *len = vslen(o); return svalue(o); } LUA_API size_t lua_rawlen (lua_State *L, int idx) { StkId o = index2addr(L, idx); - switch (ttypenv(o)) { - case LUA_TSTRING: return tsvalue(o)->len; + switch (ttype(o)) { + case LUA_TSHRSTR: return tsvalue(o)->shrlen; + case LUA_TLNGSTR: return tsvalue(o)->u.lnglen; case LUA_TUSERDATA: return uvalue(o)->len; case LUA_TTABLE: return luaH_getn(hvalue(o)); default: return 0; @@ -426,8 +412,8 @@ LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { LUA_API void *lua_touserdata (lua_State *L, int idx) { StkId o = index2addr(L, idx); - switch (ttypenv(o)) { - case LUA_TUSERDATA: return (rawuvalue(o) + 1); + switch (ttnov(o)) { + case LUA_TUSERDATA: return getudatamem(uvalue(o)); case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; } @@ -448,9 +434,8 @@ LUA_API const void *lua_topointer (lua_State *L, int idx) { case LUA_TCCL: return clCvalue(o); case LUA_TLCF: return cast(void *, cast(size_t, fvalue(o))); case LUA_TTHREAD: return thvalue(o); - case LUA_TUSERDATA: - case LUA_TLIGHTUSERDATA: - return lua_touserdata(L, idx); + case LUA_TUSERDATA: return getudatamem(uvalue(o)); + case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; } } @@ -472,9 +457,7 @@ LUA_API void lua_pushnil (lua_State *L) { LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { lua_lock(L); - setnvalue(L->top, n); - luai_checknum(L, L->top, - luaG_runerror(L, "C API - attempt to push a signaling NaN")); + setfltvalue(L->top, n); api_incr_top(L); lua_unlock(L); } @@ -482,49 +465,43 @@ LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { lua_lock(L); - setnvalue(L->top, cast_num(n)); - api_incr_top(L); - lua_unlock(L); -} - - -LUA_API void lua_pushunsigned (lua_State *L, lua_Unsigned u) { - lua_Number n; - lua_lock(L); - n = lua_unsigned2number(u); - setnvalue(L->top, n); + setivalue(L->top, n); api_incr_top(L); lua_unlock(L); } +/* +** Pushes on the stack a string with given length. Avoid using 's' when +** 'len' == 0 (as 's' can be NULL in that case), due to later use of +** 'memcmp' and 'memcpy'. +*/ LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { TString *ts; lua_lock(L); - luaC_checkGC(L); - ts = luaS_newlstr(L, s, len); + ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len); setsvalue2s(L, L->top, ts); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return getstr(ts); } LUA_API const char *lua_pushstring (lua_State *L, const char *s) { - if (s == NULL) { - lua_pushnil(L); - return NULL; - } + lua_lock(L); + if (s == NULL) + setnilvalue(L->top); else { TString *ts; - lua_lock(L); - luaC_checkGC(L); ts = luaS_new(L, s); setsvalue2s(L, L->top, ts); - api_incr_top(L); - lua_unlock(L); - return getstr(ts); + s = getstr(ts); /* internal copy's address */ } + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return s; } @@ -532,8 +509,8 @@ LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { const char *ret; lua_lock(L); - luaC_checkGC(L); ret = luaO_pushvfstring(L, fmt, argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -543,10 +520,10 @@ LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); - luaC_checkGC(L); va_start(argp, fmt); ret = luaO_pushvfstring(L, fmt, argp); va_end(argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -558,18 +535,20 @@ LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { setfvalue(L->top, fn); } else { - Closure *cl; + CClosure *cl; api_checknelems(L, n); api_check(L, n <= MAXUPVAL, "upvalue index too large"); - luaC_checkGC(L); cl = luaF_newCclosure(L, n); - cl->c.f = fn; + cl->f = fn; L->top -= n; - while (n--) - setobj2n(L, &cl->c.upvalue[n], L->top + n); + while (n--) { + setobj2n(L, &cl->upvalue[n], L->top + n); + /* does not need barrier because closure is white */ + } setclCvalue(L, L->top, cl); } api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); } @@ -605,48 +584,77 @@ LUA_API int lua_pushthread (lua_State *L) { */ -LUA_API void lua_getglobal (lua_State *L, const char *var) { +static int auxgetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setsvalue2s(L, L->top, str); + api_incr_top(L); + luaV_finishget(L, t, L->top - 1, L->top - 1, slot); + } + lua_unlock(L); + return ttnov(L->top - 1); +} + + +LUA_API int lua_getglobal (lua_State *L, const char *name) { Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ lua_lock(L); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top++, luaS_new(L, var)); - luaV_gettable(L, gt, L->top - 1, L->top - 1); - lua_unlock(L); + return auxgetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); } -LUA_API void lua_gettable (lua_State *L, int idx) { +LUA_API int lua_gettable (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2addr(L, idx); luaV_gettable(L, t, L->top - 1, L->top - 1); lua_unlock(L); + return ttnov(L->top - 1); +} + + +LUA_API int lua_getfield (lua_State *L, int idx, const char *k) { + lua_lock(L); + return auxgetstr(L, index2addr(L, idx), k); } -LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { +LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) { StkId t; + const TValue *slot; lua_lock(L); t = index2addr(L, idx); - setsvalue2s(L, L->top, luaS_new(L, k)); - api_incr_top(L); - luaV_gettable(L, t, L->top - 1, L->top - 1); + if (luaV_fastget(L, t, n, slot, luaH_getint)) { + setobj2s(L, L->top, slot); + api_incr_top(L); + } + else { + setivalue(L->top, n); + api_incr_top(L); + luaV_finishget(L, t, L->top - 1, L->top - 1, slot); + } lua_unlock(L); + return ttnov(L->top - 1); } -LUA_API void lua_rawget (lua_State *L, int idx) { +LUA_API int lua_rawget (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2addr(L, idx); api_check(L, ttistable(t), "table expected"); setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); lua_unlock(L); + return ttnov(L->top - 1); } -LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { +LUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n) { StkId t; lua_lock(L); t = index2addr(L, idx); @@ -654,10 +662,11 @@ LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { setobj2s(L, L->top, luaH_getint(hvalue(t), n)); api_incr_top(L); lua_unlock(L); + return ttnov(L->top - 1); } -LUA_API void lua_rawgetp (lua_State *L, int idx, const void *p) { +LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { StkId t; TValue k; lua_lock(L); @@ -667,29 +676,30 @@ LUA_API void lua_rawgetp (lua_State *L, int idx, const void *p) { setobj2s(L, L->top, luaH_get(hvalue(t), &k)); api_incr_top(L); lua_unlock(L); + return ttnov(L->top - 1); } LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { Table *t; lua_lock(L); - luaC_checkGC(L); t = luaH_new(L); sethvalue(L, L->top, t); api_incr_top(L); if (narray > 0 || nrec > 0) luaH_resize(L, t, narray, nrec); + luaC_checkGC(L); lua_unlock(L); } LUA_API int lua_getmetatable (lua_State *L, int objindex) { const TValue *obj; - Table *mt = NULL; - int res; + Table *mt; + int res = 0; lua_lock(L); obj = index2addr(L, objindex); - switch (ttypenv(obj)) { + switch (ttnov(obj)) { case LUA_TTABLE: mt = hvalue(obj)->metatable; break; @@ -697,12 +707,10 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { mt = uvalue(obj)->metatable; break; default: - mt = G(L)->mt[ttypenv(obj)]; + mt = G(L)->mt[ttnov(obj)]; break; } - if (mt == NULL) - res = 0; - else { + if (mt != NULL) { sethvalue(L, L->top, mt); api_incr_top(L); res = 1; @@ -712,17 +720,15 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { } -LUA_API void lua_getuservalue (lua_State *L, int idx) { +LUA_API int lua_getuservalue (lua_State *L, int idx) { StkId o; lua_lock(L); o = index2addr(L, idx); - api_check(L, ttisuserdata(o), "userdata expected"); - if (uvalue(o)->env) { - sethvalue(L, L->top, uvalue(o)->env); - } else - setnilvalue(L->top); + api_check(L, ttisfulluserdata(o), "full userdata expected"); + getuservalue(L, uvalue(o), L->top); api_incr_top(L); lua_unlock(L); + return ttnov(L->top - 1); } @@ -730,17 +736,29 @@ LUA_API void lua_getuservalue (lua_State *L, int idx) { ** set functions (stack -> Lua) */ +/* +** t[k] = value at the top of the stack (where 'k' is a string) +*/ +static void auxsetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + api_checknelems(L, 1); + if (luaV_fastset(L, t, str, slot, luaH_getstr, L->top - 1)) + L->top--; /* pop value */ + else { + setsvalue2s(L, L->top, str); /* push 'str' (to make it a TValue) */ + api_incr_top(L); + luaV_finishset(L, t, L->top - 1, L->top - 2, slot); + L->top -= 2; /* pop value and key */ + } + lua_unlock(L); /* lock done by caller */ +} + -LUA_API void lua_setglobal (lua_State *L, const char *var) { +LUA_API void lua_setglobal (lua_State *L, const char *name) { Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ - lua_lock(L); - api_checknelems(L, 1); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top++, luaS_new(L, var)); - luaV_settable(L, gt, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ - lua_unlock(L); + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name); } @@ -756,54 +774,69 @@ LUA_API void lua_settable (lua_State *L, int idx) { LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, index2addr(L, idx), k); +} + + +LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { StkId t; + const TValue *slot; lua_lock(L); api_checknelems(L, 1); t = index2addr(L, idx); - setsvalue2s(L, L->top++, luaS_new(L, k)); - luaV_settable(L, t, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ + if (luaV_fastset(L, t, n, slot, luaH_getint, L->top - 1)) + L->top--; /* pop value */ + else { + setivalue(L->top, n); + api_incr_top(L); + luaV_finishset(L, t, L->top - 1, L->top - 2, slot); + L->top -= 2; /* pop value and key */ + } lua_unlock(L); } LUA_API void lua_rawset (lua_State *L, int idx) { - StkId t; + StkId o; + TValue *slot; lua_lock(L); api_checknelems(L, 2); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1); - invalidateTMcache(hvalue(t)); - luaC_barrierback(L, gcvalue(t), L->top-1); + o = index2addr(L, idx); + api_check(L, ttistable(o), "table expected"); + slot = luaH_set(L, hvalue(o), L->top - 2); + setobj2t(L, slot, L->top - 1); + invalidateTMcache(hvalue(o)); + luaC_barrierback(L, hvalue(o), L->top-1); L->top -= 2; lua_unlock(L); } -LUA_API void lua_rawseti (lua_State *L, int idx, int n) { - StkId t; +LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { + StkId o; lua_lock(L); api_checknelems(L, 1); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - luaH_setint(L, hvalue(t), n, L->top - 1); - luaC_barrierback(L, gcvalue(t), L->top-1); + o = index2addr(L, idx); + api_check(L, ttistable(o), "table expected"); + luaH_setint(L, hvalue(o), n, L->top - 1); + luaC_barrierback(L, hvalue(o), L->top-1); L->top--; lua_unlock(L); } LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { - StkId t; - TValue k; + StkId o; + TValue k, *slot; lua_lock(L); api_checknelems(L, 1); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); + o = index2addr(L, idx); + api_check(L, ttistable(o), "table expected"); setpvalue(&k, cast(void *, p)); - setobj2t(L, luaH_set(L, hvalue(t), &k), L->top - 1); - luaC_barrierback(L, gcvalue(t), L->top - 1); + slot = luaH_set(L, hvalue(o), &k); + setobj2t(L, slot, L->top - 1); + luaC_barrierback(L, hvalue(o), L->top - 1); L->top--; lua_unlock(L); } @@ -821,11 +854,11 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { api_check(L, ttistable(L->top - 1), "table expected"); mt = hvalue(L->top - 1); } - switch (ttypenv(obj)) { + switch (ttnov(obj)) { case LUA_TTABLE: { hvalue(obj)->metatable = mt; if (mt) { - luaC_objbarrierback(L, gcvalue(obj), mt); + luaC_objbarrier(L, gcvalue(obj), mt); luaC_checkfinalizer(L, gcvalue(obj), mt); } break; @@ -833,13 +866,13 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { case LUA_TUSERDATA: { uvalue(obj)->metatable = mt; if (mt) { - luaC_objbarrier(L, rawuvalue(obj), mt); + luaC_objbarrier(L, uvalue(obj), mt); luaC_checkfinalizer(L, gcvalue(obj), mt); } break; } default: { - G(L)->mt[ttypenv(obj)] = mt; + G(L)->mt[ttnov(obj)] = mt; break; } } @@ -854,21 +887,16 @@ LUA_API void lua_setuservalue (lua_State *L, int idx) { lua_lock(L); api_checknelems(L, 1); o = index2addr(L, idx); - api_check(L, ttisuserdata(o), "userdata expected"); - if (ttisnil(L->top - 1)) - uvalue(o)->env = NULL; - else { - api_check(L, ttistable(L->top - 1), "table expected"); - uvalue(o)->env = hvalue(L->top - 1); - luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); - } + api_check(L, ttisfulluserdata(o), "full userdata expected"); + setuservalue(L, uvalue(o), L->top - 1); + luaC_barrier(L, gcvalue(o), L->top - 1); L->top--; lua_unlock(L); } /* -** `load' and `call' functions (run Lua code) +** 'load' and 'call' functions (run Lua code) */ @@ -877,17 +905,8 @@ LUA_API void lua_setuservalue (lua_State *L, int idx) { "results from function overflow current stack size") -LUA_API int lua_getctx (lua_State *L, int *ctx) { - if (L->ci->callstatus & CIST_YIELDED) { - if (ctx) *ctx = L->ci->u.c.ctx; - return L->ci->u.c.status; - } - else return LUA_OK; -} - - -LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, - lua_CFunction k) { +LUA_API void lua_callk (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k) { StkId func; lua_lock(L); api_check(L, k == NULL || !isLua(L->ci), @@ -899,10 +918,10 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, if (k != NULL && L->nny == 0) { /* need to prepare continuation? */ L->ci->u.c.k = k; /* save continuation */ L->ci->u.c.ctx = ctx; /* save context */ - luaD_call(L, func, nresults, 1); /* do the call */ + luaD_call(L, func, nresults); /* do the call */ } else /* no continuation or no yieldable */ - luaD_call(L, func, nresults, 0); /* just do the call */ + luaD_callnoyield(L, func, nresults); /* just do the call */ adjustresults(L, nresults); lua_unlock(L); } @@ -912,7 +931,7 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, int ctx, /* ** Execute a protected call. */ -struct CallS { /* data to `f_call' */ +struct CallS { /* data to 'f_call' */ StkId func; int nresults; }; @@ -920,13 +939,13 @@ struct CallS { /* data to `f_call' */ static void f_call (lua_State *L, void *ud) { struct CallS *c = cast(struct CallS *, ud); - luaD_call(L, c->func, c->nresults, 0); + luaD_callnoyield(L, c->func, c->nresults); } LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, - int ctx, lua_CFunction k) { + lua_KContext ctx, lua_KFunction k) { struct CallS c; int status; ptrdiff_t func; @@ -954,12 +973,11 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, ci->u.c.ctx = ctx; /* save context */ /* save information for error recovery */ ci->extra = savestack(L, c.func); - ci->u.c.old_allowhook = L->allowhook; ci->u.c.old_errfunc = L->errfunc; L->errfunc = func; - /* mark that function may do error recovery */ - ci->callstatus |= CIST_YPCALL; - luaD_call(L, c.func, nresults, 1); /* do the call */ + setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */ + ci->callstatus |= CIST_YPCALL; /* function can do error recovery */ + luaD_call(L, c.func, nresults); /* do the call */ ci->callstatus &= ~CIST_YPCALL; L->errfunc = ci->u.c.old_errfunc; status = LUA_OK; /* if it is here, there were no errors */ @@ -980,13 +998,13 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, status = luaD_protectedparser(L, &z, chunkname, mode); if (status == LUA_OK) { /* no errors? */ LClosure *f = clLvalue(L->top - 1); /* get newly created function */ - if (f->nupvalues == 1) { /* does it have one upvalue? */ + if (f->nupvalues >= 1) { /* does it have an upvalue? */ /* get global table from registry */ Table *reg = hvalue(&G(L)->l_registry); const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS); /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */ setobj(L, f->upvals[0]->v, gt); - luaC_barrier(L, f->upvals[0], gt); + luaC_upvalbarrier(L, f->upvals[0]); } } lua_unlock(L); @@ -994,14 +1012,14 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, } -LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) { +LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { int status; TValue *o; lua_lock(L); api_checknelems(L, 1); o = L->top - 1; if (isLfunction(o)) - status = luaU_dump(L, getproto(o), writer, data, 0); + status = luaU_dump(L, getproto(o), writer, data, strip); else status = 1; lua_unlock(L); @@ -1047,19 +1065,21 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { break; } case LUA_GCSTEP: { - if (g->gckind == KGC_GEN) { /* generational mode? */ - res = (g->GCestimate == 0); /* true if it will do major collection */ - luaC_forcestep(L); /* do a single step */ + l_mem debt = 1; /* =1 to signal that it did an actual step */ + lu_byte oldrunning = g->gcrunning; + g->gcrunning = 1; /* allow GC to run */ + if (data == 0) { + luaE_setdebt(g, -GCSTEPSIZE); /* to do a "small" step */ + luaC_step(L); } - else { - lu_mem debt = cast(lu_mem, data) * 1024 - GCSTEPSIZE; - if (g->gcrunning) - debt += g->GCdebt; /* include current debt */ - luaE_setdebt(g, debt); - luaC_forcestep(L); - if (g->gcstate == GCSpause) /* end of cycle? */ - res = 1; /* signal it */ + else { /* add 'data' to total debt */ + debt = cast(l_mem, data) * 1024 + g->GCdebt; + luaE_setdebt(g, debt); + luaC_checkGC(L); } + g->gcrunning = oldrunning; /* restore previous state */ + if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ + res = 1; /* signal it */ break; } case LUA_GCSETPAUSE: { @@ -1067,13 +1087,9 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { g->gcpause = data; break; } - case LUA_GCSETMAJORINC: { - res = g->gcmajorinc; - g->gcmajorinc = data; - break; - } case LUA_GCSETSTEPMUL: { res = g->gcstepmul; + if (data < 40) data = 40; /* avoid ridiculous low values (and 0) */ g->gcstepmul = data; break; } @@ -1081,14 +1097,6 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { res = g->gcrunning; break; } - case LUA_GCGEN: { /* change collector to generational mode */ - luaC_changemode(L, KGC_GEN); - break; - } - case LUA_GCINC: { /* change collector to incremental mode */ - luaC_changemode(L, KGC_NORMAL); - break; - } default: res = -1; /* invalid option */ } lua_unlock(L); @@ -1132,7 +1140,6 @@ LUA_API void lua_concat (lua_State *L, int n) { lua_lock(L); api_checknelems(L, n); if (n >= 2) { - luaC_checkGC(L); luaV_concat(L, n); } else if (n == 0) { /* push empty string */ @@ -1140,6 +1147,7 @@ LUA_API void lua_concat (lua_State *L, int n) { api_incr_top(L); } /* else n == 1; nothing to do */ + luaC_checkGC(L); lua_unlock(L); } @@ -1175,24 +1183,24 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { LUA_API void *lua_newuserdata (lua_State *L, size_t size) { Udata *u; lua_lock(L); - luaC_checkGC(L); - u = luaS_newudata(L, size, NULL); + u = luaS_newudata(L, size); setuvalue(L, L->top, u); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); - return u + 1; + return getudatamem(u); } static const char *aux_upvalue (StkId fi, int n, TValue **val, - GCObject **owner) { + CClosure **owner, UpVal **uv) { switch (ttype(fi)) { case LUA_TCCL: { /* C closure */ CClosure *f = clCvalue(fi); if (!(1 <= n && n <= f->nupvalues)) return NULL; *val = &f->upvalue[n-1]; - if (owner) *owner = obj2gco(f); + if (owner) *owner = f; return ""; } case LUA_TLCL: { /* Lua closure */ @@ -1201,9 +1209,9 @@ static const char *aux_upvalue (StkId fi, int n, TValue **val, Proto *p = f->p; if (!(1 <= n && n <= p->sizeupvalues)) return NULL; *val = f->upvals[n-1]->v; - if (owner) *owner = obj2gco(f->upvals[n - 1]); + if (uv) *uv = f->upvals[n - 1]; name = p->upvalues[n-1].name; - return (name == NULL) ? "" : getstr(name); + return (name == NULL) ? "(*no name)" : getstr(name); } default: return NULL; /* not a closure */ } @@ -1214,7 +1222,7 @@ LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val = NULL; /* to avoid warnings */ lua_lock(L); - name = aux_upvalue(index2addr(L, funcindex), n, &val, NULL); + name = aux_upvalue(index2addr(L, funcindex), n, &val, NULL, NULL); if (name) { setobj2s(L, L->top, val); api_incr_top(L); @@ -1227,16 +1235,18 @@ LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val = NULL; /* to avoid warnings */ - GCObject *owner = NULL; /* to avoid warnings */ + CClosure *owner = NULL; + UpVal *uv = NULL; StkId fi; lua_lock(L); fi = index2addr(L, funcindex); api_checknelems(L, 1); - name = aux_upvalue(fi, n, &val, &owner); + name = aux_upvalue(fi, n, &val, &owner, &uv); if (name) { L->top--; setobj(L, val, L->top); - luaC_barrier(L, owner, L->top); + if (owner) { luaC_barrier(L, owner, L->top); } + else if (uv) { luaC_upvalbarrier(L, uv); } } lua_unlock(L); return name; @@ -1278,7 +1288,11 @@ LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, LClosure *f1; UpVal **up1 = getupvalref(L, fidx1, n1, &f1); UpVal **up2 = getupvalref(L, fidx2, n2, NULL); + luaC_upvdeccount(L, *up1); *up1 = *up2; - luaC_objbarrier(L, f1, *up2); + (*up1)->refcount++; + if (upisopen(*up1)) (*up1)->u.open.touched = 1; + luaC_upvalbarrier(L, *up1); } + diff --git a/depends/lua/src/lauxlib.c b/depends/lua/src/lauxlib.c index b00f8c709..bacf43b3e 100644 --- a/depends/lua/src/lauxlib.c +++ b/depends/lua/src/lauxlib.c @@ -1,9 +1,14 @@ /* -** $Id: lauxlib.c,v 1.248.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lauxlib.c,v 1.286 2016/01/08 15:33:09 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ +#define lauxlib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include @@ -12,13 +17,11 @@ #include -/* This file uses only the official API of Lua. +/* +** This file uses only the official API of Lua. ** Any function declared here could be written as an application function. */ -#define lauxlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -31,8 +34,8 @@ */ -#define LEVELS1 12 /* size of the first part of the stack */ -#define LEVELS2 10 /* size of the second part of the stack */ +#define LEVELS1 10 /* size of the first part of the stack */ +#define LEVELS2 11 /* size of the second part of the stack */ @@ -64,11 +67,20 @@ static int findfield (lua_State *L, int objidx, int level) { } +/* +** Search for a name for a function in all loaded modules +** (registry._LOADED). +*/ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { int top = lua_gettop(L); lua_getinfo(L, "f", ar); /* push function */ - lua_pushglobaltable(L); + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); if (findfield(L, top + 1, 2)) { + const char *name = lua_tostring(L, -1); + if (strncmp(name, "_G.", 3) == 0) { /* name start with '_G.'? */ + lua_pushstring(L, name + 3); /* push name without prefix */ + lua_remove(L, -2); /* remove original name */ + } lua_copy(L, -1, top + 1); /* move name to proper place */ lua_pop(L, 2); /* remove pushed values */ return 1; @@ -81,24 +93,22 @@ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { static void pushfuncname (lua_State *L, lua_Debug *ar) { - if (*ar->namewhat != '\0') /* is there a name? */ - lua_pushfstring(L, "function " LUA_QS, ar->name); + if (pushglobalfuncname(L, ar)) { /* try first a global name */ + lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); + lua_remove(L, -2); /* remove name */ + } + else if (*ar->namewhat != '\0') /* is there a name from code? */ + lua_pushfstring(L, "%s '%s'", ar->namewhat, ar->name); /* use it */ else if (*ar->what == 'm') /* main? */ lua_pushliteral(L, "main chunk"); - else if (*ar->what == 'C') { - if (pushglobalfuncname(L, ar)) { - lua_pushfstring(L, "function " LUA_QS, lua_tostring(L, -1)); - lua_remove(L, -2); /* remove name */ - } - else - lua_pushliteral(L, "?"); - } - else + else if (*ar->what != 'C') /* for Lua functions, use */ lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); + else /* nothing left... */ + lua_pushliteral(L, "?"); } -static int countlevels (lua_State *L) { +static int lastlevel (lua_State *L) { lua_Debug ar; int li = 1, le = 1; /* find an upper bound */ @@ -117,14 +127,16 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { lua_Debug ar; int top = lua_gettop(L); - int numlevels = countlevels(L1); - int mark = (numlevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; - if (msg) lua_pushfstring(L, "%s\n", msg); + int last = lastlevel(L1); + int n1 = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; + if (msg) + lua_pushfstring(L, "%s\n", msg); + luaL_checkstack(L, 10, NULL); lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { - if (level == mark) { /* too many levels? */ + if (n1-- == 0) { /* too many levels? */ lua_pushliteral(L, "\n\t..."); /* add a '...' */ - level = numlevels - LEVELS2; /* and skip to last ones */ + level = last - LEVELS2 + 1; /* and skip to last ones */ } else { lua_getinfo(L1, "Slnt", &ar); @@ -150,36 +162,47 @@ LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, ** ======================================================= */ -LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) { +LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { lua_Debug ar; if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ - return luaL_error(L, "bad argument #%d (%s)", narg, extramsg); + return luaL_error(L, "bad argument #%d (%s)", arg, extramsg); lua_getinfo(L, "n", &ar); if (strcmp(ar.namewhat, "method") == 0) { - narg--; /* do not count `self' */ - if (narg == 0) /* error is in the self argument itself? */ - return luaL_error(L, "calling " LUA_QS " on bad self (%s)", + arg--; /* do not count 'self' */ + if (arg == 0) /* error is in the self argument itself? */ + return luaL_error(L, "calling '%s' on bad self (%s)", ar.name, extramsg); } if (ar.name == NULL) ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; - return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)", - narg, ar.name, extramsg); + return luaL_error(L, "bad argument #%d to '%s' (%s)", + arg, ar.name, extramsg); } -static int typeerror (lua_State *L, int narg, const char *tname) { - const char *msg = lua_pushfstring(L, "%s expected, got %s", - tname, luaL_typename(L, narg)); - return luaL_argerror(L, narg, msg); +static int typeerror (lua_State *L, int arg, const char *tname) { + const char *msg; + const char *typearg; /* name for the type of the actual argument */ + if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING) + typearg = lua_tostring(L, -1); /* use the given type name */ + else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA) + typearg = "light userdata"; /* special name for messages */ + else + typearg = luaL_typename(L, arg); /* standard name */ + msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg); + return luaL_argerror(L, arg, msg); } -static void tag_error (lua_State *L, int narg, int tag) { - typeerror(L, narg, lua_typename(L, tag)); +static void tag_error (lua_State *L, int arg, int tag) { + typeerror(L, arg, lua_typename(L, tag)); } +/* +** The use of 'lua_pushfstring' ensures this function does not +** need reserved stack space when called. +*/ LUALIB_API void luaL_where (lua_State *L, int level) { lua_Debug ar; if (lua_getstack(L, level, &ar)) { /* check function at level */ @@ -189,10 +212,15 @@ LUALIB_API void luaL_where (lua_State *L, int level) { return; } } - lua_pushliteral(L, ""); /* else, no information available... */ + lua_pushfstring(L, ""); /* else, no information available... */ } +/* +** Again, the use of 'lua_pushvfstring' ensures this function does +** not need reserved stack space when called. (At worst, it generates +** an error with "stack overflow" instead of the given message.) +*/ LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); @@ -222,7 +250,7 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { } -#if !defined(inspectstat) /* { */ +#if !defined(l_inspectstat) /* { */ #if defined(LUA_USE_POSIX) @@ -231,13 +259,13 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { /* ** use appropriate macros to interpret 'pclose' return status */ -#define inspectstat(stat,what) \ +#define l_inspectstat(stat,what) \ if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \ else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = "signal"; } #else -#define inspectstat(stat,what) /* no op */ +#define l_inspectstat(stat,what) /* no op */ #endif @@ -249,7 +277,7 @@ LUALIB_API int luaL_execresult (lua_State *L, int stat) { if (stat == -1) /* error? */ return luaL_fileresult(L, 0, NULL); else { - inspectstat(stat, what); /* interpret result */ + l_inspectstat(stat, what); /* interpret result */ if (*what == 'e' && stat == 0) /* successful termination? */ lua_pushboolean(L, 1); else @@ -270,11 +298,12 @@ LUALIB_API int luaL_execresult (lua_State *L, int stat) { */ LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { - luaL_getmetatable(L, tname); /* try to get metatable */ - if (!lua_isnil(L, -1)) /* name already in use? */ + if (luaL_getmetatable(L, tname) != LUA_TNIL) /* name already in use? */ return 0; /* leave previous value on top, but return 0 */ lua_pop(L, 1); - lua_newtable(L); /* create metatable */ + lua_createtable(L, 0, 2); /* create metatable */ + lua_pushstring(L, tname); + lua_setfield(L, -2, "__name"); /* metatable.__name = tname */ lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ return 1; @@ -317,23 +346,28 @@ LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { ** ======================================================= */ -LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def, +LUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def, const char *const lst[]) { - const char *name = (def) ? luaL_optstring(L, narg, def) : - luaL_checkstring(L, narg); + const char *name = (def) ? luaL_optstring(L, arg, def) : + luaL_checkstring(L, arg); int i; for (i=0; lst[i]; i++) if (strcmp(lst[i], name) == 0) return i; - return luaL_argerror(L, narg, - lua_pushfstring(L, "invalid option " LUA_QS, name)); + return luaL_argerror(L, arg, + lua_pushfstring(L, "invalid option '%s'", name)); } +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { - /* keep some extra space to run error routines, if needed */ - const int extra = LUA_MINSTACK; - if (!lua_checkstack(L, space + extra)) { + if (!lua_checkstack(L, space)) { if (msg) luaL_error(L, "stack overflow (%s)", msg); else @@ -342,77 +376,71 @@ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { } -LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) { - if (lua_type(L, narg) != t) - tag_error(L, narg, t); +LUALIB_API void luaL_checktype (lua_State *L, int arg, int t) { + if (lua_type(L, arg) != t) + tag_error(L, arg, t); } -LUALIB_API void luaL_checkany (lua_State *L, int narg) { - if (lua_type(L, narg) == LUA_TNONE) - luaL_argerror(L, narg, "value expected"); +LUALIB_API void luaL_checkany (lua_State *L, int arg) { + if (lua_type(L, arg) == LUA_TNONE) + luaL_argerror(L, arg, "value expected"); } -LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) { - const char *s = lua_tolstring(L, narg, len); - if (!s) tag_error(L, narg, LUA_TSTRING); +LUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) { + const char *s = lua_tolstring(L, arg, len); + if (!s) tag_error(L, arg, LUA_TSTRING); return s; } -LUALIB_API const char *luaL_optlstring (lua_State *L, int narg, +LUALIB_API const char *luaL_optlstring (lua_State *L, int arg, const char *def, size_t *len) { - if (lua_isnoneornil(L, narg)) { + if (lua_isnoneornil(L, arg)) { if (len) *len = (def ? strlen(def) : 0); return def; } - else return luaL_checklstring(L, narg, len); + else return luaL_checklstring(L, arg, len); } -LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) { +LUALIB_API lua_Number luaL_checknumber (lua_State *L, int arg) { int isnum; - lua_Number d = lua_tonumberx(L, narg, &isnum); + lua_Number d = lua_tonumberx(L, arg, &isnum); if (!isnum) - tag_error(L, narg, LUA_TNUMBER); + tag_error(L, arg, LUA_TNUMBER); return d; } -LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) { - return luaL_opt(L, luaL_checknumber, narg, def); +LUALIB_API lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number def) { + return luaL_opt(L, luaL_checknumber, arg, def); } -LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) { - int isnum; - lua_Integer d = lua_tointegerx(L, narg, &isnum); - if (!isnum) - tag_error(L, narg, LUA_TNUMBER); - return d; +static void interror (lua_State *L, int arg) { + if (lua_isnumber(L, arg)) + luaL_argerror(L, arg, "number has no integer representation"); + else + tag_error(L, arg, LUA_TNUMBER); } -LUALIB_API lua_Unsigned luaL_checkunsigned (lua_State *L, int narg) { +LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int arg) { int isnum; - lua_Unsigned d = lua_tounsignedx(L, narg, &isnum); - if (!isnum) - tag_error(L, narg, LUA_TNUMBER); + lua_Integer d = lua_tointegerx(L, arg, &isnum); + if (!isnum) { + interror(L, arg); + } return d; } -LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg, +LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg, lua_Integer def) { - return luaL_opt(L, luaL_checkinteger, narg, def); -} - - -LUALIB_API lua_Unsigned luaL_optunsigned (lua_State *L, int narg, - lua_Unsigned def) { - return luaL_opt(L, luaL_checkunsigned, narg, def); + return luaL_opt(L, luaL_checkinteger, arg, def); } /* }====================================================== */ @@ -424,6 +452,47 @@ LUALIB_API lua_Unsigned luaL_optunsigned (lua_State *L, int narg, ** ======================================================= */ +/* userdata to box arbitrary data */ +typedef struct UBox { + void *box; + size_t bsize; +} UBox; + + +static void *resizebox (lua_State *L, int idx, size_t newsize) { + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + UBox *box = (UBox *)lua_touserdata(L, idx); + void *temp = allocf(ud, box->box, box->bsize, newsize); + if (temp == NULL && newsize > 0) { /* allocation error? */ + resizebox(L, idx, 0); /* free buffer */ + luaL_error(L, "not enough memory for buffer allocation"); + } + box->box = temp; + box->bsize = newsize; + return temp; +} + + +static int boxgc (lua_State *L) { + resizebox(L, 1, 0); + return 0; +} + + +static void *newbox (lua_State *L, size_t newsize) { + UBox *box = (UBox *)lua_newuserdata(L, sizeof(UBox)); + box->box = NULL; + box->bsize = 0; + if (luaL_newmetatable(L, "LUABOX")) { /* creating metatable? */ + lua_pushcfunction(L, boxgc); + lua_setfield(L, -2, "__gc"); /* metatable.__gc = boxgc */ + } + lua_setmetatable(L, -2); + return resizebox(L, -1, newsize); +} + + /* ** check whether buffer is using a userdata on the stack as a temporary ** buffer @@ -444,11 +513,12 @@ LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { if (newsize < B->n || newsize - B->n < sz) luaL_error(L, "buffer too large"); /* create larger buffer */ - newbuff = (char *)lua_newuserdata(L, newsize * sizeof(char)); - /* move content to new buffer */ - memcpy(newbuff, B->b, B->n * sizeof(char)); if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + newbuff = (char *)resizebox(L, -1, newsize); + else { /* no buffer yet */ + newbuff = (char *)newbox(L, newsize); + memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */ + } B->b = newbuff; B->size = newsize; } @@ -457,9 +527,11 @@ LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { - char *b = luaL_prepbuffsize(B, l); - memcpy(b, s, l * sizeof(char)); - luaL_addsize(B, l); + if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */ + char *b = luaL_prepbuffsize(B, l); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); + } } @@ -471,8 +543,10 @@ LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { LUALIB_API void luaL_pushresult (luaL_Buffer *B) { lua_State *L = B->L; lua_pushlstring(L, B->b, B->n); - if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + if (buffonstack(B)) { + resizebox(L, -2, 0); /* delete old buffer */ + lua_remove(L, -2); /* remove its header from the stack */ + } } @@ -523,7 +597,7 @@ LUALIB_API int luaL_ref (lua_State *L, int t) { int ref; if (lua_isnil(L, -1)) { lua_pop(L, 1); /* remove from stack */ - return LUA_REFNIL; /* `nil' has a unique fixed reference */ + return LUA_REFNIL; /* 'nil' has a unique fixed reference */ } t = lua_absindex(L, t); lua_rawgeti(L, t, freelist); /* get first free element */ @@ -562,7 +636,7 @@ LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { typedef struct LoadF { int n; /* number of pre-read characters */ FILE *f; /* file being read */ - char buff[LUAL_BUFFERSIZE]; /* area for reading file */ + char buff[BUFSIZ]; /* area for reading file */ } LoadF; @@ -594,7 +668,7 @@ static int errfile (lua_State *L, const char *what, int fnameindex) { static int skipBOM (LoadF *lf) { - const char *p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ + const char *p = "\xEF\xBB\xBF"; /* UTF-8 BOM mark */ int c; lf->n = 0; do { @@ -619,7 +693,7 @@ static int skipcomment (LoadF *lf, int *cp) { if (c == '#') { /* first line is a comment (Unix exec. file)? */ do { /* skip first line */ c = getc(lf->f); - } while (c != EOF && c != '\n') ; + } while (c != EOF && c != '\n'); *cp = getc(lf->f); /* skip end-of-line, if present */ return 1; /* there was a comment */ } @@ -655,7 +729,7 @@ LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, readstatus = ferror(lf.f); if (filename) fclose(lf.f); /* close file (even in case of errors) */ if (readstatus) { - lua_settop(L, fnameindex); /* ignore results from `lua_load' */ + lua_settop(L, fnameindex); /* ignore results from 'lua_load' */ return errfile(L, "read", fnameindex); } lua_remove(L, fnameindex); @@ -698,23 +772,23 @@ LUALIB_API int luaL_loadstring (lua_State *L, const char *s) { LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { if (!lua_getmetatable(L, obj)) /* no metatable? */ - return 0; - lua_pushstring(L, event); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) { - lua_pop(L, 2); /* remove metatable and metafield */ - return 0; - } + return LUA_TNIL; else { - lua_remove(L, -2); /* remove only metatable */ - return 1; + int tt; + lua_pushstring(L, event); + tt = lua_rawget(L, -2); + if (tt == LUA_TNIL) /* is metafield nil? */ + lua_pop(L, 2); /* remove metatable and metafield */ + else + lua_remove(L, -2); /* remove only metatable */ + return tt; /* return metafield type */ } } LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { obj = lua_absindex(L, obj); - if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ + if (luaL_getmetafield(L, obj, event) == LUA_TNIL) /* no metafield? */ return 0; lua_pushvalue(L, obj); lua_call(L, 1, 1); @@ -722,13 +796,13 @@ LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { } -LUALIB_API int luaL_len (lua_State *L, int idx) { - int l; +LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { + lua_Integer l; int isnum; lua_len(L, idx); - l = (int)lua_tointegerx(L, -1, &isnum); + l = lua_tointegerx(L, -1, &isnum); if (!isnum) - luaL_error(L, "object length is not a number"); + luaL_error(L, "object length is not an integer"); lua_pop(L, 1); /* remove object */ return l; } @@ -737,7 +811,13 @@ LUALIB_API int luaL_len (lua_State *L, int idx) { LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */ switch (lua_type(L, idx)) { - case LUA_TNUMBER: + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) + lua_pushfstring(L, "%I", lua_tointeger(L, idx)); + else + lua_pushfstring(L, "%f", lua_tonumber(L, idx)); + break; + } case LUA_TSTRING: lua_pushvalue(L, idx); break; @@ -772,8 +852,7 @@ static const char *luaL_findtable (lua_State *L, int idx, e = strchr(fname, '.'); if (e == NULL) e = fname + strlen(fname); lua_pushlstring(L, fname, e - fname); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) { /* no such field? */ + if (lua_rawget(L, -2) == LUA_TNIL) { /* no such field? */ lua_pop(L, 1); /* remove this nil */ lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ lua_pushlstring(L, fname, e - fname); @@ -810,13 +889,12 @@ static int libsize (const luaL_Reg *l) { LUALIB_API void luaL_pushmodule (lua_State *L, const char *modname, int sizehint) { luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); /* get _LOADED table */ - lua_getfield(L, -1, modname); /* get _LOADED[modname] */ - if (!lua_istable(L, -1)) { /* not found? */ + if (lua_getfield(L, -1, modname) != LUA_TTABLE) { /* no _LOADED[modname]? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ lua_pushglobaltable(L); if (luaL_findtable(L, 0, modname, sizehint) != NULL) - luaL_error(L, "name conflict for module " LUA_QS, modname); + luaL_error(L, "name conflict for module '%s'", modname); lua_pushvalue(L, -1); lua_setfield(L, -3, modname); /* _LOADED[modname] = new table */ } @@ -846,7 +924,6 @@ LUALIB_API void luaL_openlib (lua_State *L, const char *libname, ** Returns with only the table at the stack. */ LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { - luaL_checkversion(L); luaL_checkstack(L, nup, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; @@ -864,8 +941,8 @@ LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { ** into the stack */ LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { - lua_getfield(L, idx, fname); - if (lua_istable(L, -1)) return 1; /* table already there */ + if (lua_getfield(L, idx, fname) == LUA_TTABLE) + return 1; /* table already there */ else { lua_pop(L, 1); /* remove previous result */ idx = lua_absindex(L, idx); @@ -878,22 +955,26 @@ LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { /* -** stripped-down 'require'. Calls 'openf' to open a module, -** registers the result in 'package.loaded' table and, if 'glb' -** is true, also registers the result in the global table. +** Stripped-down 'require': After checking "loaded" table, calls 'openf' +** to open a module, registers the result in 'package.loaded' table and, +** if 'glb' is true, also registers the result in the global table. ** Leaves resulting module on the top. */ LUALIB_API void luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int glb) { - lua_pushcfunction(L, openf); - lua_pushstring(L, modname); /* argument to open function */ - lua_call(L, 1, 1); /* open module */ luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_pushvalue(L, -2); /* make copy of module (call result) */ - lua_setfield(L, -2, modname); /* _LOADED[modname] = module */ - lua_pop(L, 1); /* remove _LOADED table */ + lua_getfield(L, -1, modname); /* _LOADED[modname] */ + if (!lua_toboolean(L, -1)) { /* package not already loaded? */ + lua_pop(L, 1); /* remove field */ + lua_pushcfunction(L, openf); + lua_pushstring(L, modname); /* argument to open function */ + lua_call(L, 1, 1); /* call 'openf' to open module */ + lua_pushvalue(L, -1); /* make copy of module (call result) */ + lua_setfield(L, -3, modname); /* _LOADED[modname] = module */ + } + lua_remove(L, -2); /* remove _LOADED table */ if (glb) { - lua_pushvalue(L, -1); /* copy of 'mod' */ + lua_pushvalue(L, -1); /* copy of module */ lua_setglobal(L, modname); /* _G[modname] = module */ } } @@ -908,7 +989,7 @@ LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, while ((wild = strstr(s, p)) != NULL) { luaL_addlstring(&b, s, wild - s); /* push prefix */ luaL_addstring(&b, r); /* push replacement in place of pattern */ - s = wild + l; /* continue after `p' */ + s = wild + l; /* continue after 'p' */ } luaL_addstring(&b, s); /* push last suffix */ luaL_pushresult(&b); @@ -928,8 +1009,8 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { static int panic (lua_State *L) { - luai_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", - lua_tostring(L, -1)); + lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", + lua_tostring(L, -1)); return 0; /* return to Lua to abort */ } @@ -941,19 +1022,14 @@ LUALIB_API lua_State *luaL_newstate (void) { } -LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver) { +LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) { const lua_Number *v = lua_version(L); + if (sz != LUAL_NUMSIZES) /* check numeric types */ + luaL_error(L, "core and library have incompatible numeric types"); if (v != lua_version(NULL)) luaL_error(L, "multiple Lua VMs detected"); else if (*v != ver) luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", ver, *v); - /* check conversions number -> integer types */ - lua_pushnumber(L, -(lua_Number)0x1234); - if (lua_tointeger(L, -1) != -0x1234 || - lua_tounsigned(L, -1) != (lua_Unsigned)-0x1234) - luaL_error(L, "bad conversion number->int;" - " must recompile Lua with proper settings"); - lua_pop(L, 1); } diff --git a/depends/lua/src/lbaselib.c b/depends/lua/src/lbaselib.c index 5255b3cd9..d481c4e1e 100644 --- a/depends/lua/src/lbaselib.c +++ b/depends/lua/src/lbaselib.c @@ -1,9 +1,13 @@ /* -** $Id: lbaselib.c,v 1.276.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lbaselib.c,v 1.313 2016/04/11 19:18:40 roberto Exp $ ** Basic library ** See Copyright Notice in lua.h */ +#define lbaselib_c +#define LUA_LIB + +#include "lprefix.h" #include @@ -11,9 +15,6 @@ #include #include -#define lbaselib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -32,65 +33,77 @@ static int luaB_print (lua_State *L) { lua_call(L, 1, 1); s = lua_tolstring(L, -1, &l); /* get result */ if (s == NULL) - return luaL_error(L, - LUA_QL("tostring") " must return a string to " LUA_QL("print")); - if (i>1) luai_writestring("\t", 1); - luai_writestring(s, l); + return luaL_error(L, "'tostring' must return a string to 'print'"); + if (i>1) lua_writestring("\t", 1); + lua_writestring(s, l); lua_pop(L, 1); /* pop result */ } - luai_writeline(); + lua_writeline(); return 0; } #define SPACECHARS " \f\n\r\t\v" +static const char *b_str2int (const char *s, int base, lua_Integer *pn) { + lua_Unsigned n = 0; + int neg = 0; + s += strspn(s, SPACECHARS); /* skip initial spaces */ + if (*s == '-') { s++; neg = 1; } /* handle signal */ + else if (*s == '+') s++; + if (!isalnum((unsigned char)*s)) /* no digit? */ + return NULL; + do { + int digit = (isdigit((unsigned char)*s)) ? *s - '0' + : (toupper((unsigned char)*s) - 'A') + 10; + if (digit >= base) return NULL; /* invalid numeral */ + n = n * base + digit; + s++; + } while (isalnum((unsigned char)*s)); + s += strspn(s, SPACECHARS); /* skip trailing spaces */ + *pn = (lua_Integer)((neg) ? (0u - n) : n); + return s; +} + + static int luaB_tonumber (lua_State *L) { - if (lua_isnoneornil(L, 2)) { /* standard conversion */ - int isnum; - lua_Number n = lua_tonumberx(L, 1, &isnum); - if (isnum) { - lua_pushnumber(L, n); - return 1; - } /* else not a number; must be something */ + if (lua_isnoneornil(L, 2)) { /* standard conversion? */ luaL_checkany(L, 1); + if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */ + lua_settop(L, 1); /* yes; return it */ + return 1; + } + else { + size_t l; + const char *s = lua_tolstring(L, 1, &l); + if (s != NULL && lua_stringtonumber(L, s) == l + 1) + return 1; /* successful conversion to number */ + /* else not a number */ + } } else { size_t l; - const char *s = luaL_checklstring(L, 1, &l); - const char *e = s + l; /* end point for 's' */ - int base = luaL_checkint(L, 2); - int neg = 0; + const char *s; + lua_Integer n = 0; /* to avoid warnings */ + lua_Integer base = luaL_checkinteger(L, 2); + luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */ + s = lua_tolstring(L, 1, &l); luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); - s += strspn(s, SPACECHARS); /* skip initial spaces */ - if (*s == '-') { s++; neg = 1; } /* handle signal */ - else if (*s == '+') s++; - if (isalnum((unsigned char)*s)) { - lua_Number n = 0; - do { - int digit = (isdigit((unsigned char)*s)) ? *s - '0' - : toupper((unsigned char)*s) - 'A' + 10; - if (digit >= base) break; /* invalid numeral; force a fail */ - n = n * (lua_Number)base + (lua_Number)digit; - s++; - } while (isalnum((unsigned char)*s)); - s += strspn(s, SPACECHARS); /* skip trailing spaces */ - if (s == e) { /* no invalid trailing characters? */ - lua_pushnumber(L, (neg) ? -n : n); - return 1; - } /* else not a number */ + if (b_str2int(s, (int)base, &n) == s + l) { + lua_pushinteger(L, n); + return 1; } /* else not a number */ - } + } /* else not a number */ lua_pushnil(L); /* not a number */ return 1; } static int luaB_error (lua_State *L) { - int level = luaL_optint(L, 2, 1); + int level = (int)luaL_optinteger(L, 2, 1); lua_settop(L, 1); - if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ - luaL_where(L, level); + if (lua_type(L, 1) == LUA_TSTRING && level > 0) { + luaL_where(L, level); /* add extra information */ lua_pushvalue(L, 1); lua_concat(L, 2); } @@ -114,7 +127,7 @@ static int luaB_setmetatable (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table expected"); - if (luaL_getmetafield(L, 1, "__metatable")) + if (luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL) return luaL_error(L, "cannot change a protected metatable"); lua_settop(L, 2); lua_setmetatable(L, 1); @@ -160,19 +173,18 @@ static int luaB_rawset (lua_State *L) { static int luaB_collectgarbage (lua_State *L) { static const char *const opts[] = {"stop", "restart", "collect", "count", "step", "setpause", "setstepmul", - "setmajorinc", "isrunning", "generational", "incremental", NULL}; + "isrunning", NULL}; static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, - LUA_GCSETMAJORINC, LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC}; + LUA_GCISRUNNING}; int o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; - int ex = luaL_optint(L, 2, 0); + int ex = (int)luaL_optinteger(L, 2, 0); int res = lua_gc(L, o, ex); switch (o) { case LUA_GCCOUNT: { int b = lua_gc(L, LUA_GCCOUNTB, 0); - lua_pushnumber(L, res + ((lua_Number)b/1024)); - lua_pushinteger(L, b); - return 2; + lua_pushnumber(L, (lua_Number)res + ((lua_Number)b/1024)); + return 1; } case LUA_GCSTEP: case LUA_GCISRUNNING: { lua_pushboolean(L, res); @@ -187,15 +199,16 @@ static int luaB_collectgarbage (lua_State *L) { static int luaB_type (lua_State *L) { - luaL_checkany(L, 1); - lua_pushstring(L, luaL_typename(L, 1)); + int t = lua_type(L, 1); + luaL_argcheck(L, t != LUA_TNONE, 1, "value expected"); + lua_pushstring(L, lua_typename(L, t)); return 1; } static int pairsmeta (lua_State *L, const char *method, int iszero, lua_CFunction iter) { - if (!luaL_getmetafield(L, 1, method)) { /* no metamethod? */ + if (luaL_getmetafield(L, 1, method) == LUA_TNIL) { /* no metamethod? */ luaL_checktype(L, 1, LUA_TTABLE); /* argument must be a table */ lua_pushcfunction(L, iter); /* will return generator, */ lua_pushvalue(L, 1); /* state, */ @@ -227,18 +240,30 @@ static int luaB_pairs (lua_State *L) { } +/* +** Traversal function for 'ipairs' +*/ static int ipairsaux (lua_State *L) { - int i = luaL_checkint(L, 2); - luaL_checktype(L, 1, LUA_TTABLE); - i++; /* next value */ + lua_Integer i = luaL_checkinteger(L, 2) + 1; lua_pushinteger(L, i); - lua_rawgeti(L, 1, i); - return (lua_isnil(L, -1)) ? 1 : 2; + return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; } +/* +** 'ipairs' function. Returns 'ipairsaux', given "table", 0. +** (The given "table" may not be a table.) +*/ static int luaB_ipairs (lua_State *L) { +#if defined(LUA_COMPAT_IPAIRS) return pairsmeta(L, "__ipairs", 1, ipairsaux); +#else + luaL_checkany(L, 1); + lua_pushcfunction(L, ipairsaux); /* iteration function */ + lua_pushvalue(L, 1); /* state */ + lua_pushinteger(L, 0); /* initial value */ + return 3; +#endif } @@ -284,7 +309,7 @@ static int luaB_loadfile (lua_State *L) { /* -** Reader for generic `load' function: `lua_load' uses the +** Reader for generic 'load' function: 'lua_load' uses the ** stack for internal stuff, so the reader cannot change the ** stack top. Instead, it keeps its resulting string in a ** reserved slot inside the stack. @@ -328,7 +353,8 @@ static int luaB_load (lua_State *L) { /* }====================================================== */ -static int dofilecont (lua_State *L) { +static int dofilecont (lua_State *L, int d1, lua_KContext d2) { + (void)d1; (void)d2; /* only to match 'lua_Kfunction' prototype */ return lua_gettop(L) - 1; } @@ -339,14 +365,20 @@ static int luaB_dofile (lua_State *L) { if (luaL_loadfile(L, fname) != LUA_OK) return lua_error(L); lua_callk(L, 0, LUA_MULTRET, 0, dofilecont); - return dofilecont(L); + return dofilecont(L, 0, 0); } static int luaB_assert (lua_State *L) { - if (!lua_toboolean(L, 1)) - return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); - return lua_gettop(L); + if (lua_toboolean(L, 1)) /* condition is true? */ + return lua_gettop(L); /* return all arguments */ + else { /* error */ + luaL_checkany(L, 1); /* there must be a condition */ + lua_remove(L, 1); /* remove it */ + lua_pushliteral(L, "assertion failed!"); /* default message */ + lua_settop(L, 1); /* leave only message (default if no other one) */ + return luaB_error(L); /* call 'error' */ + } } @@ -357,53 +389,57 @@ static int luaB_select (lua_State *L) { return 1; } else { - int i = luaL_checkint(L, 1); + lua_Integer i = luaL_checkinteger(L, 1); if (i < 0) i = n + i; else if (i > n) i = n; luaL_argcheck(L, 1 <= i, 1, "index out of range"); - return n - i; + return n - (int)i; } } -static int finishpcall (lua_State *L, int status) { - if (!lua_checkstack(L, 1)) { /* no space for extra boolean? */ - lua_settop(L, 0); /* create space for return values */ - lua_pushboolean(L, 0); - lua_pushstring(L, "stack overflow"); +/* +** Continuation function for 'pcall' and 'xpcall'. Both functions +** already pushed a 'true' before doing the call, so in case of success +** 'finishpcall' only has to return everything in the stack minus +** 'extra' values (where 'extra' is exactly the number of items to be +** ignored). +*/ +static int finishpcall (lua_State *L, int status, lua_KContext extra) { + if (status != LUA_OK && status != LUA_YIELD) { /* error? */ + lua_pushboolean(L, 0); /* first result (false) */ + lua_pushvalue(L, -2); /* error message */ return 2; /* return false, msg */ } - lua_pushboolean(L, status); /* first result (status) */ - lua_replace(L, 1); /* put first result in first slot */ - return lua_gettop(L); -} - - -static int pcallcont (lua_State *L) { - int status = lua_getctx(L, NULL); - return finishpcall(L, (status == LUA_YIELD)); + else + return lua_gettop(L) - (int)extra; /* return all results */ } static int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); - lua_pushnil(L); - lua_insert(L, 1); /* create space for status result */ - status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, pcallcont); - return finishpcall(L, (status == LUA_OK)); + lua_pushboolean(L, 1); /* first result if no errors */ + lua_insert(L, 1); /* put it in place */ + status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); + return finishpcall(L, status, 0); } +/* +** Do a protected call with error handling. After 'lua_rotate', the +** stack will have ; so, the function passes +** 2 to 'finishpcall' to skip the 2 first values when returning results. +*/ static int luaB_xpcall (lua_State *L) { int status; int n = lua_gettop(L); - luaL_argcheck(L, n >= 2, 2, "value expected"); - lua_pushvalue(L, 1); /* exchange function... */ - lua_copy(L, 2, 1); /* ...and error handler */ - lua_replace(L, 2); - status = lua_pcallk(L, n - 2, LUA_MULTRET, 1, 0, pcallcont); - return finishpcall(L, (status == LUA_OK)); + luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ + lua_pushboolean(L, 1); /* first result */ + lua_pushvalue(L, 1); /* function */ + lua_rotate(L, 3, 2); /* move them below function's arguments */ + status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall); + return finishpcall(L, status, 2); } @@ -440,19 +476,23 @@ static const luaL_Reg base_funcs[] = { {"tostring", luaB_tostring}, {"type", luaB_type}, {"xpcall", luaB_xpcall}, + /* placeholders */ + {"_G", NULL}, + {"_VERSION", NULL}, {NULL, NULL} }; LUAMOD_API int luaopen_base (lua_State *L) { - /* set global _G */ - lua_pushglobaltable(L); - lua_pushglobaltable(L); - lua_setfield(L, -2, "_G"); /* open lib into global table */ + lua_pushglobaltable(L); luaL_setfuncs(L, base_funcs, 0); + /* set global _G */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "_G"); + /* set global _VERSION */ lua_pushliteral(L, LUA_VERSION); - lua_setfield(L, -2, "_VERSION"); /* set global _VERSION */ + lua_setfield(L, -2, "_VERSION"); return 1; } diff --git a/depends/lua/src/lbitlib.c b/depends/lua/src/lbitlib.c index 31c7b66f1..1cb1d5b93 100644 --- a/depends/lua/src/lbitlib.c +++ b/depends/lua/src/lbitlib.c @@ -1,5 +1,5 @@ /* -** $Id: lbitlib.c,v 1.18.1.2 2013/07/09 18:01:41 roberto Exp $ +** $Id: lbitlib.c,v 1.30 2015/11/11 19:08:09 roberto Exp $ ** Standard library for bitwise operations ** See Copyright Notice in lua.h */ @@ -7,20 +7,36 @@ #define lbitlib_c #define LUA_LIB +#include "lprefix.h" + + #include "lua.h" #include "lauxlib.h" #include "lualib.h" +#if defined(LUA_COMPAT_BITLIB) /* { */ + + +#define pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define checkunsigned(L,i) ((lua_Unsigned)luaL_checkinteger(L,i)) + + /* number of bits to consider in a number */ #if !defined(LUA_NBITS) #define LUA_NBITS 32 #endif +/* +** a lua_Unsigned with its first LUA_NBITS bits equal to 1. (Shift must +** be made in two parts to avoid problems when LUA_NBITS is equal to the +** number of bits in a lua_Unsigned.) +*/ #define ALLONES (~(((~(lua_Unsigned)0) << (LUA_NBITS - 1)) << 1)) + /* macro to trim extra bits */ #define trim(x) ((x) & ALLONES) @@ -29,28 +45,25 @@ #define mask(n) (~((ALLONES << 1) << ((n) - 1))) -typedef lua_Unsigned b_uint; - - -static b_uint andaux (lua_State *L) { +static lua_Unsigned andaux (lua_State *L) { int i, n = lua_gettop(L); - b_uint r = ~(b_uint)0; + lua_Unsigned r = ~(lua_Unsigned)0; for (i = 1; i <= n; i++) - r &= luaL_checkunsigned(L, i); + r &= checkunsigned(L, i); return trim(r); } static int b_and (lua_State *L) { - b_uint r = andaux(L); - lua_pushunsigned(L, r); + lua_Unsigned r = andaux(L); + pushunsigned(L, r); return 1; } static int b_test (lua_State *L) { - b_uint r = andaux(L); + lua_Unsigned r = andaux(L); lua_pushboolean(L, r != 0); return 1; } @@ -58,32 +71,32 @@ static int b_test (lua_State *L) { static int b_or (lua_State *L) { int i, n = lua_gettop(L); - b_uint r = 0; + lua_Unsigned r = 0; for (i = 1; i <= n; i++) - r |= luaL_checkunsigned(L, i); - lua_pushunsigned(L, trim(r)); + r |= checkunsigned(L, i); + pushunsigned(L, trim(r)); return 1; } static int b_xor (lua_State *L) { int i, n = lua_gettop(L); - b_uint r = 0; + lua_Unsigned r = 0; for (i = 1; i <= n; i++) - r ^= luaL_checkunsigned(L, i); - lua_pushunsigned(L, trim(r)); + r ^= checkunsigned(L, i); + pushunsigned(L, trim(r)); return 1; } static int b_not (lua_State *L) { - b_uint r = ~luaL_checkunsigned(L, 1); - lua_pushunsigned(L, trim(r)); + lua_Unsigned r = ~checkunsigned(L, 1); + pushunsigned(L, trim(r)); return 1; } -static int b_shift (lua_State *L, b_uint r, int i) { +static int b_shift (lua_State *L, lua_Unsigned r, lua_Integer i) { if (i < 0) { /* shift right? */ i = -i; r = trim(r); @@ -95,54 +108,54 @@ static int b_shift (lua_State *L, b_uint r, int i) { else r <<= i; r = trim(r); } - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } static int b_lshift (lua_State *L) { - return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkint(L, 2)); + return b_shift(L, checkunsigned(L, 1), luaL_checkinteger(L, 2)); } static int b_rshift (lua_State *L) { - return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkint(L, 2)); + return b_shift(L, checkunsigned(L, 1), -luaL_checkinteger(L, 2)); } static int b_arshift (lua_State *L) { - b_uint r = luaL_checkunsigned(L, 1); - int i = luaL_checkint(L, 2); - if (i < 0 || !(r & ((b_uint)1 << (LUA_NBITS - 1)))) + lua_Unsigned r = checkunsigned(L, 1); + lua_Integer i = luaL_checkinteger(L, 2); + if (i < 0 || !(r & ((lua_Unsigned)1 << (LUA_NBITS - 1)))) return b_shift(L, r, -i); else { /* arithmetic shift for 'negative' number */ if (i >= LUA_NBITS) r = ALLONES; else - r = trim((r >> i) | ~(~(b_uint)0 >> i)); /* add signal bit */ - lua_pushunsigned(L, r); + r = trim((r >> i) | ~(trim(~(lua_Unsigned)0) >> i)); /* add signal bit */ + pushunsigned(L, r); return 1; } } -static int b_rot (lua_State *L, int i) { - b_uint r = luaL_checkunsigned(L, 1); - i &= (LUA_NBITS - 1); /* i = i % NBITS */ +static int b_rot (lua_State *L, lua_Integer d) { + lua_Unsigned r = checkunsigned(L, 1); + int i = d & (LUA_NBITS - 1); /* i = d % NBITS */ r = trim(r); if (i != 0) /* avoid undefined shift of LUA_NBITS when i == 0 */ r = (r << i) | (r >> (LUA_NBITS - i)); - lua_pushunsigned(L, trim(r)); + pushunsigned(L, trim(r)); return 1; } static int b_lrot (lua_State *L) { - return b_rot(L, luaL_checkint(L, 2)); + return b_rot(L, luaL_checkinteger(L, 2)); } static int b_rrot (lua_State *L) { - return b_rot(L, -luaL_checkint(L, 2)); + return b_rot(L, -luaL_checkinteger(L, 2)); } @@ -153,36 +166,35 @@ static int b_rrot (lua_State *L) { ** 'width' being used uninitialized.) */ static int fieldargs (lua_State *L, int farg, int *width) { - int f = luaL_checkint(L, farg); - int w = luaL_optint(L, farg + 1, 1); + lua_Integer f = luaL_checkinteger(L, farg); + lua_Integer w = luaL_optinteger(L, farg + 1, 1); luaL_argcheck(L, 0 <= f, farg, "field cannot be negative"); luaL_argcheck(L, 0 < w, farg + 1, "width must be positive"); if (f + w > LUA_NBITS) luaL_error(L, "trying to access non-existent bits"); - *width = w; - return f; + *width = (int)w; + return (int)f; } static int b_extract (lua_State *L) { int w; - b_uint r = luaL_checkunsigned(L, 1); + lua_Unsigned r = trim(checkunsigned(L, 1)); int f = fieldargs(L, 2, &w); r = (r >> f) & mask(w); - lua_pushunsigned(L, r); + pushunsigned(L, r); return 1; } static int b_replace (lua_State *L) { int w; - b_uint r = luaL_checkunsigned(L, 1); - b_uint v = luaL_checkunsigned(L, 2); + lua_Unsigned r = trim(checkunsigned(L, 1)); + lua_Unsigned v = trim(checkunsigned(L, 2)); int f = fieldargs(L, 3, &w); - int m = mask(w); - v &= m; /* erase bits outside given width */ - r = (r & ~(m << f)) | (v << f); - lua_pushunsigned(L, r); + lua_Unsigned m = mask(w); + r = (r & ~(m << f)) | ((v & m) << f); + pushunsigned(L, r); return 1; } @@ -210,3 +222,12 @@ LUAMOD_API int luaopen_bit32 (lua_State *L) { return 1; } + +#else /* }{ */ + + +LUAMOD_API int luaopen_bit32 (lua_State *L) { + return luaL_error(L, "library 'bit32' has been deprecated"); +} + +#endif /* } */ diff --git a/depends/lua/src/lcode.c b/depends/lua/src/lcode.c index 820b95c0e..2cd0dd2d5 100644 --- a/depends/lua/src/lcode.c +++ b/depends/lua/src/lcode.c @@ -1,15 +1,18 @@ /* -** $Id: lcode.c,v 2.62.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lcode.c,v 2.109 2016/05/13 19:09:21 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ - -#include - #define lcode_c #define LUA_CORE +#include "lprefix.h" + + +#include +#include + #include "lua.h" #include "lcode.h" @@ -26,21 +29,45 @@ #include "lvm.h" +/* Maximum number of registers in a Lua function (must fit in 8 bits) */ +#define MAXREGS 255 + + #define hasjumps(e) ((e)->t != (e)->f) -static int isnumeral(expdesc *e) { - return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +static int tonumeral(expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a numeral */ + switch (e->k) { + case VKINT: + if (v) setivalue(v, e->u.ival); + return 1; + case VKFLT: + if (v) setfltvalue(v, e->u.nval); + return 1; + default: return 0; + } } +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ void luaK_nil (FuncState *fs, int from, int n) { Instruction *previous; int l = from + n - 1; /* last register to set nil */ if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ previous = &fs->f->code[fs->pc-1]; - if (GET_OPCODE(*previous) == OP_LOADNIL) { - int pfrom = GETARG_A(*previous); + if (GET_OPCODE(*previous) == OP_LOADNIL) { /* previous is LOADNIL? */ + int pfrom = GETARG_A(*previous); /* get previous range */ int pl = pfrom + GETARG_B(*previous); if ((pfrom <= from && from <= pl + 1) || (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ @@ -56,39 +83,86 @@ void luaK_nil (FuncState *fs, int from, int n) { } +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sBx(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + lua_assert(dest != NO_JUMP); + if (abs(offset) > MAXARG_sBx) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_sBx(*jmp, offset); +} + + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; /* nothing to concatenate? */ + else if (*l1 == NO_JUMP) /* no original list? */ + *l1 = l2; /* 'l1' points to 'l2' */ + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); /* last element links to 'l2' */ + } +} + + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). If there are jumps to +** this position (kept in 'jpc'), link them all together so that +** 'patchlistaux' will fix all them directly to the final destination. +*/ int luaK_jump (FuncState *fs) { int jpc = fs->jpc; /* save list of jumps to here */ int j; - fs->jpc = NO_JUMP; + fs->jpc = NO_JUMP; /* no more jumps to here */ j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); luaK_concat(fs, &j, jpc); /* keep them on hold */ return j; } +/* +** Code a 'return' instruction +*/ void luaK_ret (FuncState *fs, int first, int nret) { luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); } +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { luaK_codeABC(fs, op, A, B, C); return luaK_jump(fs); } -static void fixjump (FuncState *fs, int pc, int dest) { - Instruction *jmp = &fs->f->code[pc]; - int offset = dest-(pc+1); - lua_assert(dest != NO_JUMP); - if (abs(offset) > MAXARG_sBx) - luaX_syntaxerror(fs->ls, "control structure too long"); - SETARG_sBx(*jmp, offset); -} - - /* -** returns current `pc' and marks it as a jump target (to avoid wrong +** returns current 'pc' and marks it as a jump target (to avoid wrong ** optimizations with consecutive instructions not in the same basic block). */ int luaK_getlabel (FuncState *fs) { @@ -97,15 +171,11 @@ int luaK_getlabel (FuncState *fs) { } -static int getjump (FuncState *fs, int pc) { - int offset = GETARG_sBx(fs->f->code[pc]); - if (offset == NO_JUMP) /* point to itself represents end of list */ - return NO_JUMP; /* end of list */ - else - return (pc+1)+offset; /* turn offset into absolute position */ -} - - +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ static Instruction *getjumpcontrol (FuncState *fs, int pc) { Instruction *pi = &fs->f->code[pc]; if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) @@ -116,37 +186,41 @@ static Instruction *getjumpcontrol (FuncState *fs, int pc) { /* -** check whether list has any jump that do not produce a value -** (or produce an inverted value) +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) */ -static int need_value (FuncState *fs, int list) { - for (; list != NO_JUMP; list = getjump(fs, list)) { - Instruction i = *getjumpcontrol(fs, list); - if (GET_OPCODE(i) != OP_TESTSET) return 1; - } - return 0; /* not found */ -} - - static int patchtestreg (FuncState *fs, int node, int reg) { Instruction *i = getjumpcontrol(fs, node); if (GET_OPCODE(*i) != OP_TESTSET) return 0; /* cannot patch other instructions */ if (reg != NO_REG && reg != GETARG_B(*i)) SETARG_A(*i, reg); - else /* no register to put value or register already has the value */ + else { + /* no register to put value or register already has the value; + change instruction to simple test */ *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); - + } return 1; } +/* +** Traverse a list of tests ensuring no one produces a value +*/ static void removevalues (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) patchtestreg(fs, list, NO_REG); } +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, int dtarget) { while (list != NO_JUMP) { @@ -160,15 +234,35 @@ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, } +/* +** Ensure all pending jumps to current position are fixed (jumping +** to current position with no values) and reset list of pending +** jumps +*/ static void dischargejpc (FuncState *fs) { patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); fs->jpc = NO_JUMP; } +/* +** Add elements in 'list' to list of pending jumps to "here" +** (current position) +*/ +void luaK_patchtohere (FuncState *fs, int list) { + luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_concat(fs, &fs->jpc, list); +} + + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ void luaK_patchlist (FuncState *fs, int list, int target) { - if (target == fs->pc) - luaK_patchtohere(fs, list); + if (target == fs->pc) /* 'target' is current position? */ + luaK_patchtohere(fs, list); /* add list to pending jumps */ else { lua_assert(target < fs->pc); patchlistaux(fs, list, target, NO_REG, target); @@ -176,42 +270,29 @@ void luaK_patchlist (FuncState *fs, int list, int target) { } -LUAI_FUNC void luaK_patchclose (FuncState *fs, int list, int level) { +/* +** Path all jumps in 'list' to close upvalues up to given 'level' +** (The assertion checks that jumps either were closing nothing +** or were closing higher levels, from inner blocks.) +*/ +void luaK_patchclose (FuncState *fs, int list, int level) { level++; /* argument is +1 to reserve 0 as non-op */ - while (list != NO_JUMP) { - int next = getjump(fs, list); + for (; list != NO_JUMP; list = getjump(fs, list)) { lua_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP && (GETARG_A(fs->f->code[list]) == 0 || GETARG_A(fs->f->code[list]) >= level)); SETARG_A(fs->f->code[list], level); - list = next; - } -} - - -void luaK_patchtohere (FuncState *fs, int list) { - luaK_getlabel(fs); - luaK_concat(fs, &fs->jpc, list); -} - - -void luaK_concat (FuncState *fs, int *l1, int l2) { - if (l2 == NO_JUMP) return; - else if (*l1 == NO_JUMP) - *l1 = l2; - else { - int list = *l1; - int next; - while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ - list = next; - fixjump(fs, list, l2); } } +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ static int luaK_code (FuncState *fs, Instruction i) { Proto *f = fs->f; - dischargejpc(fs); /* `pc' will change */ + dischargejpc(fs); /* 'pc' will change */ /* put new instruction in code array */ luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction, MAX_INT, "opcodes"); @@ -224,6 +305,10 @@ static int luaK_code (FuncState *fs, Instruction i) { } +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { lua_assert(getOpMode(o) == iABC); lua_assert(getBMode(o) != OpArgN || b == 0); @@ -233,6 +318,9 @@ int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { } +/* +** Format and emit an 'iABx' instruction. +*/ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); lua_assert(getCMode(o) == OpArgN); @@ -241,12 +329,20 @@ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { } +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ static int codeextraarg (FuncState *fs, int a) { lua_assert(a <= MAXARG_Ax); return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); } +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ int luaK_codek (FuncState *fs, int reg, int k) { if (k <= MAXARG_Bx) return luaK_codeABx(fs, OP_LOADK, reg, k); @@ -258,22 +354,35 @@ int luaK_codek (FuncState *fs, int reg, int k) { } +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ void luaK_checkstack (FuncState *fs, int n) { int newstack = fs->freereg + n; if (newstack > fs->f->maxstacksize) { - if (newstack >= MAXSTACK) - luaX_syntaxerror(fs->ls, "function or expression too complex"); + if (newstack >= MAXREGS) + luaX_syntaxerror(fs->ls, + "function or expression needs too many registers"); fs->f->maxstacksize = cast_byte(newstack); } } +/* +** Reserve 'n' registers in register stack +*/ void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +) +*/ static void freereg (FuncState *fs, int reg) { if (!ISK(reg) && reg >= fs->nactvar) { fs->freereg--; @@ -282,31 +391,58 @@ static void freereg (FuncState *fs, int reg) { } +/* +** Free register used by expression 'e' (if any) +*/ static void freeexp (FuncState *fs, expdesc *e) { if (e->k == VNONRELOC) freereg(fs, e->u.info); } +/* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { + int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1; + int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1; + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +} + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use scanner's table to cache position of constants in constant list +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +*/ static int addk (FuncState *fs, TValue *key, TValue *v) { lua_State *L = fs->ls->L; - TValue *idx = luaH_set(L, fs->h, key); Proto *f = fs->f; + TValue *idx = luaH_set(L, fs->ls->h, key); /* index scanner table */ int k, oldsize; - if (ttisnumber(idx)) { - lua_Number n = nvalue(idx); - lua_number2int(k, n); - if (luaV_rawequalobj(&f->k[k], v)) - return k; - /* else may be a collision (e.g., between 0.0 and "\0\0\0\0\0\0\0\0"); - go through and create a new entry for this value */ + if (ttisinteger(idx)) { /* is there an index there? */ + k = cast_int(ivalue(idx)); + /* correct value? (warning: must distinguish floats from integers!) */ + if (k < fs->nk && ttype(&f->k[k]) == ttype(v) && + luaV_rawequalobj(&f->k[k], v)) + return k; /* reuse index */ } /* constant not found; create a new entry */ oldsize = f->sizek; k = fs->nk; /* numerical value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ - setnvalue(idx, cast_num(k)); + setivalue(idx, k); luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); setobj(L, &f->k[k], v); @@ -316,94 +452,134 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { } +/* +** Add a string to list of constants and return its index. +*/ int luaK_stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->ls->L, &o, s); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use string itself as key */ } -int luaK_numberK (FuncState *fs, lua_Number r) { - int n; - lua_State *L = fs->ls->L; +/* +** Add an integer to list of constants and return its index. +** Integers use userdata as keys to avoid collision with floats with +** same value; conversion to 'void*' is used only for hashing, so there +** are no "precision" problems. +*/ +int luaK_intK (FuncState *fs, lua_Integer n) { + TValue k, o; + setpvalue(&k, cast(void*, cast(size_t, n))); + setivalue(&o, n); + return addk(fs, &k, &o); +} + +/* +** Add a float to list of constants and return its index. +*/ +static int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; - setnvalue(&o, r); - if (r == 0 || luai_numisnan(NULL, r)) { /* handle -0 and NaN */ - /* use raw representation as key to avoid numeric problems */ - setsvalue(L, L->top++, luaS_newlstr(L, (char *)&r, sizeof(r))); - n = addk(fs, L->top - 1, &o); - L->top--; - } - else - n = addk(fs, &o, &o); /* regular case */ - return n; + setfltvalue(&o, r); + return addk(fs, &o, &o); /* use number itself as key */ } +/* +** Add a boolean to list of constants and return its index. +*/ static int boolK (FuncState *fs, int b) { TValue o; setbvalue(&o, b); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use boolean itself as key */ } +/* +** Add nil to list of constants and return its index. +*/ static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); /* cannot use nil as key; instead use table itself to represent nil */ - sethvalue(fs->ls->L, &k, fs->h); + sethvalue(fs->ls->L, &k, fs->ls->h); return addk(fs, &k, &v); } +/* +** Fix an expression to return the number of results 'nresults'. +** Either 'e' is a multi-ret expression (function call or vararg) +** or 'nresults' is LUA_MULTRET (as any expression can satisfy that). +*/ void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { if (e->k == VCALL) { /* expression is an open function call? */ - SETARG_C(getcode(fs, e), nresults+1); + SETARG_C(getinstruction(fs, e), nresults + 1); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), nresults+1); - SETARG_A(getcode(fs, e), fs->freereg); + Instruction *pc = &getinstruction(fs, e); + SETARG_B(*pc, nresults + 1); + SETARG_A(*pc, fs->freereg); luaK_reserveregs(fs, 1); } + else lua_assert(nresults == LUA_MULTRET); } +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOCABLE (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ void luaK_setoneret (FuncState *fs, expdesc *e) { if (e->k == VCALL) { /* expression is an open function call? */ - e->k = VNONRELOC; - e->u.info = GETARG_A(getcode(fs, e)); + /* already returns 1 value */ + lua_assert(GETARG_C(getinstruction(fs, e)) == 2); + e->k = VNONRELOC; /* result has fixed position */ + e->u.info = GETARG_A(getinstruction(fs, e)); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), 2); + SETARG_B(getinstruction(fs, e), 2); e->k = VRELOCABLE; /* can relocate its simple result */ } } +/* +** Ensure that expression 'e' is not a variable. +*/ void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { - case VLOCAL: { - e->k = VNONRELOC; + case VLOCAL: { /* already in a register */ + e->k = VNONRELOC; /* becomes a non-relocatable value */ break; } - case VUPVAL: { + case VUPVAL: { /* move value to some (pending) register */ e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); e->k = VRELOCABLE; break; } case VINDEXED: { - OpCode op = OP_GETTABUP; /* assume 't' is in an upvalue */ + OpCode op; freereg(fs, e->u.ind.idx); - if (e->u.ind.vt == VLOCAL) { /* 't' is in a register? */ + if (e->u.ind.vt == VLOCAL) { /* is 't' in a register? */ freereg(fs, e->u.ind.t); op = OP_GETTABLE; } + else { + lua_assert(e->u.ind.vt == VUPVAL); + op = OP_GETTABUP; /* 't' is in an upvalue */ + } e->u.info = luaK_codeABC(fs, op, 0, e->u.ind.t, e->u.ind.idx); e->k = VRELOCABLE; break; } - case VVARARG: - case VCALL: { + case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; } @@ -412,12 +588,10 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { } -static int code_label (FuncState *fs, int A, int b, int jump) { - luaK_getlabel(fs); /* those instructions may be jump targets */ - return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); -} - - +/* +** Ensures expression value is in register 'reg' (and therefore +** 'e' will become a non-relocatable expression). +*/ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { @@ -433,13 +607,17 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_codek(fs, reg, e->u.info); break; } - case VKNUM: { + case VKFLT: { luaK_codek(fs, reg, luaK_numberK(fs, e->u.nval)); break; } + case VKINT: { + luaK_codek(fs, reg, luaK_intK(fs, e->u.ival)); + break; + } case VRELOCABLE: { - Instruction *pc = &getcode(fs, e); - SETARG_A(*pc, reg); + Instruction *pc = &getinstruction(fs, e); + SETARG_A(*pc, reg); /* instruction will put result in 'reg' */ break; } case VNONRELOC: { @@ -448,7 +626,7 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { break; } default: { - lua_assert(e->k == VVOID || e->k == VJMP); + lua_assert(e->k == VJMP); return; /* nothing to do... */ } } @@ -457,26 +635,55 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures expression value is in any register. +*/ static void discharge2anyreg (FuncState *fs, expdesc *e) { - if (e->k != VNONRELOC) { - luaK_reserveregs(fs, 1); - discharge2reg(fs, e, fs->freereg-1); + if (e->k != VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs->freereg-1); /* put value there */ } } +static int code_loadbool (FuncState *fs, int A, int b, int jump) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); +} + + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +/* +** Ensures final expression result (including results from its jump +** lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ static void exp2reg (FuncState *fs, expdesc *e, int reg) { discharge2reg(fs, e, reg); - if (e->k == VJMP) - luaK_concat(fs, &e->t, e->u.info); /* put this jump in `t' list */ + if (e->k == VJMP) /* expression itself is a test? */ + luaK_concat(fs, &e->t, e->u.info); /* put this jump in 't' list */ if (hasjumps(e)) { int final; /* position after whole expression */ int p_f = NO_JUMP; /* position of an eventual LOAD false */ int p_t = NO_JUMP; /* position of an eventual LOAD true */ if (need_value(fs, e->t) || need_value(fs, e->f)) { int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); - p_f = code_label(fs, reg, 0, 1); - p_t = code_label(fs, reg, 1, 0); + p_f = code_loadbool(fs, reg, 0, 1); + p_t = code_loadbool(fs, reg, 1, 0); luaK_patchtohere(fs, fj); } final = luaK_getlabel(fs); @@ -489,6 +696,10 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures final expression result (including results from its jump +** lists) is in next available register. +*/ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); freeexp(fs, e); @@ -497,26 +708,39 @@ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result (including results from its jump +** lists) is in some (any) register and return that register. +*/ int luaK_exp2anyreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); - if (e->k == VNONRELOC) { - if (!hasjumps(e)) return e->u.info; /* exp is already in a register */ + if (e->k == VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e->u.info; /* result is already in a register */ if (e->u.info >= fs->nactvar) { /* reg. is not a local? */ - exp2reg(fs, e, e->u.info); /* put value on it */ + exp2reg(fs, e, e->u.info); /* put final result in it */ return e->u.info; } } - luaK_exp2nextreg(fs, e); /* default */ + luaK_exp2nextreg(fs, e); /* otherwise, use next available register */ return e->u.info; } +/* +** Ensures final expression result is either in a register or in an +** upvalue. +*/ void luaK_exp2anyregup (FuncState *fs, expdesc *e) { if (e->k != VUPVAL || hasjumps(e)) luaK_exp2anyreg(fs, e); } +/* +** Ensures final expression result is either in a register or it is +** a constant. +*/ void luaK_exp2val (FuncState *fs, expdesc *e) { if (hasjumps(e)) luaK_exp2anyreg(fs, e); @@ -525,29 +749,26 @@ void luaK_exp2val (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns R/K index. +*/ int luaK_exp2RK (FuncState *fs, expdesc *e) { luaK_exp2val(fs, e); - switch (e->k) { - case VTRUE: - case VFALSE: - case VNIL: { - if (fs->nk <= MAXINDEXRK) { /* constant fits in RK operand? */ - e->u.info = (e->k == VNIL) ? nilK(fs) : boolK(fs, (e->k == VTRUE)); - e->k = VK; - return RKASK(e->u.info); - } - else break; - } - case VKNUM: { - e->u.info = luaK_numberK(fs, e->u.nval); + switch (e->k) { /* move constants to 'k' */ + case VTRUE: e->u.info = boolK(fs, 1); goto vk; + case VFALSE: e->u.info = boolK(fs, 0); goto vk; + case VNIL: e->u.info = nilK(fs); goto vk; + case VKINT: e->u.info = luaK_intK(fs, e->u.ival); goto vk; + case VKFLT: e->u.info = luaK_numberK(fs, e->u.nval); goto vk; + case VK: + vk: e->k = VK; - /* go through */ - } - case VK: { - if (e->u.info <= MAXINDEXRK) /* constant fits in argC? */ + if (e->u.info <= MAXINDEXRK) /* constant fits in 'argC'? */ return RKASK(e->u.info); else break; - } default: break; } /* not a constant in the right range: put it in a register */ @@ -555,11 +776,14 @@ int luaK_exp2RK (FuncState *fs, expdesc *e) { } +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); - exp2reg(fs, ex, var->u.info); + exp2reg(fs, ex, var->u.info); /* compute 'ex' into proper place */ return; } case VUPVAL: { @@ -573,29 +797,32 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { luaK_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); break; } - default: { - lua_assert(0); /* invalid var kind to store */ - break; - } + default: lua_assert(0); /* invalid var kind to store */ } freeexp(fs, ex); } +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int ereg; luaK_exp2anyreg(fs, e); ereg = e->u.info; /* register where 'e' was placed */ freeexp(fs, e); e->u.info = fs->freereg; /* base register for op_self */ - e->k = VNONRELOC; + e->k = VNONRELOC; /* self expression has a fixed register */ luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key)); freeexp(fs, key); } -static void invertjump (FuncState *fs, expdesc *e) { +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +static void negatecondition (FuncState *fs, expdesc *e) { Instruction *pc = getjumpcontrol(fs, e->u.info); lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && GET_OPCODE(*pc) != OP_TEST); @@ -603,9 +830,15 @@ static void invertjump (FuncState *fs, expdesc *e) { } +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ static int jumponcond (FuncState *fs, expdesc *e, int cond) { if (e->k == VRELOCABLE) { - Instruction ie = getcode(fs, e); + Instruction ie = getinstruction(fs, e); if (GET_OPCODE(ie) == OP_NOT) { fs->pc--; /* remove previous OP_NOT */ return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); @@ -618,36 +851,42 @@ static int jumponcond (FuncState *fs, expdesc *e, int cond) { } +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ void luaK_goiftrue (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { - case VJMP: { - invertjump(fs, e); - pc = e->u.info; + case VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e->u.info; /* save jump position */ break; } - case VK: case VKNUM: case VTRUE: { + case VK: case VKFLT: case VKINT: case VTRUE: { pc = NO_JUMP; /* always true; do nothing */ break; } default: { - pc = jumponcond(fs, e, 0); + pc = jumponcond(fs, e, 0); /* jump when false */ break; } } - luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */ - luaK_patchtohere(fs, e->t); + luaK_concat(fs, &e->f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e->t); /* true list jumps to here (to go through) */ e->t = NO_JUMP; } +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ void luaK_goiffalse (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { case VJMP: { - pc = e->u.info; + pc = e->u.info; /* already jump if true */ break; } case VNIL: case VFALSE: { @@ -655,29 +894,32 @@ void luaK_goiffalse (FuncState *fs, expdesc *e) { break; } default: { - pc = jumponcond(fs, e, 1); + pc = jumponcond(fs, e, 1); /* jump if true */ break; } } - luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */ - luaK_patchtohere(fs, e->f); + luaK_concat(fs, &e->t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e->f); /* false list jumps to here (to go through) */ e->f = NO_JUMP; } +/* +** Code 'not e', doing constant folding. +*/ static void codenot (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { - e->k = VTRUE; + e->k = VTRUE; /* true == not nil == not false */ break; } - case VK: case VKNUM: case VTRUE: { - e->k = VFALSE; + case VK: case VKFLT: case VKINT: case VTRUE: { + e->k = VFALSE; /* false == not "x" == not 0.5 == not 1 == not true */ break; } case VJMP: { - invertjump(fs, e); + negatecondition(fs, e); break; } case VRELOCABLE: @@ -688,118 +930,177 @@ static void codenot (FuncState *fs, expdesc *e) { e->k = VRELOCABLE; break; } - default: { - lua_assert(0); /* cannot happen */ - break; - } + default: lua_assert(0); /* cannot happen */ } /* interchange true and false lists */ { int temp = e->f; e->f = e->t; e->t = temp; } - removevalues(fs, e->f); + removevalues(fs, e->f); /* values are useless when negated */ removevalues(fs, e->t); } +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. +*/ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { - lua_assert(!hasjumps(t)); - t->u.ind.t = t->u.info; - t->u.ind.idx = luaK_exp2RK(fs, k); - t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL - : check_exp(vkisinreg(t->k), VLOCAL); + lua_assert(!hasjumps(t) && (vkisinreg(t->k) || t->k == VUPVAL)); + t->u.ind.t = t->u.info; /* register or upvalue index */ + t->u.ind.idx = luaK_exp2RK(fs, k); /* R/K index for key */ + t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL : VLOCAL; t->k = VINDEXED; } -static int constfolding (OpCode op, expdesc *e1, expdesc *e2) { - lua_Number r; - if (!isnumeral(e1) || !isnumeral(e2)) return 0; - if ((op == OP_DIV || op == OP_MOD) && e2->u.nval == 0) - return 0; /* do not attempt to divide by 0 */ - r = luaO_arith(op - OP_ADD + LUA_OPADD, e1->u.nval, e2->u.nval); - e1->u.nval = r; - return 1; +/* +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. +*/ +static int validop (int op, TValue *v1, TValue *v2) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* conversion errors */ + lua_Integer i; + return (tointeger(v1, &i) && tointeger(v2, &i)); + } + case LUA_OPDIV: case LUA_OPIDIV: case LUA_OPMOD: /* division by 0 */ + return (nvalue(v2) != 0); + default: return 1; /* everything else is valid */ + } } -static void codearith (FuncState *fs, OpCode op, - expdesc *e1, expdesc *e2, int line) { - if (constfolding(op, e1, e2)) - return; - else { - int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; - int o1 = luaK_exp2RK(fs, e1); - if (o1 > o2) { - freeexp(fs, e1); - freeexp(fs, e2); - } - else { - freeexp(fs, e2); - freeexp(fs, e1); - } - e1->u.info = luaK_codeABC(fs, op, 0, o1, o2); - e1->k = VRELOCABLE; - luaK_fixline(fs, line); +/* +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) +*/ +static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { + TValue v1, v2, res; + if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2)) + return 0; /* non-numeric operands or not safe to fold */ + luaO_arith(fs->ls->L, op, &v1, &v2, &res); /* does operation */ + if (ttisinteger(&res)) { + e1->k = VKINT; + e1->u.ival = ivalue(&res); } + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ + lua_Number n = fltvalue(&res); + if (luai_numisnan(n) || n == 0) + return 0; + e1->k = VKFLT; + e1->u.nval = n; + } + return 1; } -static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, - expdesc *e2) { - int o1 = luaK_exp2RK(fs, e1); - int o2 = luaK_exp2RK(fs, e2); - freeexp(fs, e2); - freeexp(fs, e1); - if (cond == 0 && op != OP_EQ) { - int temp; /* exchange args to replace by `<' or `<=' */ - temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ - cond = 1; +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) { + int r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e->k = VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +*/ +static void codebinexpval (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int line) { + int rk1 = luaK_exp2RK(fs, e1); /* both operands are "RK" */ + int rk2 = luaK_exp2RK(fs, e2); + freeexps(fs, e1, e2); + e1->u.info = luaK_codeABC(fs, op, 0, rk1, rk2); /* generate opcode */ + e1->k = VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for comparisons. +** 'e1' was already put in R/K form by 'luaK_infix'. +*/ +static void codecomp (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int rk1 = (e1->k == VK) ? RKASK(e1->u.info) + : check_exp(e1->k == VNONRELOC, e1->u.info); + int rk2 = luaK_exp2RK(fs, e2); + freeexps(fs, e1, e2); + switch (opr) { + case OPR_NE: { /* '(a ~= b)' ==> 'not (a == b)' */ + e1->u.info = condjump(fs, OP_EQ, 0, rk1, rk2); + break; + } + case OPR_GT: case OPR_GE: { + /* '(a > b)' ==> '(b < a)'; '(a >= b)' ==> '(b <= a)' */ + OpCode op = cast(OpCode, (opr - OPR_NE) + OP_EQ); + e1->u.info = condjump(fs, op, 1, rk2, rk1); /* invert operands */ + break; + } + default: { /* '==', '<', '<=' use their own opcodes */ + OpCode op = cast(OpCode, (opr - OPR_EQ) + OP_EQ); + e1->u.info = condjump(fs, op, 1, rk1, rk2); + break; + } } - e1->u.info = condjump(fs, op, cond, o1, o2); e1->k = VJMP; } +/* +** Aplly prefix operation 'op' to expression 'e'. +*/ void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e, int line) { - expdesc e2; - e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; + static expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP}; /* fake 2nd operand */ switch (op) { - case OPR_MINUS: { - if (isnumeral(e)) /* minus constant? */ - e->u.nval = luai_numunm(NULL, e->u.nval); /* fold it */ - else { - luaK_exp2anyreg(fs, e); - codearith(fs, OP_UNM, e, &e2, line); - } + case OPR_MINUS: case OPR_BNOT: + if (constfolding(fs, op + LUA_OPUNM, e, &ef)) + break; + /* FALLTHROUGH */ + case OPR_LEN: + codeunexpval(fs, cast(OpCode, op + OP_UNM), e, line); break; - } case OPR_NOT: codenot(fs, e); break; - case OPR_LEN: { - luaK_exp2anyreg(fs, e); /* cannot operate on constants */ - codearith(fs, OP_LEN, e, &e2, line); - break; - } default: lua_assert(0); } } +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { switch (op) { case OPR_AND: { - luaK_goiftrue(fs, v); + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ break; } case OPR_OR: { - luaK_goiffalse(fs, v); + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ break; } case OPR_CONCAT: { - luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ + luaK_exp2nextreg(fs, v); /* operand must be on the 'stack' */ break; } - case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: - case OPR_MOD: case OPR_POW: { - if (!isnumeral(v)) luaK_exp2RK(fs, v); + case OPR_ADD: case OPR_SUB: + case OPR_MUL: case OPR_DIV: case OPR_IDIV: + case OPR_MOD: case OPR_POW: + case OPR_BAND: case OPR_BOR: case OPR_BXOR: + case OPR_SHL: case OPR_SHR: { + if (!tonumeral(v, NULL)) + luaK_exp2RK(fs, v); + /* else keep numeral, which may be folded with 2nd operand */ break; } default: { @@ -810,18 +1111,24 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { } +/* +** Finalize code for binary operation, after reading 2nd operand. +** For '(a .. b .. c)' (which is '(a .. (b .. c))', because +** concatenation is right associative), merge second CONCAT into first +** one. +*/ void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2, int line) { switch (op) { case OPR_AND: { - lua_assert(e1->t == NO_JUMP); /* list must be closed */ + lua_assert(e1->t == NO_JUMP); /* list closed by 'luK_infix' */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->f, e1->f); *e1 = *e2; break; } case OPR_OR: { - lua_assert(e1->f == NO_JUMP); /* list must be closed */ + lua_assert(e1->f == NO_JUMP); /* list closed by 'luK_infix' */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->t, e1->t); *e1 = *e2; @@ -829,29 +1136,30 @@ void luaK_posfix (FuncState *fs, BinOpr op, } case OPR_CONCAT: { luaK_exp2val(fs, e2); - if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { - lua_assert(e1->u.info == GETARG_B(getcode(fs, e2))-1); + if (e2->k == VRELOCABLE && + GET_OPCODE(getinstruction(fs, e2)) == OP_CONCAT) { + lua_assert(e1->u.info == GETARG_B(getinstruction(fs, e2))-1); freeexp(fs, e1); - SETARG_B(getcode(fs, e2), e1->u.info); + SETARG_B(getinstruction(fs, e2), e1->u.info); e1->k = VRELOCABLE; e1->u.info = e2->u.info; } else { luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ - codearith(fs, OP_CONCAT, e1, e2, line); + codebinexpval(fs, OP_CONCAT, e1, e2, line); } break; } case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: - case OPR_MOD: case OPR_POW: { - codearith(fs, cast(OpCode, op - OPR_ADD + OP_ADD), e1, e2, line); - break; - } - case OPR_EQ: case OPR_LT: case OPR_LE: { - codecomp(fs, cast(OpCode, op - OPR_EQ + OP_EQ), 1, e1, e2); + case OPR_IDIV: case OPR_MOD: case OPR_POW: + case OPR_BAND: case OPR_BOR: case OPR_BXOR: + case OPR_SHL: case OPR_SHR: { + if (!constfolding(fs, op + LUA_OPADD, e1, e2)) + codebinexpval(fs, cast(OpCode, op + OP_ADD), e1, e2, line); break; } + case OPR_EQ: case OPR_LT: case OPR_LE: case OPR_NE: case OPR_GT: case OPR_GE: { - codecomp(fs, cast(OpCode, op - OPR_NE + OP_EQ), 0, e1, e2); + codecomp(fs, op, e1, e2); break; } default: lua_assert(0); @@ -859,15 +1167,25 @@ void luaK_posfix (FuncState *fs, BinOpr op, } +/* +** Change line information associated with current position. +*/ void luaK_fixline (FuncState *fs, int line) { fs->f->lineinfo[fs->pc - 1] = line; } +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; int b = (tostore == LUA_MULTRET) ? 0 : tostore; - lua_assert(tostore != 0); + lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH); if (c <= MAXARG_C) luaK_codeABC(fs, OP_SETLIST, base, b, c); else if (c <= MAXARG_Ax) { diff --git a/depends/lua/src/lcorolib.c b/depends/lua/src/lcorolib.c index ce4f6ad42..2303429e7 100644 --- a/depends/lua/src/lcorolib.c +++ b/depends/lua/src/lcorolib.c @@ -1,15 +1,16 @@ /* -** $Id: lcorolib.c,v 1.5.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lcorolib.c,v 1.10 2016/04/11 19:19:55 roberto Exp $ ** Coroutine Library ** See Copyright Notice in lua.h */ +#define lcorolib_c +#define LUA_LIB -#include +#include "lprefix.h" -#define lcorolib_c -#define LUA_LIB +#include #include "lua.h" @@ -17,6 +18,13 @@ #include "lualib.h" +static lua_State *getco (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, co, 1, "thread expected"); + return co; +} + + static int auxresume (lua_State *L, lua_State *co, int narg) { int status; if (!lua_checkstack(co, narg)) { @@ -47,9 +55,8 @@ static int auxresume (lua_State *L, lua_State *co, int narg) { static int luaB_coresume (lua_State *L) { - lua_State *co = lua_tothread(L, 1); + lua_State *co = getco(L); int r; - luaL_argcheck(L, co, 1, "coroutine expected"); r = auxresume(L, co, lua_gettop(L) - 1); if (r < 0) { lua_pushboolean(L, 0); @@ -59,7 +66,7 @@ static int luaB_coresume (lua_State *L) { else { lua_pushboolean(L, 1); lua_insert(L, -(r + 1)); - return r + 1; /* return true + `resume' returns */ + return r + 1; /* return true + 'resume' returns */ } } @@ -68,7 +75,7 @@ static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); if (r < 0) { - if (lua_isstring(L, -1)) { /* error object is a string? */ + if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */ luaL_where(L, 1); /* add extra info */ lua_insert(L, -2); lua_concat(L, 2); @@ -102,8 +109,7 @@ static int luaB_yield (lua_State *L) { static int luaB_costatus (lua_State *L) { - lua_State *co = lua_tothread(L, 1); - luaL_argcheck(L, co, 1, "coroutine expected"); + lua_State *co = getco(L); if (L == co) lua_pushliteral(L, "running"); else { switch (lua_status(co)) { @@ -129,6 +135,12 @@ static int luaB_costatus (lua_State *L) { } +static int luaB_yieldable (lua_State *L) { + lua_pushboolean(L, lua_isyieldable(L)); + return 1; +} + + static int luaB_corunning (lua_State *L) { int ismain = lua_pushthread(L); lua_pushboolean(L, ismain); @@ -143,6 +155,7 @@ static const luaL_Reg co_funcs[] = { {"status", luaB_costatus}, {"wrap", luaB_cowrap}, {"yield", luaB_yield}, + {"isyieldable", luaB_yieldable}, {NULL, NULL} }; diff --git a/depends/lua/src/lctype.c b/depends/lua/src/lctype.c index 93f8cadc3..ae9367e69 100644 --- a/depends/lua/src/lctype.c +++ b/depends/lua/src/lctype.c @@ -1,5 +1,5 @@ /* -** $Id: lctype.c,v 1.11.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lctype.c,v 1.12 2014/11/02 19:19:04 roberto Exp $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ @@ -7,6 +7,9 @@ #define lctype_c #define LUA_CORE +#include "lprefix.h" + + #include "lctype.h" #if !LUA_USE_CTYPE /* { */ diff --git a/depends/lua/src/ldblib.c b/depends/lua/src/ldblib.c index 84fe3c7d8..786f6cd95 100644 --- a/depends/lua/src/ldblib.c +++ b/depends/lua/src/ldblib.c @@ -1,26 +1,42 @@ /* -** $Id: ldblib.c,v 1.132.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldblib.c,v 1.151 2015/11/23 11:29:43 roberto Exp $ ** Interface from Lua to its debug API ** See Copyright Notice in lua.h */ +#define ldblib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include #include -#define ldblib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" #include "lualib.h" -#define HOOKKEY "_HKEY" +/* +** The hook table at registry[&HOOKKEY] maps threads to their current +** hook function. (We only need the unique address of 'HOOKKEY'.) +*/ +static const int HOOKKEY = 0; +/* +** If L1 != L, L1 can be in any state, and therefore there are no +** guarantees about its stack space; any push in L1 must be +** checked. +*/ +static void checkstack (lua_State *L, lua_State *L1, int n) { + if (L != L1 && !lua_checkstack(L1, n)) + luaL_error(L, "stack overflow"); +} + static int db_getregistry (lua_State *L) { lua_pushvalue(L, LUA_REGISTRYINDEX); @@ -57,35 +73,20 @@ static int db_getuservalue (lua_State *L) { static int db_setuservalue (lua_State *L) { - if (lua_type(L, 1) == LUA_TLIGHTUSERDATA) - luaL_argerror(L, 1, "full userdata expected, got light userdata"); luaL_checktype(L, 1, LUA_TUSERDATA); - if (!lua_isnoneornil(L, 2)) - luaL_checktype(L, 2, LUA_TTABLE); + luaL_checkany(L, 2); lua_settop(L, 2); lua_setuservalue(L, 1); return 1; } -static void settabss (lua_State *L, const char *i, const char *v) { - lua_pushstring(L, v); - lua_setfield(L, -2, i); -} - - -static void settabsi (lua_State *L, const char *i, int v) { - lua_pushinteger(L, v); - lua_setfield(L, -2, i); -} - - -static void settabsb (lua_State *L, const char *i, int v) { - lua_pushboolean(L, v); - lua_setfield(L, -2, i); -} - - +/* +** Auxiliary function used by several library functions: check for +** an optional thread as function's first argument and set 'arg' with +** 1 if this argument is present (so that functions can skip it to +** access their other arguments) +*/ static lua_State *getthread (lua_State *L, int *arg) { if (lua_isthread(L, 1)) { *arg = 1; @@ -93,44 +94,74 @@ static lua_State *getthread (lua_State *L, int *arg) { } else { *arg = 0; - return L; + return L; /* function will operate over current thread */ } } +/* +** Variations of 'lua_settable', used by 'db_getinfo' to put results +** from 'lua_getinfo' into result table. Key is always a string; +** value can be a string, an int, or a boolean. +*/ +static void settabss (lua_State *L, const char *k, const char *v) { + lua_pushstring(L, v); + lua_setfield(L, -2, k); +} + +static void settabsi (lua_State *L, const char *k, int v) { + lua_pushinteger(L, v); + lua_setfield(L, -2, k); +} + +static void settabsb (lua_State *L, const char *k, int v) { + lua_pushboolean(L, v); + lua_setfield(L, -2, k); +} + + +/* +** In function 'db_getinfo', the call to 'lua_getinfo' may push +** results on the stack; later it creates the result table to put +** these objects. Function 'treatstackoption' puts the result from +** 'lua_getinfo' on top of the result table so that it can call +** 'lua_setfield'. +*/ static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { - if (L == L1) { - lua_pushvalue(L, -2); - lua_remove(L, -3); - } + if (L == L1) + lua_rotate(L, -2, 1); /* exchange object and table */ else - lua_xmove(L1, L, 1); - lua_setfield(L, -2, fname); + lua_xmove(L1, L, 1); /* move object to the "main" stack */ + lua_setfield(L, -2, fname); /* put object into table */ } +/* +** Calls 'lua_getinfo' and collects all results in a new table. +** L1 needs stack space for an optional input (function) plus +** two optional outputs (function and line table) from function +** 'lua_getinfo'. +*/ static int db_getinfo (lua_State *L) { lua_Debug ar; int arg; lua_State *L1 = getthread(L, &arg); const char *options = luaL_optstring(L, arg+2, "flnStu"); - if (lua_isnumber(L, arg+1)) { - if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) { + checkstack(L, L1, 3); + if (lua_isfunction(L, arg + 1)) { /* info about a function? */ + options = lua_pushfstring(L, ">%s", options); /* add '>' to 'options' */ + lua_pushvalue(L, arg + 1); /* move function to 'L1' stack */ + lua_xmove(L, L1, 1); + } + else { /* stack level */ + if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) { lua_pushnil(L); /* level out of range */ return 1; } } - else if (lua_isfunction(L, arg+1)) { - lua_pushfstring(L, ">%s", options); - options = lua_tostring(L, -1); - lua_pushvalue(L, arg+1); - lua_xmove(L, L1, 1); - } - else - return luaL_argerror(L, arg+1, "function or level expected"); if (!lua_getinfo(L1, options, &ar)) return luaL_argerror(L, arg+2, "invalid option"); - lua_createtable(L, 0, 2); + lua_newtable(L); /* table to collect results */ if (strchr(options, 'S')) { settabss(L, "source", ar.source); settabss(L, "short_src", ar.short_src); @@ -164,20 +195,22 @@ static int db_getlocal (lua_State *L) { lua_State *L1 = getthread(L, &arg); lua_Debug ar; const char *name; - int nvar = luaL_checkint(L, arg+2); /* local-variable index */ + int nvar = (int)luaL_checkinteger(L, arg + 2); /* local-variable index */ if (lua_isfunction(L, arg + 1)) { /* function argument? */ lua_pushvalue(L, arg + 1); /* push function */ lua_pushstring(L, lua_getlocal(L, NULL, nvar)); /* push local name */ - return 1; + return 1; /* return only name (there is no value) */ } else { /* stack-level argument */ - if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + int level = (int)luaL_checkinteger(L, arg + 1); + if (!lua_getstack(L1, level, &ar)) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); + checkstack(L, L1, 1); name = lua_getlocal(L1, &ar, nvar); if (name) { - lua_xmove(L1, L, 1); /* push local value */ + lua_xmove(L1, L, 1); /* move local value */ lua_pushstring(L, name); /* push name */ - lua_pushvalue(L, -2); /* re-order */ + lua_rotate(L, -2, 1); /* re-order */ return 2; } else { @@ -190,26 +223,36 @@ static int db_getlocal (lua_State *L) { static int db_setlocal (lua_State *L) { int arg; + const char *name; lua_State *L1 = getthread(L, &arg); lua_Debug ar; - if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + int level = (int)luaL_checkinteger(L, arg + 1); + int nvar = (int)luaL_checkinteger(L, arg + 2); + if (!lua_getstack(L1, level, &ar)) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); luaL_checkany(L, arg+3); lua_settop(L, arg+3); + checkstack(L, L1, 1); lua_xmove(L, L1, 1); - lua_pushstring(L, lua_setlocal(L1, &ar, luaL_checkint(L, arg+2))); + name = lua_setlocal(L1, &ar, nvar); + if (name == NULL) + lua_pop(L1, 1); /* pop value (if not popped by 'lua_setlocal') */ + lua_pushstring(L, name); return 1; } +/* +** get (if 'get' is true) or set an upvalue from a closure +*/ static int auxupvalue (lua_State *L, int get) { const char *name; - int n = luaL_checkint(L, 2); - luaL_checktype(L, 1, LUA_TFUNCTION); + int n = (int)luaL_checkinteger(L, 2); /* upvalue index */ + luaL_checktype(L, 1, LUA_TFUNCTION); /* closure */ name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); if (name == NULL) return 0; lua_pushstring(L, name); - lua_insert(L, -(get+1)); + lua_insert(L, -(get+1)); /* no-op if get is false */ return get + 1; } @@ -225,13 +268,15 @@ static int db_setupvalue (lua_State *L) { } +/* +** Check whether a given upvalue from a given closure exists and +** returns its index +*/ static int checkupval (lua_State *L, int argf, int argnup) { - lua_Debug ar; - int nup = luaL_checkint(L, argnup); - luaL_checktype(L, argf, LUA_TFUNCTION); - lua_pushvalue(L, argf); - lua_getinfo(L, ">u", &ar); - luaL_argcheck(L, 1 <= nup && nup <= ar.nups, argnup, "invalid upvalue index"); + int nup = (int)luaL_checkinteger(L, argnup); /* upvalue index */ + luaL_checktype(L, argf, LUA_TFUNCTION); /* closure */ + luaL_argcheck(L, (lua_getupvalue(L, argf, nup) != NULL), argnup, + "invalid upvalue index"); return nup; } @@ -253,26 +298,29 @@ static int db_upvaluejoin (lua_State *L) { } -#define gethooktable(L) luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY) - - +/* +** Call hook function registered at hook table for the current +** thread (if there is one) +*/ static void hookf (lua_State *L, lua_Debug *ar) { static const char *const hooknames[] = {"call", "return", "line", "count", "tail call"}; - gethooktable(L); + lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY); lua_pushthread(L); - lua_rawget(L, -2); - if (lua_isfunction(L, -1)) { - lua_pushstring(L, hooknames[(int)ar->event]); + if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */ + lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */ if (ar->currentline >= 0) - lua_pushinteger(L, ar->currentline); + lua_pushinteger(L, ar->currentline); /* push current line */ else lua_pushnil(L); lua_assert(lua_getinfo(L, "lS", ar)); - lua_call(L, 2, 0); + lua_call(L, 2, 0); /* call hook function */ } } +/* +** Convert a string mask (for 'sethook') into a bit mask +*/ static int makemask (const char *smask, int count) { int mask = 0; if (strchr(smask, 'c')) mask |= LUA_MASKCALL; @@ -283,6 +331,9 @@ static int makemask (const char *smask, int count) { } +/* +** Convert a bit mask (for 'gethook') into a string mask +*/ static char *unmakemask (int mask, char *smask) { int i = 0; if (mask & LUA_MASKCALL) smask[i++] = 'c'; @@ -297,26 +348,30 @@ static int db_sethook (lua_State *L) { int arg, mask, count; lua_Hook func; lua_State *L1 = getthread(L, &arg); - if (lua_isnoneornil(L, arg+1)) { + if (lua_isnoneornil(L, arg+1)) { /* no hook? */ lua_settop(L, arg+1); func = NULL; mask = 0; count = 0; /* turn off hooks */ } else { const char *smask = luaL_checkstring(L, arg+2); luaL_checktype(L, arg+1, LUA_TFUNCTION); - count = luaL_optint(L, arg+3, 0); + count = (int)luaL_optinteger(L, arg + 3, 0); func = hookf; mask = makemask(smask, count); } - if (gethooktable(L) == 0) { /* creating hook table? */ + if (lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY) == LUA_TNIL) { + lua_createtable(L, 0, 2); /* create a hook table */ + lua_pushvalue(L, -1); + lua_rawsetp(L, LUA_REGISTRYINDEX, &HOOKKEY); /* set it in position */ lua_pushstring(L, "k"); lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ lua_pushvalue(L, -1); lua_setmetatable(L, -2); /* setmetatable(hooktable) = hooktable */ } - lua_pushthread(L1); lua_xmove(L1, L, 1); - lua_pushvalue(L, arg+1); - lua_rawset(L, -3); /* set new hook */ - lua_sethook(L1, func, mask, count); /* set hooks */ + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */ + lua_pushvalue(L, arg + 1); /* value (hook function) */ + lua_rawset(L, -3); /* hooktable[L1] = new Lua hook */ + lua_sethook(L1, func, mask, count); return 0; } @@ -327,16 +382,19 @@ static int db_gethook (lua_State *L) { char buff[5]; int mask = lua_gethookmask(L1); lua_Hook hook = lua_gethook(L1); - if (hook != NULL && hook != hookf) /* external hook? */ + if (hook == NULL) /* no hook? */ + lua_pushnil(L); + else if (hook != hookf) /* external hook? */ lua_pushliteral(L, "external hook"); - else { - gethooktable(L); + else { /* hook table must exist */ + lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY); + checkstack(L, L1, 1); lua_pushthread(L1); lua_xmove(L1, L, 1); - lua_rawget(L, -2); /* get hook */ + lua_rawget(L, -2); /* 1st result = hooktable[L1] */ lua_remove(L, -2); /* remove hook table */ } - lua_pushstring(L, unmakemask(mask, buff)); - lua_pushinteger(L, lua_gethookcount(L1)); + lua_pushstring(L, unmakemask(mask, buff)); /* 2nd result = mask */ + lua_pushinteger(L, lua_gethookcount(L1)); /* 3rd result = count */ return 3; } @@ -344,13 +402,13 @@ static int db_gethook (lua_State *L) { static int db_debug (lua_State *L) { for (;;) { char buffer[250]; - luai_writestringerror("%s", "lua_debug> "); + lua_writestringerror("%s", "lua_debug> "); if (fgets(buffer, sizeof(buffer), stdin) == 0 || strcmp(buffer, "cont\n") == 0) return 0; if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || lua_pcall(L, 0, 0, 0)) - luai_writestringerror("%s\n", lua_tostring(L, -1)); + lua_writestringerror("%s\n", lua_tostring(L, -1)); lua_settop(L, 0); /* remove eventual returns */ } } @@ -363,7 +421,7 @@ static int db_traceback (lua_State *L) { if (msg == NULL && !lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */ lua_pushvalue(L, arg + 1); /* return it untouched */ else { - int level = luaL_optint(L, arg + 2, (L == L1) ? 1 : 0); + int level = (int)luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0); luaL_traceback(L, L1, msg, level); } return 1; diff --git a/depends/lua/src/ldebug.c b/depends/lua/src/ldebug.c index 20d663eff..e499ee362 100644 --- a/depends/lua/src/ldebug.c +++ b/depends/lua/src/ldebug.c @@ -1,18 +1,19 @@ /* -** $Id: ldebug.c,v 2.90.1.3 2013/05/16 16:04:15 roberto Exp $ +** $Id: ldebug.c,v 2.120 2016/03/31 19:01:21 roberto Exp $ ** Debug Interface ** See Copyright Notice in lua.h */ +#define ldebug_c +#define LUA_CORE + +#include "lprefix.h" + #include #include #include - -#define ldebug_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -33,6 +34,10 @@ #define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_TCCL) +/* Active Lua function (given call info) */ +#define ci_func(ci) (clLvalue((ci)->func)) + + static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); @@ -48,9 +53,31 @@ static int currentline (CallInfo *ci) { /* -** this function can be called asynchronous (e.g. during a signal) +** If function yielded, its 'func' can be in the 'extra' field. The +** next function restores 'func' to its correct value for debugging +** purposes. (It exchanges 'func' and 'extra'; so, when called again, +** after debugging, it also "re-restores" ** 'func' to its altered value. +*/ +static void swapextra (lua_State *L) { + if (L->status == LUA_YIELD) { + CallInfo *ci = L->ci; /* get function that yielded */ + StkId temp = ci->func; /* exchange its 'func' and 'extra' values */ + ci->func = restorestack(L, ci->extra); + ci->extra = savestack(L, temp); + } +} + + +/* +** This function can be called asynchronously (e.g. during a signal). +** Fields 'oldpc', 'basehookcount', and 'hookcount' (set by +** 'resethookcount') are for debug only, and it is no problem if they +** get arbitrary values (causes at most one wrong hook call). 'hookmask' +** is an atomic value. We assume that pointers are atomic too (e.g., gcc +** ensures that for all platforms where it runs). Moreover, 'hook' is +** always checked before being called (see 'luaD_hook'). */ -LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { +LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { if (func == NULL || mask == 0) { /* turn off hooks? */ mask = 0; func = NULL; @@ -61,7 +88,6 @@ LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { L->basehookcount = count; resethookcount(L); L->hookmask = cast_byte(mask); - return 1; } @@ -106,7 +132,7 @@ static const char *upvalname (Proto *p, int uv) { static const char *findvararg (CallInfo *ci, int n, StkId *pos) { int nparams = clLvalue(ci->func)->p->numparams; - if (n >= ci->u.l.base - ci->func - nparams) + if (n >= cast_int(ci->u.l.base - ci->func) - nparams) return NULL; /* no such vararg */ else { *pos = ci->func + nparams + n; @@ -144,6 +170,7 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n, LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { const char *name; lua_lock(L); + swapextra(L); if (ar == NULL) { /* information about non-active function? */ if (!isLfunction(L->top - 1)) /* not a Lua function? */ name = NULL; @@ -151,25 +178,30 @@ LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { name = luaF_getlocalname(clLvalue(L->top - 1)->p, n, 0); } else { /* active function; get information through 'ar' */ - StkId pos = 0; /* to avoid warnings */ + StkId pos = NULL; /* to avoid warnings */ name = findlocal(L, ar->i_ci, n, &pos); if (name) { setobj2s(L, L->top, pos); api_incr_top(L); } } + swapextra(L); lua_unlock(L); return name; } LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { - StkId pos = 0; /* to avoid warnings */ - const char *name = findlocal(L, ar->i_ci, n, &pos); + StkId pos = NULL; /* to avoid warnings */ + const char *name; lua_lock(L); - if (name) + swapextra(L); + name = findlocal(L, ar->i_ci, n, &pos); + if (name) { setobjs2s(L, pos, L->top - 1); - L->top--; /* pop value */ + L->top--; /* pop value */ + } + swapextra(L); lua_unlock(L); return name; } @@ -269,6 +301,7 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { CallInfo *ci; StkId func; lua_lock(L); + swapextra(L); if (*what == '>') { ci = NULL; func = L->top - 1; @@ -287,6 +320,7 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { setobjs2s(L, L->top, func); api_incr_top(L); } + swapextra(L); /* correct before option 'L', which can raise a mem. error */ if (strchr(what, 'L')) collectvalidlines(L, cl); lua_unlock(L); @@ -366,18 +400,13 @@ static int findsetreg (Proto *p, int lastpc, int reg) { case OP_JMP: { int b = GETARG_sBx(i); int dest = pc + 1 + b; - /* jump is forward and do not skip `lastpc'? */ + /* jump is forward and do not skip 'lastpc'? */ if (pc < dest && dest <= lastpc) { if (dest > jmptarget) jmptarget = dest; /* update 'jmptarget' */ } break; } - case OP_TEST: { - if (reg == a) /* jumped code can change 'a' */ - setreg = filterpc(pc, jmptarget); - break; - } default: if (testAMode(op) && reg == a) /* any instruction that set A */ setreg = filterpc(pc, jmptarget); @@ -443,10 +472,14 @@ static const char *getobjname (Proto *p, int lastpc, int reg, static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { - TMS tm; + TMS tm = (TMS)0; /* to avoid warnings */ Proto *p = ci_func(ci)->p; /* calling function */ int pc = currentpc(ci); /* calling instruction index */ Instruction i = p->code[pc]; /* calling instruction */ + if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */ + *name = "?"; + return "hook"; + } switch (GET_OPCODE(i)) { case OP_CALL: case OP_TAILCALL: /* get function name */ @@ -456,25 +489,27 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { return "for iterator"; } /* all other instructions can call only through metamethods */ - case OP_SELF: - case OP_GETTABUP: - case OP_GETTABLE: tm = TM_INDEX; break; - case OP_SETTABUP: - case OP_SETTABLE: tm = TM_NEWINDEX; break; - case OP_EQ: tm = TM_EQ; break; - case OP_ADD: tm = TM_ADD; break; - case OP_SUB: tm = TM_SUB; break; - case OP_MUL: tm = TM_MUL; break; - case OP_DIV: tm = TM_DIV; break; - case OP_MOD: tm = TM_MOD; break; - case OP_POW: tm = TM_POW; break; + case OP_SELF: case OP_GETTABUP: case OP_GETTABLE: + tm = TM_INDEX; + break; + case OP_SETTABUP: case OP_SETTABLE: + tm = TM_NEWINDEX; + break; + case OP_ADD: case OP_SUB: case OP_MUL: case OP_MOD: + case OP_POW: case OP_DIV: case OP_IDIV: case OP_BAND: + case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: { + int offset = cast_int(GET_OPCODE(i)) - cast_int(OP_ADD); /* ORDER OP */ + tm = cast(TMS, offset + cast_int(TM_ADD)); /* ORDER TM */ + break; + } case OP_UNM: tm = TM_UNM; break; + case OP_BNOT: tm = TM_BNOT; break; case OP_LEN: tm = TM_LEN; break; + case OP_CONCAT: tm = TM_CONCAT; break; + case OP_EQ: tm = TM_EQ; break; case OP_LT: tm = TM_LT; break; case OP_LE: tm = TM_LE; break; - case OP_CONCAT: tm = TM_CONCAT; break; - default: - return NULL; /* else no useful name can be found */ + default: lua_assert(0); /* other instructions cannot call a function */ } *name = getstr(G(L)->tmname[tm]); return "metamethod"; @@ -485,17 +520,21 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { /* -** only ANSI way to check whether a pointer points to an array -** (used only for error messages, so efficiency is not a big concern) +** The subtraction of two potentially unrelated pointers is +** not ISO C, but it should not crash a program; the subsequent +** checks are ISO C and ensure a correct result. */ static int isinstack (CallInfo *ci, const TValue *o) { - StkId p; - for (p = ci->u.l.base; p < ci->top; p++) - if (o == p) return 1; - return 0; + ptrdiff_t i = o - ci->u.l.base; + return (0 <= i && i < (ci->top - ci->u.l.base) && ci->u.l.base + i == o); } +/* +** Checks whether value 'o' came from an upvalue. (That can only happen +** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on +** upvalues.) +*/ static const char *getupvalname (CallInfo *ci, const TValue *o, const char **name) { LClosure *c = ci_func(ci); @@ -510,10 +549,9 @@ static const char *getupvalname (CallInfo *ci, const TValue *o, } -l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { +static const char *varinfo (lua_State *L, const TValue *o) { + const char *name = NULL; /* to avoid warnings */ CallInfo *ci = L->ci; - const char *name = NULL; - const char *t = objtypename(o); const char *kind = NULL; if (isLua(ci)) { kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */ @@ -521,73 +559,121 @@ l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { kind = getobjname(ci_func(ci)->p, currentpc(ci), cast_int(o - ci->u.l.base), &name); } - if (kind) - luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)", - op, kind, name, t); - else - luaG_runerror(L, "attempt to %s a %s value", op, t); + return (kind) ? luaO_pushfstring(L, " (%s '%s')", kind, name) : ""; } -l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2) { - if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; - lua_assert(!ttisstring(p1) && !ttisnumber(p1)); +l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { + const char *t = luaT_objtypename(L, o); + luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); +} + + +l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) { + if (ttisstring(p1) || cvt2str(p1)) p1 = p2; luaG_typeerror(L, p1, "concatenate"); } -l_noret luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) { - TValue temp; - if (luaV_tonumber(p1, &temp) == NULL) - p2 = p1; /* first operand is wrong */ - luaG_typeerror(L, p2, "perform arithmetic on"); +l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, const char *msg) { + lua_Number temp; + if (!tonumber(p1, &temp)) /* first operand is wrong? */ + p2 = p1; /* now second is wrong */ + luaG_typeerror(L, p2, msg); +} + + +/* +** Error when both values are convertible to numbers, but not to integers +*/ +l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) { + lua_Integer temp; + if (!tointeger(p1, &temp)) + p2 = p1; + luaG_runerror(L, "number%s has no integer representation", varinfo(L, p2)); } l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { - const char *t1 = objtypename(p1); - const char *t2 = objtypename(p2); - if (t1 == t2) + const char *t1 = luaT_objtypename(L, p1); + const char *t2 = luaT_objtypename(L, p2); + if (strcmp(t1, t2) == 0) luaG_runerror(L, "attempt to compare two %s values", t1); else luaG_runerror(L, "attempt to compare %s with %s", t1, t2); } -static void addinfo (lua_State *L, const char *msg) { - CallInfo *ci = L->ci; - if (isLua(ci)) { /* is Lua code? */ - char buff[LUA_IDSIZE]; /* add file:line information */ - int line = currentline(ci); - TString *src = ci_func(ci)->p->source; - if (src) - luaO_chunkid(buff, getstr(src), LUA_IDSIZE); - else { /* no source available; use "?" instead */ - buff[0] = '?'; buff[1] = '\0'; - } - luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); +/* add src:line information to 'msg' */ +const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, + int line) { + char buff[LUA_IDSIZE]; + if (src) + luaO_chunkid(buff, getstr(src), LUA_IDSIZE); + else { /* no source available; use "?" instead */ + buff[0] = '?'; buff[1] = '\0'; } + return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } l_noret luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { /* is there an error handling function? */ StkId errfunc = restorestack(L, L->errfunc); - if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR); setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ - L->top++; - luaD_call(L, L->top - 2, 1, 0); /* call it */ + L->top++; /* assume EXTRA_STACK */ + luaD_callnoyield(L, L->top - 2, 1); /* call it */ } luaD_throw(L, LUA_ERRRUN); } l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { + CallInfo *ci = L->ci; + const char *msg; va_list argp; va_start(argp, fmt); - addinfo(L, luaO_pushvfstring(L, fmt, argp)); + msg = luaO_pushvfstring(L, fmt, argp); /* format message */ va_end(argp); + if (isLua(ci)) /* if Lua function, add source:line information */ + luaG_addinfo(L, msg, ci_func(ci)->p->source, currentline(ci)); luaG_errormsg(L); } + +void luaG_traceexec (lua_State *L) { + CallInfo *ci = L->ci; + lu_byte mask = L->hookmask; + int counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); + if (counthook) + resethookcount(L); /* reset count */ + else if (!(mask & LUA_MASKLINE)) + return; /* no line hook and count != 0; nothing to be done */ + if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ + return; /* do not call hook again (VM yielded, so it did not move) */ + } + if (counthook) + luaD_hook(L, LUA_HOOKCOUNT, -1); /* call count hook */ + if (mask & LUA_MASKLINE) { + Proto *p = ci_func(ci)->p; + int npc = pcRel(ci->u.l.savedpc, p); + int newline = getfuncline(p, npc); + if (npc == 0 || /* call linehook when enter a new function, */ + ci->u.l.savedpc <= L->oldpc || /* when jump back (loop), or when */ + newline != getfuncline(p, pcRel(L->oldpc, p))) /* enter a new line */ + luaD_hook(L, LUA_HOOKLINE, newline); /* call line hook */ + } + L->oldpc = ci->u.l.savedpc; + if (L->status == LUA_YIELD) { /* did hook yield? */ + if (counthook) + L->hookcount = 1; /* undo decrement to zero */ + ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ + ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ + ci->func = L->top - 1; /* protect stack below results */ + luaD_throw(L, LUA_YIELD); + } +} + diff --git a/depends/lua/src/ldo.c b/depends/lua/src/ldo.c index e9dd5fa95..8804c9977 100644 --- a/depends/lua/src/ldo.c +++ b/depends/lua/src/ldo.c @@ -1,17 +1,19 @@ /* -** $Id: ldo.c,v 2.108.1.3 2013/11/08 18:22:50 roberto Exp $ +** $Id: ldo.c,v 2.151 2015/12/16 16:40:07 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ +#define ldo_c +#define LUA_CORE + +#include "lprefix.h" + #include #include #include -#define ldo_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -33,6 +35,8 @@ +#define errorstatus(s) ((s) > LUA_YIELD) + /* ** {====================================================== @@ -46,30 +50,33 @@ ** C++ code, with _longjmp/_setjmp when asked to use them, and with ** longjmp/setjmp otherwise. */ -#if !defined(LUAI_THROW) +#if !defined(LUAI_THROW) /* { */ + +#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* { */ -#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* C++ exceptions */ #define LUAI_THROW(L,c) throw(c) #define LUAI_TRY(L,c,a) \ try { a } catch(...) { if ((c)->status == 0) (c)->status = -1; } #define luai_jmpbuf int /* dummy variable */ -#elif defined(LUA_USE_ULONGJMP) -/* in Unix, try _longjmp/_setjmp (more efficient) */ +#elif defined(LUA_USE_POSIX) /* }{ */ + +/* in POSIX, try _longjmp/_setjmp (more efficient) */ #define LUAI_THROW(L,c) _longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf -#else -/* default handling with long jumps */ +#else /* }{ */ + +/* ISO C handling with long jumps */ #define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf -#endif +#endif /* } */ -#endif +#endif /* } */ @@ -106,15 +113,19 @@ l_noret luaD_throw (lua_State *L, int errcode) { LUAI_THROW(L, L->errorJmp); /* jump to it */ } else { /* thread has no error handler */ + global_State *g = G(L); L->status = cast_byte(errcode); /* mark it as dead */ - if (G(L)->mainthread->errorJmp) { /* main thread has a handler? */ - setobjs2s(L, G(L)->mainthread->top++, L->top - 1); /* copy error obj. */ - luaD_throw(G(L)->mainthread, errcode); /* re-throw in main thread */ + if (g->mainthread->errorJmp) { /* main thread has a handler? */ + setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ + luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ } else { /* no handler at all; abort */ - if (G(L)->panic) { /* panic function? */ + if (g->panic) { /* panic function? */ + seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ + if (L->ci->top < L->top) + L->ci->top = L->top; /* pushing msg. can break this invariant */ lua_unlock(L); - G(L)->panic(L); /* call it (last chance to jump out) */ + g->panic(L); /* call panic function (last chance to jump out) */ } abort(); } @@ -139,12 +150,17 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { /* }====================================================== */ +/* +** {================================================================== +** Stack reallocation +** =================================================================== +*/ static void correctstack (lua_State *L, TValue *oldstack) { CallInfo *ci; - GCObject *up; + UpVal *up; L->top = (L->top - oldstack) + L->stack; - for (up = L->openupval; up != NULL; up = up->gch.next) - gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; + for (up = L->openupval; up != NULL; up = up->u.open.next) + up->v = (up->v - oldstack) + L->stack; for (ci = L->ci; ci != NULL; ci = ci->previous) { ci->top = (ci->top - oldstack) + L->stack; ci->func = (ci->func - oldstack) + L->stack; @@ -206,17 +222,34 @@ void luaD_shrinkstack (lua_State *L) { int inuse = stackinuse(L); int goodsize = inuse + (inuse / 8) + 2*EXTRA_STACK; if (goodsize > LUAI_MAXSTACK) goodsize = LUAI_MAXSTACK; - if (inuse > LUAI_MAXSTACK || /* handling stack overflow? */ - goodsize >= L->stacksize) /* would grow instead of shrink? */ - condmovestack(L); /* don't change stack (change only for debugging) */ + if (L->stacksize > LUAI_MAXSTACK) /* was handling stack overflow? */ + luaE_freeCI(L); /* free all CIs (list grew because of an error) */ else + luaE_shrinkCI(L); /* shrink list */ + if (inuse <= LUAI_MAXSTACK && /* not handling stack overflow? */ + goodsize < L->stacksize) /* trying to shrink? */ luaD_reallocstack(L, goodsize); /* shrink it */ + else + condmovestack(L,,); /* don't change stack (change only for debugging) */ } +void luaD_inctop (lua_State *L) { + luaD_checkstack(L, 1); + L->top++; +} + +/* }================================================================== */ + + +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which triggers this +** function, can be changed asynchronously by signals.) +*/ void luaD_hook (lua_State *L, int event, int line) { lua_Hook hook = L->hook; - if (hook && L->allowhook) { + if (hook && L->allowhook) { /* make sure there is a hook */ CallInfo *ci = L->ci; ptrdiff_t top = savestack(L, L->top); ptrdiff_t ci_top = savestack(L, ci->top); @@ -258,31 +291,34 @@ static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { int i; int nfixargs = p->numparams; StkId base, fixed; - lua_assert(actual >= nfixargs); /* move fixed parameters to final position */ - luaD_checkstack(L, p->maxstacksize); /* check again for new 'base' */ fixed = L->top - actual; /* first fixed argument */ base = L->top; /* final position of first argument */ - for (i=0; itop++, fixed + i); - setnilvalue(fixed + i); + setnilvalue(fixed + i); /* erase original copy (for GC) */ } + for (; i < nfixargs; i++) + setnilvalue(L->top++); /* complete missing arguments */ return base; } -static StkId tryfuncTM (lua_State *L, StkId func) { +/* +** Check whether __call metafield of 'func' is a function. If so, put +** it in stack below original 'func' so that 'luaD_precall' can call +** it. Raise an error if __call metafield is not a function. +*/ +static void tryfuncTM (lua_State *L, StkId func) { const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); StkId p; - ptrdiff_t funcr = savestack(L, func); if (!ttisfunction(tm)) luaG_typeerror(L, func, "call"); - /* Open a hole inside the stack at `func' */ - for (p = L->top; p > func; p--) setobjs2s(L, p, p-1); - incr_top(L); - func = restorestack(L, funcr); /* previous call may change stack */ + /* Open a hole inside the stack at 'func' */ + for (p = L->top; p > func; p--) + setobjs2s(L, p, p-1); + L->top++; /* slot ensured by caller */ setobj2s(L, func, tm); /* tag method is the new function to be called */ - return func; } @@ -290,79 +326,133 @@ static StkId tryfuncTM (lua_State *L, StkId func) { #define next_ci(L) (L->ci = (L->ci->next ? L->ci->next : luaE_extendCI(L))) +/* macro to check stack size, preserving 'p' */ +#define checkstackp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \ + luaC_checkGC(L), /* stack grow uses memory */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + + /* -** returns true if function has been executed (C function) +** Prepares a function call: checks the stack, creates a new CallInfo +** entry, fills in the relevant information, calls hook if needed. +** If function is a C function, does the call, too. (Otherwise, leave +** the execution ('luaV_execute') to the caller, to allow stackless +** calls.) Returns true iff function has been executed (C function). */ int luaD_precall (lua_State *L, StkId func, int nresults) { lua_CFunction f; CallInfo *ci; - int n; /* number of arguments (Lua) or returns (C) */ - ptrdiff_t funcr = savestack(L, func); switch (ttype(func)) { + case LUA_TCCL: /* C closure */ + f = clCvalue(func)->f; + goto Cfunc; case LUA_TLCF: /* light C function */ f = fvalue(func); - goto Cfunc; - case LUA_TCCL: { /* C closure */ - f = clCvalue(func)->f; - Cfunc: - luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + Cfunc: { + int n; /* number of returns */ + checkstackp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ ci = next_ci(L); /* now 'enter' new function */ ci->nresults = nresults; - ci->func = restorestack(L, funcr); + ci->func = func; ci->top = L->top + LUA_MINSTACK; lua_assert(ci->top <= L->stack_last); ci->callstatus = 0; - luaC_checkGC(L); /* stack grow uses memory */ if (L->hookmask & LUA_MASKCALL) luaD_hook(L, LUA_HOOKCALL, -1); lua_unlock(L); n = (*f)(L); /* do the actual call */ lua_lock(L); api_checknelems(L, n); - luaD_poscall(L, L->top - n); + luaD_poscall(L, ci, L->top - n, n); return 1; } case LUA_TLCL: { /* Lua function: prepare its call */ StkId base; Proto *p = clLvalue(func)->p; - n = cast_int(L->top - func) - 1; /* number of real arguments */ - luaD_checkstack(L, p->maxstacksize); - for (; n < p->numparams; n++) - setnilvalue(L->top++); /* complete missing arguments */ - if (!p->is_vararg) { - func = restorestack(L, funcr); + int n = cast_int(L->top - func) - 1; /* number of real arguments */ + int fsize = p->maxstacksize; /* frame size */ + checkstackp(L, fsize, func); + if (p->is_vararg != 1) { /* do not use vararg? */ + for (; n < p->numparams; n++) + setnilvalue(L->top++); /* complete missing arguments */ base = func + 1; } - else { + else base = adjust_varargs(L, p, n); - func = restorestack(L, funcr); /* previous call can change stack */ - } ci = next_ci(L); /* now 'enter' new function */ ci->nresults = nresults; ci->func = func; ci->u.l.base = base; - ci->top = base + p->maxstacksize; + L->top = ci->top = base + fsize; lua_assert(ci->top <= L->stack_last); ci->u.l.savedpc = p->code; /* starting point */ ci->callstatus = CIST_LUA; - L->top = ci->top; - luaC_checkGC(L); /* stack grow uses memory */ if (L->hookmask & LUA_MASKCALL) callhook(L, ci); return 0; } default: { /* not a function */ - func = tryfuncTM(L, func); /* retry with 'function' tag method */ + checkstackp(L, 1, func); /* ensure space for metamethod */ + tryfuncTM(L, func); /* try to get '__call' metamethod */ return luaD_precall(L, func, nresults); /* now it must be a function */ } } } -int luaD_poscall (lua_State *L, StkId firstResult) { +/* +** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'. +** Handle most typical cases (zero results for commands, one result for +** expressions, multiple results for tail calls/single parameters) +** separated. +*/ +static int moveresults (lua_State *L, const TValue *firstResult, StkId res, + int nres, int wanted) { + switch (wanted) { /* handle typical cases separately */ + case 0: break; /* nothing to move */ + case 1: { /* one result needed */ + if (nres == 0) /* no results? */ + firstResult = luaO_nilobject; /* adjust with nil */ + setobjs2s(L, res, firstResult); /* move it to proper place */ + break; + } + case LUA_MULTRET: { + int i; + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstResult + i); + L->top = res + nres; + return 0; /* wanted == LUA_MULTRET */ + } + default: { + int i; + if (wanted <= nres) { /* enough results? */ + for (i = 0; i < wanted; i++) /* move wanted results to correct place */ + setobjs2s(L, res + i, firstResult + i); + } + else { /* not enough results; use all of them plus nils */ + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstResult + i); + for (; i < wanted; i++) /* complete wanted number of results */ + setnilvalue(res + i); + } + break; + } + } + L->top = res + wanted; /* top points after the last result */ + return 1; +} + + +/* +** Finishes a function call: calls hook if necessary, removes CallInfo, +** moves current number of results to proper place; returns 0 iff call +** wanted multiple (variable number of) results. +*/ +int luaD_poscall (lua_State *L, CallInfo *ci, StkId firstResult, int nres) { StkId res; - int wanted, i; - CallInfo *ci = L->ci; + int wanted = ci->nresults; if (L->hookmask & (LUA_MASKRET | LUA_MASKLINE)) { if (L->hookmask & LUA_MASKRET) { ptrdiff_t fr = savestack(L, firstResult); /* hook may change stack */ @@ -372,15 +462,24 @@ int luaD_poscall (lua_State *L, StkId firstResult) { L->oldpc = ci->previous->u.l.savedpc; /* 'oldpc' for caller function */ } res = ci->func; /* res == final position of 1st result */ - wanted = ci->nresults; - L->ci = ci = ci->previous; /* back to caller */ - /* move results to correct place */ - for (i = wanted; i != 0 && firstResult < L->top; i--) - setobjs2s(L, res++, firstResult++); - while (i-- > 0) - setnilvalue(res++); - L->top = res; - return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ + L->ci = ci->previous; /* back to caller */ + /* move results to proper place */ + return moveresults(L, firstResult, res, nres, wanted); +} + + +/* +** Check appropriate error for stack overflow ("regular" overflow or +** overflow while handling stack overflow). If 'nCalls' is larger than +** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but +** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to +** allow overflow handling to work) +*/ +static void stackerror (lua_State *L) { + if (L->nCcalls == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } @@ -390,53 +489,67 @@ int luaD_poscall (lua_State *L, StkId firstResult) { ** When returns, all the results are on the stack, starting at the original ** function position. */ -void luaD_call (lua_State *L, StkId func, int nResults, int allowyield) { - if (++L->nCcalls >= LUAI_MAXCCALLS) { - if (L->nCcalls == LUAI_MAXCCALLS) - luaG_runerror(L, "C stack overflow"); - else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) - luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ - } - if (!allowyield) L->nny++; +void luaD_call (lua_State *L, StkId func, int nResults) { + if (++L->nCcalls >= LUAI_MAXCCALLS) + stackerror(L); if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ luaV_execute(L); /* call it */ - if (!allowyield) L->nny--; L->nCcalls--; } -static void finishCcall (lua_State *L) { +/* +** Similar to 'luaD_call', but does not allow yields during the call +*/ +void luaD_callnoyield (lua_State *L, StkId func, int nResults) { + L->nny++; + luaD_call(L, func, nResults); + L->nny--; +} + + +/* +** Completes the execution of an interrupted C function, calling its +** continuation function. +*/ +static void finishCcall (lua_State *L, int status) { CallInfo *ci = L->ci; int n; - lua_assert(ci->u.c.k != NULL); /* must have a continuation */ - lua_assert(L->nny == 0); + /* must have a continuation and must be able to call it */ + lua_assert(ci->u.c.k != NULL && L->nny == 0); + /* error status can only happen in a protected call */ + lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD); if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ ci->callstatus &= ~CIST_YPCALL; /* finish 'lua_pcall' */ L->errfunc = ci->u.c.old_errfunc; } - /* finish 'lua_callk'/'lua_pcall' */ + /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already + handled */ adjustresults(L, ci->nresults); /* call continuation function */ - if (!(ci->callstatus & CIST_STAT)) /* no call status? */ - ci->u.c.status = LUA_YIELD; /* 'default' status */ - lua_assert(ci->u.c.status != LUA_OK); - ci->callstatus = (ci->callstatus & ~(CIST_YPCALL | CIST_STAT)) | CIST_YIELDED; lua_unlock(L); - n = (*ci->u.c.k)(L); + n = (*ci->u.c.k)(L, status, ci->u.c.ctx); lua_lock(L); api_checknelems(L, n); /* finish 'luaD_precall' */ - luaD_poscall(L, L->top - n); + luaD_poscall(L, ci, L->top - n, n); } +/* +** Executes "full continuation" (everything in the stack) of a +** previously interrupted coroutine until the stack is empty (or another +** interruption long-jumps out of the loop). If the coroutine is +** recovering from an error, 'ud' points to the error status, which must +** be passed to the first continuation function (otherwise the default +** status is LUA_YIELD). +*/ static void unroll (lua_State *L, void *ud) { - UNUSED(ud); - for (;;) { - if (L->ci == &L->base_ci) /* stack is empty? */ - return; /* coroutine finished normally */ + if (ud != NULL) /* error status? */ + finishCcall(L, *(int *)ud); /* finish 'lua_pcallk' callee */ + while (L->ci != &L->base_ci) { /* something in the stack */ if (!isLua(L->ci)) /* C function? */ - finishCcall(L); + finishCcall(L, LUA_YIELD); /* complete its execution */ else { /* Lua function */ luaV_finishOp(L); /* finish interrupted instruction */ luaV_execute(L); /* execute down to higher C 'boundary' */ @@ -446,7 +559,8 @@ static void unroll (lua_State *L, void *ud) { /* -** check whether thread has a suspended protected call +** Try to find a suspended protected call (a "recover point") for the +** given thread. */ static CallInfo *findpcall (lua_State *L) { CallInfo *ci; @@ -458,6 +572,11 @@ static CallInfo *findpcall (lua_State *L) { } +/* +** Recovers from an error in a coroutine. Finds a recover point (if +** there is one) and completes the execution of the interrupted +** 'luaD_pcall'. If there is no recover point, returns zero. +*/ static int recover (lua_State *L, int status) { StkId oldtop; CallInfo *ci = findpcall(L); @@ -467,12 +586,10 @@ static int recover (lua_State *L, int status) { luaF_close(L, oldtop); seterrorobj(L, status, oldtop); L->ci = ci; - L->allowhook = ci->u.c.old_allowhook; + L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ L->nny = 0; /* should be zero to be yieldable */ luaD_shrinkstack(L); L->errfunc = ci->u.c.old_errfunc; - ci->callstatus |= CIST_STAT; /* call has error status */ - ci->u.c.status = status; /* (here it is) */ return 1; /* continue running the coroutine */ } @@ -491,11 +608,16 @@ static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { /* -** do the work for 'lua_resume' in protected mode +** Do the work for 'lua_resume' in protected mode. Most of the work +** depends on the status of the coroutine: initial state, suspended +** inside a hook, or regularly suspended (optionally with a continuation +** function), plus erroneous cases: non-suspended coroutine or dead +** coroutine. */ static void resume (lua_State *L, void *ud) { int nCcalls = L->nCcalls; - StkId firstArg = cast(StkId, ud); + int n = *(cast(int*, ud)); /* number of arguments */ + StkId firstArg = L->top - n; /* first argument */ CallInfo *ci = L->ci; if (nCcalls >= LUAI_MAXCCALLS) resume_error(L, "C stack overflow", firstArg); @@ -509,24 +631,21 @@ static void resume (lua_State *L, void *ud) { else if (L->status != LUA_YIELD) resume_error(L, "cannot resume dead coroutine", firstArg); else { /* resuming from previous yield */ - L->status = LUA_OK; + L->status = LUA_OK; /* mark that it is running (again) */ ci->func = restorestack(L, ci->extra); if (isLua(ci)) /* yielded inside a hook? */ luaV_execute(L); /* just continue running Lua code */ else { /* 'common' yield */ - if (ci->u.c.k != NULL) { /* does it have a continuation? */ - int n; - ci->u.c.status = LUA_YIELD; /* 'default' status */ - ci->callstatus |= CIST_YIELDED; + if (ci->u.c.k != NULL) { /* does it have a continuation function? */ lua_unlock(L); - n = (*ci->u.c.k)(L); /* call continuation */ + n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx); /* call continuation */ lua_lock(L); api_checknelems(L, n); firstArg = L->top - n; /* yield results come from continuation */ } - luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ + luaD_poscall(L, ci, firstArg, n); /* finish 'luaD_precall' */ } - unroll(L, NULL); + unroll(L, NULL); /* run continuation */ } lua_assert(nCcalls == L->nCcalls); } @@ -534,27 +653,26 @@ static void resume (lua_State *L, void *ud) { LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { int status; - int oldnny = L->nny; /* save 'nny' */ + unsigned short oldnny = L->nny; /* save "number of non-yieldable" calls */ lua_lock(L); luai_userstateresume(L, nargs); L->nCcalls = (from) ? from->nCcalls + 1 : 1; L->nny = 0; /* allow yields */ api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); - status = luaD_rawrunprotected(L, resume, L->top - nargs); + status = luaD_rawrunprotected(L, resume, &nargs); if (status == -1) /* error calling 'lua_resume'? */ status = LUA_ERRRUN; - else { /* yield or regular error */ - while (status != LUA_OK && status != LUA_YIELD) { /* error? */ - if (recover(L, status)) /* recover point? */ - status = luaD_rawrunprotected(L, unroll, NULL); /* run continuation */ - else { /* unrecoverable error */ - L->status = cast_byte(status); /* mark thread as `dead' */ - seterrorobj(L, status, L->top); - L->ci->top = L->top; - break; - } + else { /* continue running after recoverable errors */ + while (errorstatus(status) && recover(L, status)) { + /* unroll continuation */ + status = luaD_rawrunprotected(L, unroll, &status); } - lua_assert(status == L->status); + if (errorstatus(status)) { /* unrecoverable error? */ + L->status = cast_byte(status); /* mark thread as 'dead' */ + seterrorobj(L, status, L->top); /* push error message */ + L->ci->top = L->top; + } + else lua_assert(status == L->status); /* normal end or yield */ } L->nny = oldnny; /* restore 'nny' */ L->nCcalls--; @@ -564,7 +682,13 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { } -LUA_API int lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { +LUA_API int lua_isyieldable (lua_State *L) { + return (L->nny == 0); +} + + +LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k) { CallInfo *ci = L->ci; luai_userstateyield(L, nresults); lua_lock(L); @@ -619,7 +743,7 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u, /* ** Execute a protected parser. */ -struct SParser { /* data to `f_parser' */ +struct SParser { /* data to 'f_parser' */ ZIO *z; Mbuffer buff; /* dynamic structure used by the scanner */ Dyndata dyd; /* dynamic structures used by the parser */ @@ -631,31 +755,26 @@ struct SParser { /* data to `f_parser' */ static void checkmode (lua_State *L, const char *mode, const char *x) { if (mode && strchr(mode, x[0]) == NULL) { luaO_pushfstring(L, - "attempt to load a %s chunk (mode is " LUA_QS ")", x, mode); + "attempt to load a %s chunk (mode is '%s')", x, mode); luaD_throw(L, LUA_ERRSYNTAX); } } static void f_parser (lua_State *L, void *ud) { - int i; - Closure *cl; + LClosure *cl; struct SParser *p = cast(struct SParser *, ud); int c = zgetc(p->z); /* read first character */ if (c == LUA_SIGNATURE[0]) { checkmode(L, p->mode, "binary"); - cl = luaU_undump(L, p->z, &p->buff, p->name); + cl = luaU_undump(L, p->z, p->name); } else { checkmode(L, p->mode, "text"); cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); } - lua_assert(cl->l.nupvalues == cl->l.p->sizeupvalues); - for (i = 0; i < cl->l.nupvalues; i++) { /* initialize upvalues */ - UpVal *up = luaF_newupval(L); - cl->l.upvals[i] = up; - luaC_objbarrier(L, cl, up); - } + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luaF_initupvals(L, cl); } diff --git a/depends/lua/src/ldump.c b/depends/lua/src/ldump.c index 61fa2cd89..016e30082 100644 --- a/depends/lua/src/ldump.c +++ b/depends/lua/src/ldump.c @@ -1,173 +1,215 @@ /* -** $Id: ldump.c,v 2.17.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ldump.c,v 2.37 2015/10/08 15:53:49 roberto Exp $ ** save precompiled Lua chunks ** See Copyright Notice in lua.h */ -#include - #define ldump_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "lobject.h" #include "lstate.h" #include "lundump.h" + typedef struct { - lua_State* L; - lua_Writer writer; - void* data; - int strip; - int status; + lua_State *L; + lua_Writer writer; + void *data; + int strip; + int status; } DumpState; -#define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D) -#define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D) - -static void DumpBlock(const void* b, size_t size, DumpState* D) -{ - if (D->status==0) - { - lua_unlock(D->L); - D->status=(*D->writer)(D->L,b,size,D->data); - lua_lock(D->L); - } + +/* +** All high-level dumps go through DumpVector; you can change it to +** change the endianness of the result +*/ +#define DumpVector(v,n,D) DumpBlock(v,(n)*sizeof((v)[0]),D) + +#define DumpLiteral(s,D) DumpBlock(s, sizeof(s) - sizeof(char), D) + + +static void DumpBlock (const void *b, size_t size, DumpState *D) { + if (D->status == 0 && size > 0) { + lua_unlock(D->L); + D->status = (*D->writer)(D->L, b, size, D->data); + lua_lock(D->L); + } } -static void DumpChar(int y, DumpState* D) -{ - char x=(char)y; - DumpVar(x,D); + +#define DumpVar(x,D) DumpVector(&x,1,D) + + +static void DumpByte (int y, DumpState *D) { + lu_byte x = (lu_byte)y; + DumpVar(x, D); } -static void DumpInt(int x, DumpState* D) -{ - DumpVar(x,D); + +static void DumpInt (int x, DumpState *D) { + DumpVar(x, D); } -static void DumpNumber(lua_Number x, DumpState* D) -{ - DumpVar(x,D); + +static void DumpNumber (lua_Number x, DumpState *D) { + DumpVar(x, D); } -static void DumpVector(const void* b, int n, size_t size, DumpState* D) -{ - DumpInt(n,D); - DumpMem(b,n,size,D); + +static void DumpInteger (lua_Integer x, DumpState *D) { + DumpVar(x, D); } -static void DumpString(const TString* s, DumpState* D) -{ - if (s==NULL) - { - size_t size=0; - DumpVar(size,D); - } - else - { - size_t size=s->tsv.len+1; /* include trailing '\0' */ - DumpVar(size,D); - DumpBlock(getstr(s),size*sizeof(char),D); - } + +static void DumpString (const TString *s, DumpState *D) { + if (s == NULL) + DumpByte(0, D); + else { + size_t size = tsslen(s) + 1; /* include trailing '\0' */ + const char *str = getstr(s); + if (size < 0xFF) + DumpByte(cast_int(size), D); + else { + DumpByte(0xFF, D); + DumpVar(size, D); + } + DumpVector(str, size - 1, D); /* no need to save '\0' */ + } } -#define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D) - -static void DumpFunction(const Proto* f, DumpState* D); - -static void DumpConstants(const Proto* f, DumpState* D) -{ - int i,n=f->sizek; - DumpInt(n,D); - for (i=0; ik[i]; - DumpChar(ttypenv(o),D); - switch (ttypenv(o)) - { - case LUA_TNIL: - break; - case LUA_TBOOLEAN: - DumpChar(bvalue(o),D); - break; - case LUA_TNUMBER: - DumpNumber(nvalue(o),D); - break; - case LUA_TSTRING: - DumpString(rawtsvalue(o),D); - break; - default: lua_assert(0); + +static void DumpCode (const Proto *f, DumpState *D) { + DumpInt(f->sizecode, D); + DumpVector(f->code, f->sizecode, D); +} + + +static void DumpFunction(const Proto *f, TString *psource, DumpState *D); + +static void DumpConstants (const Proto *f, DumpState *D) { + int i; + int n = f->sizek; + DumpInt(n, D); + for (i = 0; i < n; i++) { + const TValue *o = &f->k[i]; + DumpByte(ttype(o), D); + switch (ttype(o)) { + case LUA_TNIL: + break; + case LUA_TBOOLEAN: + DumpByte(bvalue(o), D); + break; + case LUA_TNUMFLT: + DumpNumber(fltvalue(o), D); + break; + case LUA_TNUMINT: + DumpInteger(ivalue(o), D); + break; + case LUA_TSHRSTR: + case LUA_TLNGSTR: + DumpString(tsvalue(o), D); + break; + default: + lua_assert(0); + } } - } - n=f->sizep; - DumpInt(n,D); - for (i=0; ip[i],D); } -static void DumpUpvalues(const Proto* f, DumpState* D) -{ - int i,n=f->sizeupvalues; - DumpInt(n,D); - for (i=0; iupvalues[i].instack,D); - DumpChar(f->upvalues[i].idx,D); - } + +static void DumpProtos (const Proto *f, DumpState *D) { + int i; + int n = f->sizep; + DumpInt(n, D); + for (i = 0; i < n; i++) + DumpFunction(f->p[i], f->source, D); +} + + +static void DumpUpvalues (const Proto *f, DumpState *D) { + int i, n = f->sizeupvalues; + DumpInt(n, D); + for (i = 0; i < n; i++) { + DumpByte(f->upvalues[i].instack, D); + DumpByte(f->upvalues[i].idx, D); + } } -static void DumpDebug(const Proto* f, DumpState* D) -{ - int i,n; - DumpString((D->strip) ? NULL : f->source,D); - n= (D->strip) ? 0 : f->sizelineinfo; - DumpVector(f->lineinfo,n,sizeof(int),D); - n= (D->strip) ? 0 : f->sizelocvars; - DumpInt(n,D); - for (i=0; ilocvars[i].varname,D); - DumpInt(f->locvars[i].startpc,D); - DumpInt(f->locvars[i].endpc,D); - } - n= (D->strip) ? 0 : f->sizeupvalues; - DumpInt(n,D); - for (i=0; iupvalues[i].name,D); + +static void DumpDebug (const Proto *f, DumpState *D) { + int i, n; + n = (D->strip) ? 0 : f->sizelineinfo; + DumpInt(n, D); + DumpVector(f->lineinfo, n, D); + n = (D->strip) ? 0 : f->sizelocvars; + DumpInt(n, D); + for (i = 0; i < n; i++) { + DumpString(f->locvars[i].varname, D); + DumpInt(f->locvars[i].startpc, D); + DumpInt(f->locvars[i].endpc, D); + } + n = (D->strip) ? 0 : f->sizeupvalues; + DumpInt(n, D); + for (i = 0; i < n; i++) + DumpString(f->upvalues[i].name, D); } -static void DumpFunction(const Proto* f, DumpState* D) -{ - DumpInt(f->linedefined,D); - DumpInt(f->lastlinedefined,D); - DumpChar(f->numparams,D); - DumpChar(f->is_vararg,D); - DumpChar(f->maxstacksize,D); - DumpCode(f,D); - DumpConstants(f,D); - DumpUpvalues(f,D); - DumpDebug(f,D); + +static void DumpFunction (const Proto *f, TString *psource, DumpState *D) { + if (D->strip || f->source == psource) + DumpString(NULL, D); /* no debug info or same source as its parent */ + else + DumpString(f->source, D); + DumpInt(f->linedefined, D); + DumpInt(f->lastlinedefined, D); + DumpByte(f->numparams, D); + DumpByte(f->is_vararg, D); + DumpByte(f->maxstacksize, D); + DumpCode(f, D); + DumpConstants(f, D); + DumpUpvalues(f, D); + DumpProtos(f, D); + DumpDebug(f, D); } -static void DumpHeader(DumpState* D) -{ - lu_byte h[LUAC_HEADERSIZE]; - luaU_header(h); - DumpBlock(h,LUAC_HEADERSIZE,D); + +static void DumpHeader (DumpState *D) { + DumpLiteral(LUA_SIGNATURE, D); + DumpByte(LUAC_VERSION, D); + DumpByte(LUAC_FORMAT, D); + DumpLiteral(LUAC_DATA, D); + DumpByte(sizeof(int), D); + DumpByte(sizeof(size_t), D); + DumpByte(sizeof(Instruction), D); + DumpByte(sizeof(lua_Integer), D); + DumpByte(sizeof(lua_Number), D); + DumpInteger(LUAC_INT, D); + DumpNumber(LUAC_NUM, D); } + /* ** dump Lua function as precompiled chunk */ -int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) -{ - DumpState D; - D.L=L; - D.writer=w; - D.data=data; - D.strip=strip; - D.status=0; - DumpHeader(&D); - DumpFunction(f,&D); - return D.status; +int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data, + int strip) { + DumpState D; + D.L = L; + D.writer = w; + D.data = data; + D.strip = strip; + D.status = 0; + DumpHeader(&D); + DumpByte(f->sizeupvalues, &D); + DumpFunction(f, NULL, &D); + return D.status; } + diff --git a/depends/lua/src/lfunc.c b/depends/lua/src/lfunc.c index e90e1520c..67967dab3 100644 --- a/depends/lua/src/lfunc.c +++ b/depends/lua/src/lfunc.c @@ -1,15 +1,17 @@ /* -** $Id: lfunc.c,v 2.30.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lfunc.c,v 2.45 2014/11/02 19:19:04 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ - -#include - #define lfunc_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "lfunc.h" @@ -20,95 +22,83 @@ -Closure *luaF_newCclosure (lua_State *L, int n) { - Closure *c = &luaC_newobj(L, LUA_TCCL, sizeCclosure(n), NULL, 0)->cl; - c->c.nupvalues = cast_byte(n); +CClosure *luaF_newCclosure (lua_State *L, int n) { + GCObject *o = luaC_newobj(L, LUA_TCCL, sizeCclosure(n)); + CClosure *c = gco2ccl(o); + c->nupvalues = cast_byte(n); return c; } -Closure *luaF_newLclosure (lua_State *L, int n) { - Closure *c = &luaC_newobj(L, LUA_TLCL, sizeLclosure(n), NULL, 0)->cl; - c->l.p = NULL; - c->l.nupvalues = cast_byte(n); - while (n--) c->l.upvals[n] = NULL; +LClosure *luaF_newLclosure (lua_State *L, int n) { + GCObject *o = luaC_newobj(L, LUA_TLCL, sizeLclosure(n)); + LClosure *c = gco2lcl(o); + c->p = NULL; + c->nupvalues = cast_byte(n); + while (n--) c->upvals[n] = NULL; return c; } - -UpVal *luaF_newupval (lua_State *L) { - UpVal *uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), NULL, 0)->uv; - uv->v = &uv->u.value; - setnilvalue(uv->v); - return uv; +/* +** fill a closure with new closed upvalues +*/ +void luaF_initupvals (lua_State *L, LClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) { + UpVal *uv = luaM_new(L, UpVal); + uv->refcount = 1; + uv->v = &uv->u.value; /* make it closed */ + setnilvalue(uv->v); + cl->upvals[i] = uv; + } } UpVal *luaF_findupval (lua_State *L, StkId level) { - global_State *g = G(L); - GCObject **pp = &L->openupval; + UpVal **pp = &L->openupval; UpVal *p; UpVal *uv; - while (*pp != NULL && (p = gco2uv(*pp))->v >= level) { - GCObject *o = obj2gco(p); - lua_assert(p->v != &p->u.value); - lua_assert(!isold(o) || isold(obj2gco(L))); - if (p->v == level) { /* found a corresponding upvalue? */ - if (isdead(g, o)) /* is it dead? */ - changewhite(o); /* resurrect it */ - return p; - } - pp = &p->next; + lua_assert(isintwups(L) || L->openupval == NULL); + while (*pp != NULL && (p = *pp)->v >= level) { + lua_assert(upisopen(p)); + if (p->v == level) /* found a corresponding upvalue? */ + return p; /* return it */ + pp = &p->u.open.next; } - /* not found: create a new one */ - uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), pp, 0)->uv; + /* not found: create a new upvalue */ + uv = luaM_new(L, UpVal); + uv->refcount = 0; + uv->u.open.next = *pp; /* link it to list of open upvalues */ + uv->u.open.touched = 1; + *pp = uv; uv->v = level; /* current value lives in the stack */ - uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ - uv->u.l.next = g->uvhead.u.l.next; - uv->u.l.next->u.l.prev = uv; - g->uvhead.u.l.next = uv; - lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ + L->twups = G(L)->twups; /* link it to the list */ + G(L)->twups = L; + } return uv; } -static void unlinkupval (UpVal *uv) { - lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); - uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */ - uv->u.l.prev->u.l.next = uv->u.l.next; -} - - -void luaF_freeupval (lua_State *L, UpVal *uv) { - if (uv->v != &uv->u.value) /* is it open? */ - unlinkupval(uv); /* remove from open list */ - luaM_free(L, uv); /* free upvalue */ -} - - void luaF_close (lua_State *L, StkId level) { UpVal *uv; - global_State *g = G(L); - while (L->openupval != NULL && (uv = gco2uv(L->openupval))->v >= level) { - GCObject *o = obj2gco(uv); - lua_assert(!isblack(o) && uv->v != &uv->u.value); - L->openupval = uv->next; /* remove from `open' list */ - if (isdead(g, o)) - luaF_freeupval(L, uv); /* free upvalue */ + while (L->openupval != NULL && (uv = L->openupval)->v >= level) { + lua_assert(upisopen(uv)); + L->openupval = uv->u.open.next; /* remove from 'open' list */ + if (uv->refcount == 0) /* no references? */ + luaM_free(L, uv); /* free upvalue */ else { - unlinkupval(uv); /* remove upvalue from 'uvhead' list */ setobj(L, &uv->u.value, uv->v); /* move value to upvalue slot */ uv->v = &uv->u.value; /* now current value lives here */ - gch(o)->next = g->allgc; /* link upvalue into 'allgc' list */ - g->allgc = o; - luaC_checkupvalcolor(g, uv); + luaC_upvalbarrier(L, uv); } } } Proto *luaF_newproto (lua_State *L) { - Proto *f = &luaC_newobj(L, LUA_TPROTO, sizeof(Proto), NULL, 0)->p; + GCObject *o = luaC_newobj(L, LUA_TPROTO, sizeof(Proto)); + Proto *f = gco2p(o); f->k = NULL; f->sizek = 0; f->p = NULL; @@ -144,7 +134,7 @@ void luaF_freeproto (lua_State *L, Proto *f) { /* -** Look for n-th local variable at line `line' in function `func'. +** Look for n-th local variable at line 'line' in function 'func'. ** Returns NULL if not found. */ const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { diff --git a/depends/lua/src/lgc.c b/depends/lua/src/lgc.c index 52460dcdd..7c29fb030 100644 --- a/depends/lua/src/lgc.c +++ b/depends/lua/src/lgc.c @@ -1,14 +1,17 @@ /* -** $Id: lgc.c,v 2.140.1.2 2013/04/26 18:22:05 roberto Exp $ +** $Id: lgc.c,v 2.212 2016/03/31 19:02:03 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ -#include - #define lgc_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "ldebug.h" @@ -23,6 +26,11 @@ #include "ltm.h" +/* +** internal state for collector while inside the atomic phase. The +** collector should never be in this state while running regular code. +*/ +#define GCSinsideatomic (GCSpause + 1) /* ** cost of sweeping one element (the size of a small object divided @@ -33,8 +41,8 @@ /* maximum number of elements to sweep in each single step */ #define GCSWEEPMAX (cast_int((GCSTEPSIZE / GCSWEEPCOST) / 4)) -/* maximum number of finalizers to call in each GC step */ -#define GCFINALIZENUM 4 +/* cost of calling one finalizer */ +#define GCFINALIZECOST GCSWEEPCOST /* @@ -52,18 +60,18 @@ /* -** 'makewhite' erases all color bits plus the old bit and then -** sets only the current white bit +** 'makewhite' erases all color bits then sets only the current white +** bit */ -#define maskcolors (~(bit2mask(BLACKBIT, OLDBIT) | WHITEBITS)) +#define maskcolors (~(bitmask(BLACKBIT) | WHITEBITS)) #define makewhite(g,x) \ - (gch(x)->marked = cast_byte((gch(x)->marked & maskcolors) | luaC_white(g))) + (x->marked = cast_byte((x->marked & maskcolors) | luaC_white(g))) -#define white2gray(x) resetbits(gch(x)->marked, WHITEBITS) -#define black2gray(x) resetbit(gch(x)->marked, BLACKBIT) +#define white2gray(x) resetbits(x->marked, WHITEBITS) +#define black2gray(x) resetbit(x->marked, BLACKBIT) -#define isfinalized(x) testbit(gch(x)->marked, FINALIZEDBIT) +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) #define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n))) @@ -75,8 +83,13 @@ #define markvalue(g,o) { checkconsistency(o); \ if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } -#define markobject(g,t) { if ((t) && iswhite(obj2gco(t))) \ - reallymarkobject(g, obj2gco(t)); } +#define markobject(g,t) { if (iswhite(t)) reallymarkobject(g, obj2gco(t)); } + +/* +** mark an object that can be NULL (either because it is really optional, +** or it was stripped as debug info, or inside an uncompleted structure) +*/ +#define markobjectN(g,t) { if (t) markobject(g,t); } static void reallymarkobject (global_State *g, GCObject *o); @@ -95,33 +108,38 @@ static void reallymarkobject (global_State *g, GCObject *o); /* -** link table 'h' into list pointed by 'p' +** link collectable object 'o' into list pointed by 'p' */ -#define linktable(h,p) ((h)->gclist = *(p), *(p) = obj2gco(h)) +#define linkgclist(o,p) ((o)->gclist = (p), (p) = obj2gco(o)) /* -** if key is not marked, mark its entry as dead (therefore removing it -** from the table) +** If key is not marked, mark its entry as dead. This allows key to be +** collected, but keeps its entry in the table. A dead node is needed +** when Lua looks up for a key (it may be part of a chain) and when +** traversing a weak table (key might be removed from the table during +** traversal). Other places never manipulate dead keys, because its +** associated nil value is enough to signal that the entry is logically +** empty. */ static void removeentry (Node *n) { lua_assert(ttisnil(gval(n))); if (valiswhite(gkey(n))) - setdeadvalue(gkey(n)); /* unused and unmarked key; remove it */ + setdeadvalue(wgkey(n)); /* unused and unmarked key; remove it */ } /* ** tells whether a key or value can be cleared from a weak ** table. Non-collectable objects are never removed from weak -** tables. Strings behave as `values', so are never removed too. for +** tables. Strings behave as 'values', so are never removed too. for ** other objects: if really collected, cannot keep them; for objects ** being finalized, keep them in keys, but not in values */ static int iscleared (global_State *g, const TValue *o) { if (!iscollectable(o)) return 0; else if (ttisstring(o)) { - markobject(g, rawtsvalue(o)); /* strings are `values', so are never weak */ + markobject(g, tsvalue(o)); /* strings are 'values', so are never weak */ return 0; } else return iswhite(gcvalue(o)); @@ -130,14 +148,14 @@ static int iscleared (global_State *g, const TValue *o) { /* ** barrier that moves collector forward, that is, mark the white object -** being pointed by a black object. +** being pointed by a black object. (If in sweep phase, clear the black +** object to white [sweep it] to avoid other barrier calls for this +** same object.) */ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { global_State *g = G(L); lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); - lua_assert(g->gcstate != GCSpause); - lua_assert(gch(o)->tt != LUA_TTABLE); - if (keepinvariantout(g)) /* must keep invariant? */ + if (keepinvariant(g)) /* must keep invariant? */ reallymarkobject(g, v); /* restore invariant */ else { /* sweep phase */ lua_assert(issweepphase(g)); @@ -148,78 +166,52 @@ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { /* ** barrier that moves collector backward, that is, mark the black object -** pointing to a white object as gray again. (Current implementation -** only works for tables; access to 'gclist' is not uniform across -** different types.) +** pointing to a white object as gray again. */ -void luaC_barrierback_ (lua_State *L, GCObject *o) { +void luaC_barrierback_ (lua_State *L, Table *t) { global_State *g = G(L); - lua_assert(isblack(o) && !isdead(g, o) && gch(o)->tt == LUA_TTABLE); - black2gray(o); /* make object gray (again) */ - gco2t(o)->gclist = g->grayagain; - g->grayagain = o; + lua_assert(isblack(t) && !isdead(g, t)); + black2gray(t); /* make table gray (again) */ + linkgclist(t, g->grayagain); } /* -** barrier for prototypes. When creating first closure (cache is -** NULL), use a forward barrier; this may be the only closure of the -** prototype (if it is a "regular" function, with a single instance) -** and the prototype may be big, so it is better to avoid traversing -** it again. Otherwise, use a backward barrier, to avoid marking all -** possible instances. +** barrier for assignments to closed upvalues. Because upvalues are +** shared among closures, it is impossible to know the color of all +** closures pointing to it. So, we assume that the object being assigned +** must be marked. */ -LUAI_FUNC void luaC_barrierproto_ (lua_State *L, Proto *p, Closure *c) { +void luaC_upvalbarrier_ (lua_State *L, UpVal *uv) { global_State *g = G(L); - lua_assert(isblack(obj2gco(p))); - if (p->cache == NULL) { /* first time? */ - luaC_objbarrier(L, p, c); - } - else { /* use a backward barrier */ - black2gray(obj2gco(p)); /* make prototype gray (again) */ - p->gclist = g->grayagain; - g->grayagain = obj2gco(p); - } + GCObject *o = gcvalue(uv->v); + lua_assert(!upisopen(uv)); /* ensured by macro luaC_upvalbarrier */ + if (keepinvariant(g)) + markobject(g, o); } -/* -** check color (and invariants) for an upvalue that was closed, -** i.e., moved into the 'allgc' list -*/ -void luaC_checkupvalcolor (global_State *g, UpVal *uv) { - GCObject *o = obj2gco(uv); - lua_assert(!isblack(o)); /* open upvalues are never black */ - if (isgray(o)) { - if (keepinvariant(g)) { - resetoldbit(o); /* see MOVE OLD rule */ - gray2black(o); /* it is being visited now */ - markvalue(g, uv->v); - } - else { - lua_assert(issweepphase(g)); - makewhite(g, o); - } - } +void luaC_fix (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ + white2gray(o); /* they will be gray forever */ + g->allgc = o->next; /* remove object from 'allgc' list */ + o->next = g->fixedgc; /* link it to 'fixedgc' list */ + g->fixedgc = o; } /* ** create a new collectable object (with given type and size) and link -** it to '*list'. 'offset' tells how many bytes to allocate before the -** object itself (used only by states). +** it to 'allgc' list. */ -GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, GCObject **list, - int offset) { +GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { global_State *g = G(L); - char *raw = cast(char *, luaM_newobject(L, novariant(tt), sz)); - GCObject *o = obj2gco(raw + offset); - if (list == NULL) - list = &g->allgc; /* standard list for collectable objects */ - gch(o)->marked = luaC_white(g); - gch(o)->tt = tt; - gch(o)->next = *list; - *list = o; + GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz)); + o->marked = luaC_white(g); + o->tt = tt; + o->next = g->allgc; + g->allgc = o; return o; } @@ -241,57 +233,53 @@ GCObject *luaC_newobj (lua_State *L, int tt, size_t sz, GCObject **list, ** upvalues are already linked in 'headuv' list.) */ static void reallymarkobject (global_State *g, GCObject *o) { - lu_mem size; + reentry: white2gray(o); - switch (gch(o)->tt) { - case LUA_TSHRSTR: - case LUA_TLNGSTR: { - size = sizestring(gco2ts(o)); - break; /* nothing else to mark; make it black */ + switch (o->tt) { + case LUA_TSHRSTR: { + gray2black(o); + g->GCmemtrav += sizelstring(gco2ts(o)->shrlen); + break; } - case LUA_TUSERDATA: { - Table *mt = gco2u(o)->metatable; - markobject(g, mt); - markobject(g, gco2u(o)->env); - size = sizeudata(gco2u(o)); + case LUA_TLNGSTR: { + gray2black(o); + g->GCmemtrav += sizelstring(gco2ts(o)->u.lnglen); break; } - case LUA_TUPVAL: { - UpVal *uv = gco2uv(o); - markvalue(g, uv->v); - if (uv->v != &uv->u.value) /* open? */ - return; /* open upvalues remain gray */ - size = sizeof(UpVal); + case LUA_TUSERDATA: { + TValue uvalue; + markobjectN(g, gco2u(o)->metatable); /* mark its metatable */ + gray2black(o); + g->GCmemtrav += sizeudata(gco2u(o)); + getuservalue(g->mainthread, gco2u(o), &uvalue); + if (valiswhite(&uvalue)) { /* markvalue(g, &uvalue); */ + o = gcvalue(&uvalue); + goto reentry; + } break; } case LUA_TLCL: { - gco2lcl(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2lcl(o), g->gray); + break; } case LUA_TCCL: { - gco2ccl(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2ccl(o), g->gray); + break; } case LUA_TTABLE: { - linktable(gco2t(o), &g->gray); - return; + linkgclist(gco2t(o), g->gray); + break; } case LUA_TTHREAD: { - gco2th(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2th(o), g->gray); + break; } case LUA_TPROTO: { - gco2p(o)->gclist = g->gray; - g->gray = o; - return; + linkgclist(gco2p(o), g->gray); + break; } - default: lua_assert(0); return; + default: lua_assert(0); break; } - gray2black(o); - g->GCmemtrav += size; } @@ -301,7 +289,7 @@ static void reallymarkobject (global_State *g, GCObject *o) { static void markmt (global_State *g) { int i; for (i=0; i < LUA_NUMTAGS; i++) - markobject(g, g->mt[i]); + markobjectN(g, g->mt[i]); } @@ -310,29 +298,41 @@ static void markmt (global_State *g) { */ static void markbeingfnz (global_State *g) { GCObject *o; - for (o = g->tobefnz; o != NULL; o = gch(o)->next) { - makewhite(g, o); - reallymarkobject(g, o); - } + for (o = g->tobefnz; o != NULL; o = o->next) + markobject(g, o); } /* -** mark all values stored in marked open upvalues. (See comment in -** 'lstate.h'.) +** Mark all values stored in marked open upvalues from non-marked threads. +** (Values from marked threads were already marked when traversing the +** thread.) Remove from the list threads that no longer have upvalues and +** not-marked threads. */ static void remarkupvals (global_State *g) { - UpVal *uv; - for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { - if (isgray(obj2gco(uv))) - markvalue(g, uv->v); + lua_State *thread; + lua_State **p = &g->twups; + while ((thread = *p) != NULL) { + lua_assert(!isblack(thread)); /* threads are never black */ + if (isgray(thread) && thread->openupval != NULL) + p = &thread->twups; /* keep marked thread with upvalues in the list */ + else { /* thread is not marked or without upvalues */ + UpVal *uv; + *p = thread->twups; /* remove thread from the list */ + thread->twups = thread; /* mark that it is out of list */ + for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) { + if (uv->u.open.touched) { + markvalue(g, uv->v); /* remark upvalue's value */ + uv->u.open.touched = 0; + } + } + } } } /* -** mark root set and reset all gray lists, to start a new -** incremental (or full) collection +** mark root set and reset all gray lists, to start a new collection */ static void restartcollection (global_State *g) { g->gray = g->grayagain = NULL; @@ -352,12 +352,18 @@ static void restartcollection (global_State *g) { ** ======================================================= */ +/* +** Traverse a table with weak values and link it to proper list. During +** propagate phase, keep it in 'grayagain' list, to be revisited in the +** atomic phase. In the atomic phase, if table has any white value, +** put it in 'weak' list, to be cleared. +*/ static void traverseweakvalue (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); - /* if there is array part, assume it may have white values (do not - traverse it just to check) */ + /* if there is array part, assume it may have white values (it is not + worth traversing it now just to check) */ int hasclears = (h->sizearray > 0); - for (n = gnode(h, 0); n < limit; n++) { + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ checkdeadkey(n); if (ttisnil(gval(n))) /* entry is empty? */ removeentry(n); /* remove it */ @@ -368,20 +374,30 @@ static void traverseweakvalue (global_State *g, Table *h) { hasclears = 1; /* table will have to be cleared */ } } - if (hasclears) - linktable(h, &g->weak); /* has to be cleared later */ - else /* no white values */ - linktable(h, &g->grayagain); /* no need to clean */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasclears) + linkgclist(h, g->weak); /* has to be cleared later */ } +/* +** Traverse an ephemeron table and link it to proper list. Returns true +** iff any object was marked during this traversal (which implies that +** convergence has to continue). During propagation phase, keep table +** in 'grayagain' list, to be visited again in the atomic phase. In +** the atomic phase, if table has any white->white entry, it has to +** be revisited during ephemeron convergence (as that key may turn +** black). Otherwise, if it has any white key, table has to be cleared +** (in the atomic phase). +*/ static int traverseephemeron (global_State *g, Table *h) { int marked = 0; /* true if an object is marked in this traversal */ int hasclears = 0; /* true if table has white keys */ - int prop = 0; /* true if table has entry "white-key -> white-value" */ + int hasww = 0; /* true if table has entry "white-key -> white-value" */ Node *n, *limit = gnodelast(h); - int i; - /* traverse array part (numeric keys are 'strong') */ + unsigned int i; + /* traverse array part */ for (i = 0; i < h->sizearray; i++) { if (valiswhite(&h->array[i])) { marked = 1; @@ -396,26 +412,27 @@ static int traverseephemeron (global_State *g, Table *h) { else if (iscleared(g, gkey(n))) { /* key is not marked (yet)? */ hasclears = 1; /* table must be cleared */ if (valiswhite(gval(n))) /* value not marked yet? */ - prop = 1; /* must propagate again */ + hasww = 1; /* white-white entry */ } else if (valiswhite(gval(n))) { /* value not marked yet? */ marked = 1; reallymarkobject(g, gcvalue(gval(n))); /* mark it now */ } } - if (prop) - linktable(h, &g->ephemeron); /* have to propagate again */ - else if (hasclears) /* does table have white keys? */ - linktable(h, &g->allweak); /* may have to clean white keys */ - else /* no white keys */ - linktable(h, &g->grayagain); /* no need to clean */ + /* link table into proper list */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasww) /* table has white->white entries? */ + linkgclist(h, g->ephemeron); /* have to propagate again */ + else if (hasclears) /* table has white keys? */ + linkgclist(h, g->allweak); /* may have to clean white keys */ return marked; } static void traversestrongtable (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); - int i; + unsigned int i; for (i = 0; i < h->sizearray; i++) /* traverse array part */ markvalue(g, &h->array[i]); for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ @@ -434,18 +451,18 @@ static void traversestrongtable (global_State *g, Table *h) { static lu_mem traversetable (global_State *g, Table *h) { const char *weakkey, *weakvalue; const TValue *mode = gfasttm(g, h->metatable, TM_MODE); - markobject(g, h->metatable); + markobjectN(g, h->metatable); if (mode && ttisstring(mode) && /* is there a weak mode? */ ((weakkey = strchr(svalue(mode), 'k')), (weakvalue = strchr(svalue(mode), 'v')), (weakkey || weakvalue))) { /* is really weak? */ - black2gray(obj2gco(h)); /* keep table gray */ + black2gray(h); /* keep table gray */ if (!weakkey) /* strong keys? */ traverseweakvalue(g, h); else if (!weakvalue) /* strong values? */ traverseephemeron(g, h); else /* all weak */ - linktable(h, &g->allweak); /* nothing to traverse now */ + linkgclist(h, g->allweak); /* nothing to traverse now */ } else /* not weak */ traversestrongtable(g, h); @@ -454,19 +471,24 @@ static lu_mem traversetable (global_State *g, Table *h) { } +/* +** Traverse a prototype. (While a prototype is being build, its +** arrays can be larger than needed; the extra slots are filled with +** NULL, so the use of 'markobjectN') +*/ static int traverseproto (global_State *g, Proto *f) { int i; - if (f->cache && iswhite(obj2gco(f->cache))) + if (f->cache && iswhite(f->cache)) f->cache = NULL; /* allow cache to be collected */ - markobject(g, f->source); + markobjectN(g, f->source); for (i = 0; i < f->sizek; i++) /* mark literals */ markvalue(g, &f->k[i]); for (i = 0; i < f->sizeupvalues; i++) /* mark upvalue names */ - markobject(g, f->upvalues[i].name); + markobjectN(g, f->upvalues[i].name); for (i = 0; i < f->sizep; i++) /* mark nested protos */ - markobject(g, f->p[i]); + markobjectN(g, f->p[i]); for (i = 0; i < f->sizelocvars; i++) /* mark local-variable names */ - markobject(g, f->locvars[i].varname); + markobjectN(g, f->locvars[i].varname); return sizeof(Proto) + sizeof(Instruction) * f->sizecode + sizeof(Proto *) * f->sizep + sizeof(TValue) * f->sizek + @@ -483,34 +505,50 @@ static lu_mem traverseCclosure (global_State *g, CClosure *cl) { return sizeCclosure(cl->nupvalues); } +/* +** open upvalues point to values in a thread, so those values should +** be marked when the thread is traversed except in the atomic phase +** (because then the value cannot be changed by the thread and the +** thread may not be traversed again) +*/ static lu_mem traverseLclosure (global_State *g, LClosure *cl) { int i; - markobject(g, cl->p); /* mark its prototype */ - for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ - markobject(g, cl->upvals[i]); + markobjectN(g, cl->p); /* mark its prototype */ + for (i = 0; i < cl->nupvalues; i++) { /* mark its upvalues */ + UpVal *uv = cl->upvals[i]; + if (uv != NULL) { + if (upisopen(uv) && g->gcstate != GCSinsideatomic) + uv->u.open.touched = 1; /* can be marked in 'remarkupvals' */ + else + markvalue(g, uv->v); + } + } return sizeLclosure(cl->nupvalues); } -static lu_mem traversestack (global_State *g, lua_State *th) { - int n = 0; +static lu_mem traversethread (global_State *g, lua_State *th) { StkId o = th->stack; if (o == NULL) return 1; /* stack not completely built yet */ + lua_assert(g->gcstate == GCSinsideatomic || + th->openupval == NULL || isintwups(th)); for (; o < th->top; o++) /* mark live elements in the stack */ markvalue(g, o); - if (g->gcstate == GCSatomic) { /* final traversal? */ + if (g->gcstate == GCSinsideatomic) { /* final traversal? */ StkId lim = th->stack + th->stacksize; /* real end of stack */ for (; o < lim; o++) /* clear not-marked stack slice */ setnilvalue(o); + /* 'remarkupvals' may have removed thread from 'twups' list */ + if (!isintwups(th) && th->openupval != NULL) { + th->twups = g->twups; /* link it back to the list */ + g->twups = th; + } } - else { /* count call infos to compute size */ - CallInfo *ci; - for (ci = &th->base_ci; ci != th->ci; ci = ci->next) - n++; - } - return sizeof(lua_State) + sizeof(TValue) * th->stacksize + - sizeof(CallInfo) * n; + else if (g->gckind != KGC_EMERGENCY) + luaD_shrinkstack(th); /* do not change stack in emergency cycle */ + return (sizeof(lua_State) + sizeof(TValue) * th->stacksize + + sizeof(CallInfo) * th->nci); } @@ -523,7 +561,7 @@ static void propagatemark (global_State *g) { GCObject *o = g->gray; lua_assert(isgray(o)); gray2black(o); - switch (gch(o)->tt) { + switch (o->tt) { case LUA_TTABLE: { Table *h = gco2t(o); g->gray = h->gclist; /* remove from 'gray' list */ @@ -545,10 +583,9 @@ static void propagatemark (global_State *g) { case LUA_TTHREAD: { lua_State *th = gco2th(o); g->gray = th->gclist; /* remove from 'gray' list */ - th->gclist = g->grayagain; - g->grayagain = o; /* insert into 'grayagain' list */ + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ black2gray(o); - size = traversestack(g, th); + size = traversethread(g, th); break; } case LUA_TPROTO: { @@ -568,35 +605,12 @@ static void propagateall (global_State *g) { } -static void propagatelist (global_State *g, GCObject *l) { - lua_assert(g->gray == NULL); /* no grays left */ - g->gray = l; - propagateall(g); /* traverse all elements from 'l' */ -} - -/* -** retraverse all gray lists. Because tables may be reinserted in other -** lists when traversed, traverse the original lists to avoid traversing -** twice the same table (which is not wrong, but inefficient) -*/ -static void retraversegrays (global_State *g) { - GCObject *weak = g->weak; /* save original lists */ - GCObject *grayagain = g->grayagain; - GCObject *ephemeron = g->ephemeron; - g->weak = g->grayagain = g->ephemeron = NULL; - propagateall(g); /* traverse main gray list */ - propagatelist(g, grayagain); - propagatelist(g, weak); - propagatelist(g, ephemeron); -} - - static void convergeephemerons (global_State *g) { int changed; do { GCObject *w; GCObject *next = g->ephemeron; /* get ephemeron list */ - g->ephemeron = NULL; /* tables will return to this list when traversed */ + g->ephemeron = NULL; /* tables may return to this list when traversed */ changed = 0; while ((w = next) != NULL) { next = gco2t(w)->gclist; @@ -644,7 +658,7 @@ static void clearvalues (global_State *g, GCObject *l, GCObject *f) { for (; l != f; l = gco2t(l)->gclist) { Table *h = gco2t(l); Node *n, *limit = gnodelast(h); - int i; + unsigned int i; for (i = 0; i < h->sizearray; i++) { TValue *o = &h->array[i]; if (iscleared(g, o)) /* value was collected? */ @@ -660,26 +674,45 @@ static void clearvalues (global_State *g, GCObject *l, GCObject *f) { } +void luaC_upvdeccount (lua_State *L, UpVal *uv) { + lua_assert(uv->refcount > 0); + uv->refcount--; + if (uv->refcount == 0 && !upisopen(uv)) + luaM_free(L, uv); +} + + +static void freeLclosure (lua_State *L, LClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) { + UpVal *uv = cl->upvals[i]; + if (uv) + luaC_upvdeccount(L, uv); + } + luaM_freemem(L, cl, sizeLclosure(cl->nupvalues)); +} + + static void freeobj (lua_State *L, GCObject *o) { - switch (gch(o)->tt) { + switch (o->tt) { case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; case LUA_TLCL: { - luaM_freemem(L, o, sizeLclosure(gco2lcl(o)->nupvalues)); + freeLclosure(L, gco2lcl(o)); break; } case LUA_TCCL: { luaM_freemem(L, o, sizeCclosure(gco2ccl(o)->nupvalues)); break; } - case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break; case LUA_TTABLE: luaH_free(L, gco2t(o)); break; case LUA_TTHREAD: luaE_freethread(L, gco2th(o)); break; case LUA_TUSERDATA: luaM_freemem(L, o, sizeudata(gco2u(o))); break; case LUA_TSHRSTR: - G(L)->strt.nuse--; - /* go through */ + luaS_remove(L, gco2ts(o)); /* remove it from hash table */ + luaM_freemem(L, o, sizelstring(gco2ts(o)->shrlen)); + break; case LUA_TLNGSTR: { - luaM_freemem(L, o, sizestring(gco2ts(o))); + luaM_freemem(L, o, sizelstring(gco2ts(o)->u.lnglen)); break; } default: lua_assert(0); @@ -691,61 +724,27 @@ static void freeobj (lua_State *L, GCObject *o) { static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count); -/* -** sweep the (open) upvalues of a thread and resize its stack and -** list of call-info structures. -*/ -static void sweepthread (lua_State *L, lua_State *L1) { - if (L1->stack == NULL) return; /* stack not completely built yet */ - sweepwholelist(L, &L1->openupval); /* sweep open upvalues */ - luaE_freeCI(L1); /* free extra CallInfo slots */ - /* should not change the stack during an emergency gc cycle */ - if (G(L)->gckind != KGC_EMERGENCY) - luaD_shrinkstack(L1); -} - - /* ** sweep at most 'count' elements from a list of GCObjects erasing dead -** objects, where a dead (not alive) object is one marked with the "old" -** (non current) white and not fixed. -** In non-generational mode, change all non-dead objects back to white, -** preparing for next collection cycle. -** In generational mode, keep black objects black, and also mark them as -** old; stop when hitting an old object, as all objects after that -** one will be old too. -** When object is a thread, sweep its list of open upvalues too. +** objects, where a dead object is one marked with the old (non current) +** white; change all non-dead objects back to white, preparing for next +** collection cycle. Return where to continue the traversal or NULL if +** list is finished. */ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { global_State *g = G(L); int ow = otherwhite(g); - int toclear, toset; /* bits to clear and to set in all live objects */ - int tostop; /* stop sweep when this is true */ - if (isgenerational(g)) { /* generational mode? */ - toclear = ~0; /* clear nothing */ - toset = bitmask(OLDBIT); /* set the old bit of all surviving objects */ - tostop = bitmask(OLDBIT); /* do not sweep old generation */ - } - else { /* normal mode */ - toclear = maskcolors; /* clear all color bits + old bit */ - toset = luaC_white(g); /* make object white */ - tostop = 0; /* do not stop */ - } + int white = luaC_white(g); /* current white */ while (*p != NULL && count-- > 0) { GCObject *curr = *p; - int marked = gch(curr)->marked; + int marked = curr->marked; if (isdeadm(ow, marked)) { /* is 'curr' dead? */ - *p = gch(curr)->next; /* remove 'curr' from list */ + *p = curr->next; /* remove 'curr' from list */ freeobj(L, curr); /* erase 'curr' */ } - else { - if (testbits(marked, tostop)) - return NULL; /* stop sweeping this list */ - if (gch(curr)->tt == LUA_TTHREAD) - sweepthread(L, gco2th(curr)); /* sweep thread's upvalues */ - /* update marks */ - gch(curr)->marked = cast_byte((marked & toclear) | toset); - p = &gch(curr)->next; /* go to next element */ + else { /* change mark to 'white' */ + curr->marked = cast_byte((marked & maskcolors) | white); + p = &curr->next; /* go to next element */ } } return (*p == NULL) ? NULL : p; @@ -755,14 +754,11 @@ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { /* ** sweep a list until a live object (or end of list) */ -static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { - GCObject ** old = p; - int i = 0; +static GCObject **sweeptolive (lua_State *L, GCObject **p) { + GCObject **old = p; do { - i++; p = sweeplist(L, p, 1); } while (p == old); - if (n) *n += i; return p; } @@ -775,26 +771,27 @@ static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { ** ======================================================= */ -static void checkSizes (lua_State *L) { - global_State *g = G(L); - if (g->gckind != KGC_EMERGENCY) { /* do not change sizes in emergency */ - int hs = g->strt.size / 2; /* half the size of the string table */ - if (g->strt.nuse < cast(lu_int32, hs)) /* using less than that half? */ - luaS_resize(L, hs); /* halve its size */ - luaZ_freebuffer(L, &g->buff); /* free concatenation buffer */ +/* +** If possible, shrink string table +*/ +static void checkSizes (lua_State *L, global_State *g) { + if (g->gckind != KGC_EMERGENCY) { + l_mem olddebt = g->GCdebt; + if (g->strt.nuse < g->strt.size / 4) /* string table too big? */ + luaS_resize(L, g->strt.size / 2); /* shrink it a little */ + g->GCestimate += g->GCdebt - olddebt; /* update estimate */ } } static GCObject *udata2finalize (global_State *g) { GCObject *o = g->tobefnz; /* get first element */ - lua_assert(isfinalized(o)); - g->tobefnz = gch(o)->next; /* remove it from 'tobefnz' list */ - gch(o)->next = g->allgc; /* return it to 'allgc' list */ + lua_assert(tofinalize(o)); + g->tobefnz = o->next; /* remove it from 'tobefnz' list */ + o->next = g->allgc; /* return it to 'allgc' list */ g->allgc = o; - resetbit(gch(o)->marked, SEPARATED); /* mark that it is not in 'tobefnz' */ - lua_assert(!isold(o)); /* see MOVE OLD rule */ - if (!keepinvariantout(g)) /* not keeping invariant? */ + resetbit(o->marked, FINALIZEDBIT); /* object is "normal" again */ + if (issweepphase(g)) makewhite(g, o); /* "sweep" object */ return o; } @@ -802,7 +799,7 @@ static GCObject *udata2finalize (global_State *g) { static void dothecall (lua_State *L, void *ud) { UNUSED(ud); - luaD_call(L, L->top - 2, 0, 0); + luaD_callnoyield(L, L->top - 2, 0); } @@ -838,29 +835,58 @@ static void GCTM (lua_State *L, int propagateerrors) { } +/* +** call a few (up to 'g->gcfinnum') finalizers +*/ +static int runafewfinalizers (lua_State *L) { + global_State *g = G(L); + unsigned int i; + lua_assert(!g->tobefnz || g->gcfinnum > 0); + for (i = 0; g->tobefnz && i < g->gcfinnum; i++) + GCTM(L, 1); /* call one finalizer */ + g->gcfinnum = (!g->tobefnz) ? 0 /* nothing more to finalize? */ + : g->gcfinnum * 2; /* else call a few more next time */ + return i; +} + + +/* +** call all pending finalizers +*/ +static void callallpendingfinalizers (lua_State *L) { + global_State *g = G(L); + while (g->tobefnz) + GCTM(L, 0); +} + + +/* +** find last 'next' field in list 'p' list (to add elements in its end) +*/ +static GCObject **findlast (GCObject **p) { + while (*p != NULL) + p = &(*p)->next; + return p; +} + + /* ** move all unreachable objects (or 'all' objects) that need ** finalization from list 'finobj' to list 'tobefnz' (to be finalized) */ -static void separatetobefnz (lua_State *L, int all) { - global_State *g = G(L); - GCObject **p = &g->finobj; +static void separatetobefnz (global_State *g, int all) { GCObject *curr; - GCObject **lastnext = &g->tobefnz; - /* find last 'next' field in 'tobefnz' list (to add elements in its end) */ - while (*lastnext != NULL) - lastnext = &gch(*lastnext)->next; + GCObject **p = &g->finobj; + GCObject **lastnext = findlast(&g->tobefnz); while ((curr = *p) != NULL) { /* traverse all finalizable objects */ - lua_assert(!isfinalized(curr)); - lua_assert(testbit(gch(curr)->marked, SEPARATED)); + lua_assert(tofinalize(curr)); if (!(iswhite(curr) || all)) /* not being collected? */ - p = &gch(curr)->next; /* don't bother with it */ + p = &curr->next; /* don't bother with it */ else { - l_setbit(gch(curr)->marked, FINALIZEDBIT); /* won't be finalized again */ - *p = gch(curr)->next; /* remove 'curr' from 'finobj' list */ - gch(curr)->next = *lastnext; /* link at the end of 'tobefnz' list */ + *p = curr->next; /* remove 'curr' from 'finobj' list */ + curr->next = *lastnext; /* link at the end of 'tobefnz' list */ *lastnext = curr; - lastnext = &gch(curr)->next; + lastnext = &curr->next; } } } @@ -872,33 +898,29 @@ static void separatetobefnz (lua_State *L, int all) { */ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { global_State *g = G(L); - if (testbit(gch(o)->marked, SEPARATED) || /* obj. is already separated... */ - isfinalized(o) || /* ... or is finalized... */ - gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ + if (tofinalize(o) || /* obj. is already marked... */ + gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ return; /* nothing to be done */ else { /* move 'o' to 'finobj' list */ GCObject **p; - GCheader *ho = gch(o); - if (g->sweepgc == &ho->next) { /* avoid removing current sweep object */ - lua_assert(issweepphase(g)); - g->sweepgc = sweeptolive(L, g->sweepgc, NULL); + if (issweepphase(g)) { + makewhite(g, o); /* "sweep" object 'o' */ + if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */ + g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */ } /* search for pointer pointing to 'o' */ - for (p = &g->allgc; *p != o; p = &gch(*p)->next) { /* empty */ } - *p = ho->next; /* remove 'o' from root list */ - ho->next = g->finobj; /* link it in list 'finobj' */ + for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ } + *p = o->next; /* remove 'o' from 'allgc' list */ + o->next = g->finobj; /* link it in 'finobj' list */ g->finobj = o; - l_setbit(ho->marked, SEPARATED); /* mark it as such */ - if (!keepinvariantout(g)) /* not keeping invariant? */ - makewhite(g, o); /* "sweep" object */ - else - resetoldbit(o); /* see MOVE OLD rule */ + l_setbit(o->marked, FINALIZEDBIT); /* mark it as such */ } } /* }====================================================== */ + /* ** {====================================================== ** GC control @@ -907,195 +929,164 @@ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { /* -** set a reasonable "time" to wait before starting a new GC cycle; -** cycle will start when memory use hits threshold +** Set a reasonable "time" to wait before starting a new GC cycle; cycle +** will start when memory use hits threshold. (Division by 'estimate' +** should be OK: it cannot be zero (because Lua cannot even start with +** less than PAUSEADJ bytes). */ -static void setpause (global_State *g, l_mem estimate) { - l_mem debt, threshold; - estimate = estimate / PAUSEADJ; /* adjust 'estimate' */ +static void setpause (global_State *g) { + l_mem threshold, debt; + l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */ + lua_assert(estimate > 0); threshold = (g->gcpause < MAX_LMEM / estimate) /* overflow? */ ? estimate * g->gcpause /* no overflow */ : MAX_LMEM; /* overflow; truncate to maximum */ - debt = -cast(l_mem, threshold - gettotalbytes(g)); + debt = gettotalbytes(g) - threshold; luaE_setdebt(g, debt); } -#define sweepphases \ - (bitmask(GCSsweepstring) | bitmask(GCSsweepudata) | bitmask(GCSsweep)) - - -/* -** enter first sweep phase (strings) and prepare pointers for other -** sweep phases. The calls to 'sweeptolive' make pointers point to an -** object inside the list (instead of to the header), so that the real -** sweep do not need to skip objects created between "now" and the start -** of the real sweep. -** Returns how many objects it swept. -*/ -static int entersweep (lua_State *L) { - global_State *g = G(L); - int n = 0; - g->gcstate = GCSsweepstring; - lua_assert(g->sweepgc == NULL && g->sweepfin == NULL); - /* prepare to sweep strings, finalizable objects, and regular objects */ - g->sweepstrgc = 0; - g->sweepfin = sweeptolive(L, &g->finobj, &n); - g->sweepgc = sweeptolive(L, &g->allgc, &n); - return n; -} - - -/* -** change GC mode -*/ -void luaC_changemode (lua_State *L, int mode) { - global_State *g = G(L); - if (mode == g->gckind) return; /* nothing to change */ - if (mode == KGC_GEN) { /* change to generational mode */ - /* make sure gray lists are consistent */ - luaC_runtilstate(L, bitmask(GCSpropagate)); - g->GCestimate = gettotalbytes(g); - g->gckind = KGC_GEN; - } - else { /* change to incremental mode */ - /* sweep all objects to turn them back to white - (as white has not changed, nothing extra will be collected) */ - g->gckind = KGC_NORMAL; - entersweep(L); - luaC_runtilstate(L, ~sweepphases); - } -} - - /* -** call all pending finalizers +** Enter first sweep phase. +** The call to 'sweeplist' tries to make pointer point to an object +** inside the list (instead of to the header), so that the real sweep do +** not need to skip objects created between "now" and the start of the +** real sweep. */ -static void callallpendingfinalizers (lua_State *L, int propagateerrors) { +static void entersweep (lua_State *L) { global_State *g = G(L); - while (g->tobefnz) { - resetoldbit(g->tobefnz); - GCTM(L, propagateerrors); - } + g->gcstate = GCSswpallgc; + lua_assert(g->sweepgc == NULL); + g->sweepgc = sweeplist(L, &g->allgc, 1); } void luaC_freeallobjects (lua_State *L) { global_State *g = G(L); - int i; - separatetobefnz(L, 1); /* separate all objects with finalizers */ + separatetobefnz(g, 1); /* separate all objects with finalizers */ lua_assert(g->finobj == NULL); - callallpendingfinalizers(L, 0); + callallpendingfinalizers(L); + lua_assert(g->tobefnz == NULL); g->currentwhite = WHITEBITS; /* this "white" makes all objects look dead */ g->gckind = KGC_NORMAL; - sweepwholelist(L, &g->finobj); /* finalizers can create objs. in 'finobj' */ + sweepwholelist(L, &g->finobj); sweepwholelist(L, &g->allgc); - for (i = 0; i < g->strt.size; i++) /* free all string lists */ - sweepwholelist(L, &g->strt.hash[i]); + sweepwholelist(L, &g->fixedgc); /* collect fixed objects */ lua_assert(g->strt.nuse == 0); } static l_mem atomic (lua_State *L) { global_State *g = G(L); - l_mem work = -cast(l_mem, g->GCmemtrav); /* start counting work */ + l_mem work; GCObject *origweak, *origall; - lua_assert(!iswhite(obj2gco(g->mainthread))); + GCObject *grayagain = g->grayagain; /* save original list */ + lua_assert(g->ephemeron == NULL && g->weak == NULL); + lua_assert(!iswhite(g->mainthread)); + g->gcstate = GCSinsideatomic; + g->GCmemtrav = 0; /* start counting work */ markobject(g, L); /* mark running thread */ /* registry and global metatables may be changed by API */ markvalue(g, &g->l_registry); - markmt(g); /* mark basic metatables */ + markmt(g); /* mark global metatables */ /* remark occasional upvalues of (maybe) dead threads */ remarkupvals(g); propagateall(g); /* propagate changes */ - work += g->GCmemtrav; /* stop counting (do not (re)count grays) */ - /* traverse objects caught by write barrier and by 'remarkupvals' */ - retraversegrays(g); - work -= g->GCmemtrav; /* restart counting */ + work = g->GCmemtrav; /* stop counting (do not recount 'grayagain') */ + g->gray = grayagain; + propagateall(g); /* traverse 'grayagain' list */ + g->GCmemtrav = 0; /* restart counting */ convergeephemerons(g); /* at this point, all strongly accessible objects are marked. */ - /* clear values from weak tables, before checking finalizers */ + /* Clear values from weak tables, before checking finalizers */ clearvalues(g, g->weak, NULL); clearvalues(g, g->allweak, NULL); origweak = g->weak; origall = g->allweak; work += g->GCmemtrav; /* stop counting (objects being finalized) */ - separatetobefnz(L, 0); /* separate objects to be finalized */ + separatetobefnz(g, 0); /* separate objects to be finalized */ + g->gcfinnum = 1; /* there may be objects to be finalized */ markbeingfnz(g); /* mark objects that will be finalized */ - propagateall(g); /* remark, to propagate `preserveness' */ - work -= g->GCmemtrav; /* restart counting */ + propagateall(g); /* remark, to propagate 'resurrection' */ + g->GCmemtrav = 0; /* restart counting */ convergeephemerons(g); /* at this point, all resurrected objects are marked. */ /* remove dead objects from weak tables */ clearkeys(g, g->ephemeron, NULL); /* clear keys from all ephemeron tables */ - clearkeys(g, g->allweak, NULL); /* clear keys from all allweak tables */ + clearkeys(g, g->allweak, NULL); /* clear keys from all 'allweak' tables */ /* clear values from resurrected weak tables */ clearvalues(g, g->weak, origweak); clearvalues(g, g->allweak, origall); + luaS_clearcache(g); g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ work += g->GCmemtrav; /* complete counting */ return work; /* estimate of memory marked by 'atomic' */ } +static lu_mem sweepstep (lua_State *L, global_State *g, + int nextstate, GCObject **nextlist) { + if (g->sweepgc) { + l_mem olddebt = g->GCdebt; + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); + g->GCestimate += g->GCdebt - olddebt; /* update estimate */ + if (g->sweepgc) /* is there still something to sweep? */ + return (GCSWEEPMAX * GCSWEEPCOST); + } + /* else enter next state */ + g->gcstate = nextstate; + g->sweepgc = nextlist; + return 0; +} + + static lu_mem singlestep (lua_State *L) { global_State *g = G(L); switch (g->gcstate) { case GCSpause: { - /* start to count memory traversed */ g->GCmemtrav = g->strt.size * sizeof(GCObject*); - lua_assert(!isgenerational(g)); restartcollection(g); g->gcstate = GCSpropagate; return g->GCmemtrav; } case GCSpropagate: { - if (g->gray) { - lu_mem oldtrav = g->GCmemtrav; - propagatemark(g); - return g->GCmemtrav - oldtrav; /* memory traversed in this step */ - } - else { /* no more `gray' objects */ - lu_mem work; - int sw; - g->gcstate = GCSatomic; /* finish mark phase */ - g->GCestimate = g->GCmemtrav; /* save what was counted */; - work = atomic(L); /* add what was traversed by 'atomic' */ - g->GCestimate += work; /* estimate of total memory traversed */ - sw = entersweep(L); - return work + sw * GCSWEEPCOST; - } + g->GCmemtrav = 0; + lua_assert(g->gray); + propagatemark(g); + if (g->gray == NULL) /* no more gray objects? */ + g->gcstate = GCSatomic; /* finish propagate phase */ + return g->GCmemtrav; /* memory traversed in this step */ } - case GCSsweepstring: { - int i; - for (i = 0; i < GCSWEEPMAX && g->sweepstrgc + i < g->strt.size; i++) - sweepwholelist(L, &g->strt.hash[g->sweepstrgc + i]); - g->sweepstrgc += i; - if (g->sweepstrgc >= g->strt.size) /* no more strings to sweep? */ - g->gcstate = GCSsweepudata; - return i * GCSWEEPCOST; + case GCSatomic: { + lu_mem work; + propagateall(g); /* make sure gray list is empty */ + work = atomic(L); /* work is what was traversed by 'atomic' */ + entersweep(L); + g->GCestimate = gettotalbytes(g); /* first estimate */; + return work; } - case GCSsweepudata: { - if (g->sweepfin) { - g->sweepfin = sweeplist(L, g->sweepfin, GCSWEEPMAX); - return GCSWEEPMAX*GCSWEEPCOST; - } - else { - g->gcstate = GCSsweep; - return 0; - } + case GCSswpallgc: { /* sweep "regular" objects */ + return sweepstep(L, g, GCSswpfinobj, &g->finobj); + } + case GCSswpfinobj: { /* sweep objects with finalizers */ + return sweepstep(L, g, GCSswptobefnz, &g->tobefnz); } - case GCSsweep: { - if (g->sweepgc) { - g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); - return GCSWEEPMAX*GCSWEEPCOST; + case GCSswptobefnz: { /* sweep objects to be finalized */ + return sweepstep(L, g, GCSswpend, NULL); + } + case GCSswpend: { /* finish sweeps */ + makewhite(g, g->mainthread); /* sweep main thread */ + checkSizes(L, g); + g->gcstate = GCScallfin; + return 0; + } + case GCScallfin: { /* call remaining finalizers */ + if (g->tobefnz && g->gckind != KGC_EMERGENCY) { + int n = runafewfinalizers(L); + return (n * GCFINALIZECOST); } - else { - /* sweep main thread */ - GCObject *mt = obj2gco(g->mainthread); - sweeplist(L, &mt, 1); - checkSizes(L); + else { /* emergency mode or no more finalizers */ g->gcstate = GCSpause; /* finish collection */ - return GCSWEEPCOST; + return 0; } } default: lua_assert(0); return 0; @@ -1114,105 +1105,70 @@ void luaC_runtilstate (lua_State *L, int statesmask) { } -static void generationalcollection (lua_State *L) { - global_State *g = G(L); - lua_assert(g->gcstate == GCSpropagate); - if (g->GCestimate == 0) { /* signal for another major collection? */ - luaC_fullgc(L, 0); /* perform a full regular collection */ - g->GCestimate = gettotalbytes(g); /* update control */ - } +/* +** get GC debt and convert it from Kb to 'work units' (avoid zero debt +** and overflows) +*/ +static l_mem getdebt (global_State *g) { + l_mem debt = g->GCdebt; + int stepmul = g->gcstepmul; + if (debt <= 0) return 0; /* minimal debt */ else { - lu_mem estimate = g->GCestimate; - luaC_runtilstate(L, bitmask(GCSpause)); /* run complete (minor) cycle */ - g->gcstate = GCSpropagate; /* skip restart */ - if (gettotalbytes(g) > (estimate / 100) * g->gcmajorinc) - g->GCestimate = 0; /* signal for a major collection */ - else - g->GCestimate = estimate; /* keep estimate from last major coll. */ - + debt = (debt / STEPMULADJ) + 1; + debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; + return debt; } - setpause(g, gettotalbytes(g)); - lua_assert(g->gcstate == GCSpropagate); } - -static void incstep (lua_State *L) { +/* +** performs a basic GC step when collector is running +*/ +void luaC_step (lua_State *L) { global_State *g = G(L); - l_mem debt = g->GCdebt; - int stepmul = g->gcstepmul; - if (stepmul < 40) stepmul = 40; /* avoid ridiculous low values (and 0) */ - /* convert debt from Kb to 'work units' (avoid zero debt and overflows) */ - debt = (debt / STEPMULADJ) + 1; - debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; - do { /* always perform at least one single step */ - lu_mem work = singlestep(L); /* do some work */ + l_mem debt = getdebt(g); /* GC deficit (be paid now) */ + if (!g->gcrunning) { /* not running? */ + luaE_setdebt(g, -GCSTEPSIZE * 10); /* avoid being called too often */ + return; + } + do { /* repeat until pause or enough "credit" (negative debt) */ + lu_mem work = singlestep(L); /* perform one single step */ debt -= work; } while (debt > -GCSTEPSIZE && g->gcstate != GCSpause); if (g->gcstate == GCSpause) - setpause(g, g->GCestimate); /* pause until next cycle */ + setpause(g); /* pause until next cycle */ else { - debt = (debt / stepmul) * STEPMULADJ; /* convert 'work units' to Kb */ + debt = (debt / g->gcstepmul) * STEPMULADJ; /* convert 'work units' to Kb */ luaE_setdebt(g, debt); + runafewfinalizers(L); } } /* -** performs a basic GC step -*/ -void luaC_forcestep (lua_State *L) { - global_State *g = G(L); - int i; - if (isgenerational(g)) generationalcollection(L); - else incstep(L); - /* run a few finalizers (or all of them at the end of a collect cycle) */ - for (i = 0; g->tobefnz && (i < GCFINALIZENUM || g->gcstate == GCSpause); i++) - GCTM(L, 1); /* call one finalizer */ -} - - -/* -** performs a basic GC step only if collector is running -*/ -void luaC_step (lua_State *L) { - global_State *g = G(L); - if (g->gcrunning) luaC_forcestep(L); - else luaE_setdebt(g, -GCSTEPSIZE); /* avoid being called too often */ -} - - - -/* -** performs a full GC cycle; if "isemergency", does not call -** finalizers (which could change stack positions) +** Performs a full GC cycle; if 'isemergency', set a flag to avoid +** some operations which could change the interpreter state in some +** unexpected ways (running finalizers and shrinking some structures). +** Before running the collection, check 'keepinvariant'; if it is true, +** there may be some objects marked as black, so the collector has +** to sweep all objects to turn them back to white (as white has not +** changed, nothing will be collected). */ void luaC_fullgc (lua_State *L, int isemergency) { global_State *g = G(L); - int origkind = g->gckind; - lua_assert(origkind != KGC_EMERGENCY); - if (isemergency) /* do not run finalizers during emergency GC */ - g->gckind = KGC_EMERGENCY; - else { - g->gckind = KGC_NORMAL; - callallpendingfinalizers(L, 1); - } - if (keepinvariant(g)) { /* may there be some black objects? */ - /* must sweep all objects to turn them back to white - (as white has not changed, nothing will be collected) */ - entersweep(L); + lua_assert(g->gckind == KGC_NORMAL); + if (isemergency) g->gckind = KGC_EMERGENCY; /* set flag */ + if (keepinvariant(g)) { /* black objects? */ + entersweep(L); /* sweep everything to turn them back to white */ } /* finish any pending sweep phase to start a new cycle */ luaC_runtilstate(L, bitmask(GCSpause)); luaC_runtilstate(L, ~bitmask(GCSpause)); /* start new collection */ - luaC_runtilstate(L, bitmask(GCSpause)); /* run entire collection */ - if (origkind == KGC_GEN) { /* generational mode? */ - /* generational mode must be kept in propagate phase */ - luaC_runtilstate(L, bitmask(GCSpropagate)); - } - g->gckind = origkind; - setpause(g, gettotalbytes(g)); - if (!isemergency) /* do not run finalizers during emergency GC */ - callallpendingfinalizers(L, 1); + luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ + /* estimate must be correct after a full GC cycle */ + lua_assert(g->GCestimate == gettotalbytes(g)); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + g->gckind = KGC_NORMAL; + setpause(g); } /* }====================================================== */ diff --git a/depends/lua/src/linit.c b/depends/lua/src/linit.c index c1a383047..8ce94ccb3 100644 --- a/depends/lua/src/linit.c +++ b/depends/lua/src/linit.c @@ -1,20 +1,33 @@ /* -** $Id: linit.c,v 1.32.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: linit.c,v 1.38 2015/01/05 13:48:33 roberto Exp $ ** Initialization of libraries for lua.c and other clients ** See Copyright Notice in lua.h */ +#define linit_c +#define LUA_LIB + /* ** If you embed Lua in your program and need to open the standard ** libraries, call luaL_openlibs in your program. If you need a ** different set of libraries, copy this file to your project and edit ** it to suit your needs. +** +** You can also *preload* libraries, so that a later 'require' can +** open the library, which is already linked to the application. +** For that, do the following code: +** +** luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); +** lua_pushcfunction(L, luaopen_modname); +** lua_setfield(L, -2, modname); +** lua_pop(L, 1); // remove _PRELOAD table */ +#include "lprefix.h" -#define linit_c -#define LUA_LIB + +#include #include "lua.h" @@ -34,34 +47,22 @@ static const luaL_Reg loadedlibs[] = { {LUA_IOLIBNAME, luaopen_io}, {LUA_OSLIBNAME, luaopen_os}, {LUA_STRLIBNAME, luaopen_string}, - {LUA_BITLIBNAME, luaopen_bit32}, {LUA_MATHLIBNAME, luaopen_math}, + {LUA_UTF8LIBNAME, luaopen_utf8}, {LUA_DBLIBNAME, luaopen_debug}, - {NULL, NULL} -}; - - -/* -** these libs are preloaded and must be required before used -*/ -static const luaL_Reg preloadedlibs[] = { +#if defined(LUA_COMPAT_BITLIB) + {LUA_BITLIBNAME, luaopen_bit32}, +#endif {NULL, NULL} }; LUALIB_API void luaL_openlibs (lua_State *L) { const luaL_Reg *lib; - /* call open functions from 'loadedlibs' and set results to global table */ + /* "require" functions from 'loadedlibs' and set results to global table */ for (lib = loadedlibs; lib->func; lib++) { luaL_requiref(L, lib->name, lib->func, 1); lua_pop(L, 1); /* remove lib */ } - /* add open functions from 'preloadedlibs' into 'package.preload' table */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); - for (lib = preloadedlibs; lib->func; lib++) { - lua_pushcfunction(L, lib->func); - lua_setfield(L, -2, lib->name); - } - lua_pop(L, 1); /* remove _PRELOAD table */ } diff --git a/depends/lua/src/liolib.c b/depends/lua/src/liolib.c index 2a4ec4aa3..aa78e593f 100644 --- a/depends/lua/src/liolib.c +++ b/depends/lua/src/liolib.c @@ -1,120 +1,139 @@ /* -** $Id: liolib.c,v 2.112.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: liolib.c,v 2.149 2016/05/02 14:03:19 roberto Exp $ ** Standard I/O (and system) library ** See Copyright Notice in lua.h */ +#define liolib_c +#define LUA_LIB -/* -** This definition must come before the inclusion of 'stdio.h'; it -** should not affect non-POSIX systems -*/ -#if !defined(_FILE_OFFSET_BITS) -#define _LARGEFILE_SOURCE 1 -#define _FILE_OFFSET_BITS 64 -#endif +#include "lprefix.h" +#include #include +#include #include #include #include -#define liolib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" #include "lualib.h" -#if !defined(lua_checkmode) + /* -** Check whether 'mode' matches '[rwa]%+?b?'. ** Change this macro to accept other modes for 'fopen' besides ** the standard ones. */ -#define lua_checkmode(mode) \ +#if !defined(l_checkmode) + +/* accepted extensions to 'mode' in 'fopen' */ +#if !defined(L_MODEEXT) +#define L_MODEEXT "b" +#endif + +/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ +#define l_checkmode(mode) \ (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && \ - (*mode != '+' || ++mode) && /* skip if char is '+' */ \ - (*mode != 'b' || ++mode) && /* skip if char is 'b' */ \ - (*mode == '\0')) + (*mode != '+' || (++mode, 1)) && /* skip if char is '+' */ \ + (strspn(mode, L_MODEEXT) == strlen(mode))) #endif /* ** {====================================================== -** lua_popen spawns a new process connected to the current +** l_popen spawns a new process connected to the current ** one through the file streams. ** ======================================================= */ -#if !defined(lua_popen) /* { */ - -#if defined(LUA_USE_POPEN) /* { */ +#if !defined(l_popen) /* { */ -#define lua_popen(L,c,m) ((void)L, fflush(NULL), popen(c,m)) -#define lua_pclose(L,file) ((void)L, pclose(file)) +#if defined(LUA_USE_POSIX) /* { */ -#elif defined(LUA_WIN) /* }{ */ +#define l_popen(L,c,m) (fflush(NULL), popen(c,m)) +#define l_pclose(L,file) (pclose(file)) -#define lua_popen(L,c,m) ((void)L, _popen(c,m)) -#define lua_pclose(L,file) ((void)L, _pclose(file)) +#elif defined(LUA_USE_WINDOWS) /* }{ */ +#define l_popen(L,c,m) (_popen(c,m)) +#define l_pclose(L,file) (_pclose(file)) #else /* }{ */ -#define lua_popen(L,c,m) ((void)((void)c, m), \ - luaL_error(L, LUA_QL("popen") " not supported"), (FILE*)0) -#define lua_pclose(L,file) ((void)((void)L, file), -1) - +/* ISO C definitions */ +#define l_popen(L,c,m) \ + ((void)((void)c, m), \ + luaL_error(L, "'popen' not supported"), \ + (FILE*)0) +#define l_pclose(L,file) ((void)L, (void)file, -1) #endif /* } */ -#endif /* } */ +#endif /* } */ /* }====================================================== */ +#if !defined(l_getc) /* { */ + +#if defined(LUA_USE_POSIX) +#define l_getc(f) getc_unlocked(f) +#define l_lockfile(f) flockfile(f) +#define l_unlockfile(f) funlockfile(f) +#else +#define l_getc(f) getc(f) +#define l_lockfile(f) ((void)0) +#define l_unlockfile(f) ((void)0) +#endif + +#endif /* } */ + + /* ** {====================================================== -** lua_fseek: configuration for longer offsets +** l_fseek: configuration for longer offsets ** ======================================================= */ -#if !defined(lua_fseek) && !defined(LUA_ANSI) /* { */ +#if !defined(l_fseek) /* { */ #if defined(LUA_USE_POSIX) /* { */ +#include + #define l_fseek(f,o,w) fseeko(f,o,w) #define l_ftell(f) ftello(f) #define l_seeknum off_t -#elif defined(LUA_WIN) && !defined(_CRTIMP_TYPEINFO) \ +#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ -/* Windows (but not DDK) and Visual C++ 2005 or higher */ +/* Windows (but not DDK) and Visual C++ 2005 or higher */ #define l_fseek(f,o,w) _fseeki64(f,o,w) #define l_ftell(f) _ftelli64(f) #define l_seeknum __int64 -#endif /* } */ - -#endif /* } */ - +#else /* }{ */ -#if !defined(l_fseek) /* default definitions */ +/* ISO C definitions */ #define l_fseek(f,o,w) fseek(f,o,w) #define l_ftell(f) ftell(f) #define l_seeknum long -#endif + +#endif /* } */ + +#endif /* } */ /* }====================================================== */ #define IO_PREFIX "_IO_" +#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) #define IO_INPUT (IO_PREFIX "input") #define IO_OUTPUT (IO_PREFIX "output") @@ -161,9 +180,9 @@ static FILE *tofile (lua_State *L) { /* -** When creating file handles, always creates a `closed' file handle +** When creating file handles, always creates a 'closed' file handle ** before opening the actual file; so, if there is a memory error, the -** file is not left opened. +** handle is in a consistent state. */ static LStream *newprefile (lua_State *L) { LStream *p = (LStream *)lua_newuserdata(L, sizeof(LStream)); @@ -173,9 +192,14 @@ static LStream *newprefile (lua_State *L) { } +/* +** Calls the 'close' function from a file handle. The 'volatile' avoids +** a bug in some versions of the Clang compiler (e.g., clang 3.0 for +** 32 bits). +*/ static int aux_close (lua_State *L) { LStream *p = tolstream(L); - lua_CFunction cf = p->closef; + volatile lua_CFunction cf = p->closef; p->closef = NULL; /* mark stream as closed */ return (*cf)(L); /* close it */ } @@ -219,7 +243,7 @@ static void opencheck (lua_State *L, const char *fname, const char *mode) { LStream *p = newfile(L); p->f = fopen(fname, mode); if (p->f == NULL) - luaL_error(L, "cannot open file " LUA_QS " (%s)", fname, strerror(errno)); + luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno)); } @@ -228,7 +252,7 @@ static int io_open (lua_State *L) { const char *mode = luaL_optstring(L, 2, "r"); LStream *p = newfile(L); const char *md = mode; /* to traverse/check mode */ - luaL_argcheck(L, lua_checkmode(md), 2, "invalid mode"); + luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); p->f = fopen(filename, mode); return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; } @@ -239,7 +263,7 @@ static int io_open (lua_State *L) { */ static int io_pclose (lua_State *L) { LStream *p = tolstream(L); - return luaL_execresult(L, lua_pclose(L, p->f)); + return luaL_execresult(L, l_pclose(L, p->f)); } @@ -247,7 +271,7 @@ static int io_popen (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); LStream *p = newprefile(L); - p->f = lua_popen(L, filename, mode); + p->f = l_popen(L, filename, mode); p->closef = &io_pclose; return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; } @@ -265,7 +289,7 @@ static FILE *getiofile (lua_State *L, const char *findex) { lua_getfield(L, LUA_REGISTRYINDEX, findex); p = (LStream *)lua_touserdata(L, -1); if (isclosed(p)) - luaL_error(L, "standard %s file is closed", findex + strlen(IO_PREFIX)); + luaL_error(L, "standard %s file is closed", findex + IOPREF_LEN); return p->f; } @@ -300,15 +324,18 @@ static int io_output (lua_State *L) { static int io_readline (lua_State *L); +/* +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit +** in the limit for upvalues of a closure) +*/ +#define MAXARGLINE 250 + static void aux_lines (lua_State *L, int toclose) { - int i; int n = lua_gettop(L) - 1; /* number of arguments to read */ - /* ensure that arguments will fit here and into 'io_readline' stack */ - luaL_argcheck(L, n <= LUA_MINSTACK - 3, LUA_MINSTACK - 3, "too many options"); - lua_pushvalue(L, 1); /* file handle */ + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); lua_pushinteger(L, n); /* number of arguments to read */ lua_pushboolean(L, toclose); /* close/not close file when finished */ - for (i = 1; i <= n; i++) lua_pushvalue(L, i + 1); /* copy arguments */ + lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */ lua_pushcclosure(L, io_readline, 3 + n); } @@ -347,13 +374,91 @@ static int io_lines (lua_State *L) { */ -static int read_number (lua_State *L, FILE *f) { - lua_Number d; - if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) { - lua_pushnumber(L, d); - return 1; +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + + +/* auxiliary structure used by 'read_number' */ +typedef struct { + FILE *f; /* file being read */ + int c; /* current character (look ahead) */ + int n; /* number of elements in buffer 'buff' */ + char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ +} RN; + + +/* +** Add current char to buffer (if not out of space) and read next one +*/ +static int nextc (RN *rn) { + if (rn->n >= L_MAXLENNUM) { /* buffer overflow? */ + rn->buff[0] = '\0'; /* invalidate result */ + return 0; /* fail */ } else { + rn->buff[rn->n++] = rn->c; /* save current char */ + rn->c = l_getc(rn->f); /* read next one */ + return 1; + } +} + + +/* +** Accept current char if it is in 'set' (of size 2) +*/ +static int test2 (RN *rn, const char *set) { + if (rn->c == set[0] || rn->c == set[1]) + return nextc(rn); + else return 0; +} + + +/* +** Read a sequence of (hex)digits +*/ +static int readdigits (RN *rn, int hex) { + int count = 0; + while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) + count++; + return count; +} + + +/* +** Read a number: first reads a valid prefix of a numeral into a buffer. +** Then it calls 'lua_stringtonumber' to check whether the format is +** correct and to convert it to a Lua number +*/ +static int read_number (lua_State *L, FILE *f) { + RN rn; + int count = 0; + int hex = 0; + char decp[2]; + rn.f = f; rn.n = 0; + decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ + decp[1] = '.'; /* always accept a dot */ + l_lockfile(rn.f); + do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ + test2(&rn, "-+"); /* optional signal */ + if (test2(&rn, "00")) { + if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ + else count = 1; /* count initial '0' as a valid digit */ + } + count += readdigits(&rn, hex); /* integral part */ + if (test2(&rn, decp)) /* decimal point? */ + count += readdigits(&rn, hex); /* fractional part */ + if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ + test2(&rn, "-+"); /* exponent signal */ + readdigits(&rn, 0); /* exponent digits */ + } + ungetc(rn.c, rn.f); /* unread look-ahead char */ + l_unlockfile(rn.f); + rn.buff[rn.n] = '\0'; /* finish string */ + if (lua_stringtonumber(L, rn.buff)) /* is this a valid number? */ + return 1; /* ok */ + else { /* invalid format */ lua_pushnil(L); /* "result" to be removed */ return 0; /* read fails */ } @@ -362,48 +467,42 @@ static int read_number (lua_State *L, FILE *f) { static int test_eof (lua_State *L, FILE *f) { int c = getc(f); - ungetc(c, f); - lua_pushlstring(L, NULL, 0); + ungetc(c, f); /* no-op when c == EOF */ + lua_pushliteral(L, ""); return (c != EOF); } static int read_line (lua_State *L, FILE *f, int chop) { luaL_Buffer b; + int c = '\0'; luaL_buffinit(L, &b); - for (;;) { - size_t l; - char *p = luaL_prepbuffer(&b); - if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */ - luaL_pushresult(&b); /* close buffer */ - return (lua_rawlen(L, -1) > 0); /* check whether read something */ - } - l = strlen(p); - if (l == 0 || p[l-1] != '\n') - luaL_addsize(&b, l); - else { - luaL_addsize(&b, l - chop); /* chop 'eol' if needed */ - luaL_pushresult(&b); /* close buffer */ - return 1; /* read at least an `eol' */ - } + while (c != EOF && c != '\n') { /* repeat until end of line */ + char *buff = luaL_prepbuffer(&b); /* preallocate buffer */ + int i = 0; + l_lockfile(f); /* no memory errors can happen inside the lock */ + while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') + buff[i++] = c; + l_unlockfile(f); + luaL_addsize(&b, i); } + if (!chop && c == '\n') /* want a newline and have one? */ + luaL_addchar(&b, c); /* add ending newline to result */ + luaL_pushresult(&b); /* close buffer */ + /* return ok if read something (either a newline or something else) */ + return (c == '\n' || lua_rawlen(L, -1) > 0); } -#define MAX_SIZE_T (~(size_t)0) - static void read_all (lua_State *L, FILE *f) { - size_t rlen = LUAL_BUFFERSIZE; /* how much to read in each cycle */ + size_t nr; luaL_Buffer b; luaL_buffinit(L, &b); - for (;;) { - char *p = luaL_prepbuffsize(&b, rlen); - size_t nr = fread(p, sizeof(char), rlen, f); + do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ + char *p = luaL_prepbuffer(&b); + nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f); luaL_addsize(&b, nr); - if (nr < rlen) break; /* eof? */ - else if (rlen <= (MAX_SIZE_T / 4)) /* avoid buffers too large */ - rlen *= 2; /* double buffer size at each iteration */ - } + } while (nr == LUAL_BUFFERSIZE); luaL_pushresult(&b); /* close buffer */ } @@ -435,13 +534,13 @@ static int g_read (lua_State *L, FILE *f, int first) { success = 1; for (n = first; nargs-- && success; n++) { if (lua_type(L, n) == LUA_TNUMBER) { - size_t l = (size_t)lua_tointeger(L, n); + size_t l = (size_t)luaL_checkinteger(L, n); success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); } else { - const char *p = lua_tostring(L, n); - luaL_argcheck(L, p && p[0] == '*', n, "invalid option"); - switch (p[1]) { + const char *p = luaL_checkstring(L, n); + if (*p == '*') p++; /* skip optional '*' (for compatibility) */ + switch (*p) { case 'n': /* number */ success = read_number(L, f); break; @@ -488,11 +587,12 @@ static int io_readline (lua_State *L) { if (isclosed(p)) /* file is already closed? */ return luaL_error(L, "file is already closed"); lua_settop(L , 1); + luaL_checkstack(L, n, "too many arguments"); for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ lua_pushvalue(L, lua_upvalueindex(3 + i)); n = g_read(L, p->f, 2); /* 'n' is number of results */ lua_assert(n > 0); /* should return at least a nil */ - if (!lua_isnil(L, -n)) /* read at least one value? */ + if (lua_toboolean(L, -n)) /* read at least one value? */ return n; /* return them */ else { /* first result is nil: EOF or error */ if (n > 1) { /* is there error information? */ @@ -517,8 +617,10 @@ static int g_write (lua_State *L, FILE *f, int arg) { for (; nargs--; arg++) { if (lua_type(L, arg) == LUA_TNUMBER) { /* optimization: could be done exactly as for strings */ - status = status && - fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0; + int len = lua_isinteger(L, arg) + ? fprintf(f, LUA_INTEGER_FMT, lua_tointeger(L, arg)) + : fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)); + status = status && (len > 0); } else { size_t l; @@ -548,15 +650,15 @@ static int f_seek (lua_State *L) { static const char *const modenames[] = {"set", "cur", "end", NULL}; FILE *f = tofile(L); int op = luaL_checkoption(L, 2, "cur", modenames); - lua_Number p3 = luaL_optnumber(L, 3, 0); + lua_Integer p3 = luaL_optinteger(L, 3, 0); l_seeknum offset = (l_seeknum)p3; - luaL_argcheck(L, (lua_Number)offset == p3, 3, + luaL_argcheck(L, (lua_Integer)offset == p3, 3, "not an integer in proper range"); op = l_fseek(f, offset, mode[op]); if (op) return luaL_fileresult(L, 0, NULL); /* error */ else { - lua_pushnumber(L, (lua_Number)l_ftell(f)); + lua_pushinteger(L, (lua_Integer)l_ftell(f)); return 1; } } @@ -568,7 +670,7 @@ static int f_setvbuf (lua_State *L) { FILE *f = tofile(L); int op = luaL_checkoption(L, 2, NULL, modenames); lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); - int res = setvbuf(f, NULL, mode[op], sz); + int res = setvbuf(f, NULL, mode[op], (size_t)sz); return luaL_fileresult(L, res == 0, NULL); } diff --git a/depends/lua/src/llex.c b/depends/lua/src/llex.c index c4b820e83..70328273f 100644 --- a/depends/lua/src/llex.c +++ b/depends/lua/src/llex.c @@ -1,20 +1,24 @@ /* -** $Id: llex.c,v 2.63.1.2 2013/08/30 15:49:41 roberto Exp $ +** $Id: llex.c,v 2.96 2016/05/02 14:02:12 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ +#define llex_c +#define LUA_CORE + +#include "lprefix.h" + #include #include -#define llex_c -#define LUA_CORE - #include "lua.h" #include "lctype.h" +#include "ldebug.h" #include "ldo.h" +#include "lgc.h" #include "llex.h" #include "lobject.h" #include "lparser.h" @@ -38,8 +42,9 @@ static const char *const luaX_tokens [] = { "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", - "..", "...", "==", ">=", "<=", "~=", "::", "", - "", "", "" + "//", "..", "...", "==", ">=", "<=", "~=", + "<<", ">>", "::", "", + "", "", "", "" }; @@ -53,7 +58,7 @@ static void save (LexState *ls, int c) { Mbuffer *b = ls->buff; if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) { size_t newsize; - if (luaZ_sizebuffer(b) >= MAX_SIZET/2) + if (luaZ_sizebuffer(b) >= MAX_SIZE/2) lexerror(ls, "lexical element too long", 0); newsize = luaZ_sizebuffer(b) * 2; luaZ_resizebuffer(ls->L, b, newsize); @@ -64,24 +69,25 @@ static void save (LexState *ls, int c) { void luaX_init (lua_State *L) { int i; + TString *e = luaS_newliteral(L, LUA_ENV); /* create env name */ + luaC_fix(L, obj2gco(e)); /* never collect this name */ for (i=0; itsv.extra = cast_byte(i+1); /* reserved word */ + luaC_fix(L, obj2gco(ts)); /* reserved words are never collected */ + ts->extra = cast_byte(i+1); /* reserved word */ } } const char *luaX_token2str (LexState *ls, int token) { if (token < FIRST_RESERVED) { /* single-byte symbols? */ - lua_assert(token == cast(unsigned char, token)); - return (lisprint(token)) ? luaO_pushfstring(ls->L, LUA_QL("%c"), token) : - luaO_pushfstring(ls->L, "char(%d)", token); + lua_assert(token == cast_uchar(token)); + return luaO_pushfstring(ls->L, "'%c'", token); } else { const char *s = luaX_tokens[token - FIRST_RESERVED]; if (token < TK_EOS) /* fixed format (symbols and reserved words)? */ - return luaO_pushfstring(ls->L, LUA_QS, s); + return luaO_pushfstring(ls->L, "'%s'", s); else /* names, strings, and numerals */ return s; } @@ -90,11 +96,10 @@ const char *luaX_token2str (LexState *ls, int token) { static const char *txtToken (LexState *ls, int token) { switch (token) { - case TK_NAME: - case TK_STRING: - case TK_NUMBER: + case TK_NAME: case TK_STRING: + case TK_FLT: case TK_INT: save(ls, '\0'); - return luaO_pushfstring(ls->L, LUA_QS, luaZ_buffer(ls->buff)); + return luaO_pushfstring(ls->L, "'%s'", luaZ_buffer(ls->buff)); default: return luaX_token2str(ls, token); } @@ -102,9 +107,7 @@ static const char *txtToken (LexState *ls, int token) { static l_noret lexerror (LexState *ls, const char *msg, int token) { - char buff[LUA_IDSIZE]; - luaO_chunkid(buff, getstr(ls->source), LUA_IDSIZE); - msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg); + msg = luaG_addinfo(ls->L, msg, ls->source, ls->linenumber); if (token) luaO_pushfstring(ls->L, "%s near %s", msg, txtToken(ls, token)); luaD_throw(ls->L, LUA_ERRSYNTAX); @@ -117,24 +120,24 @@ l_noret luaX_syntaxerror (LexState *ls, const char *msg) { /* -** creates a new string and anchors it in function's table so that -** it will not be collected until the end of the function's compilation -** (by that time it should be anchored in function's prototype) +** creates a new string and anchors it in scanner's table so that +** it will not be collected until the end of the compilation +** (by that time it should be anchored somewhere) */ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->L; - TValue *o; /* entry for `str' */ + TValue *o; /* entry for 'str' */ TString *ts = luaS_newlstr(L, str, l); /* create new string */ setsvalue2s(L, L->top++, ts); /* temporarily anchor it in stack */ - o = luaH_set(L, ls->fs->h, L->top - 1); - if (ttisnil(o)) { /* not in use yet? (see 'addK') */ + o = luaH_set(L, ls->h, L->top - 1); + if (ttisnil(o)) { /* not in use yet? */ /* boolean value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ setbvalue(o, 1); /* t[string] = true */ luaC_checkGC(L); } else { /* string already present */ - ts = rawtsvalue(keyfromval(o)); /* re-use value previously stored */ + ts = tsvalue(keyfromval(o)); /* re-use value previously stored */ } L->top--; /* remove string from stack */ return ts; @@ -148,17 +151,17 @@ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { static void inclinenumber (LexState *ls) { int old = ls->current; lua_assert(currIsNewline(ls)); - next(ls); /* skip `\n' or `\r' */ + next(ls); /* skip '\n' or '\r' */ if (currIsNewline(ls) && ls->current != old) - next(ls); /* skip `\n\r' or `\r\n' */ + next(ls); /* skip '\n\r' or '\r\n' */ if (++ls->linenumber >= MAX_INT) - luaX_syntaxerror(ls, "chunk has too many lines"); + lexerror(ls, "chunk has too many lines", 0); } void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, int firstchar) { - ls->decpoint = '.'; + ls->t.token = 0; ls->L = L; ls->current = firstchar; ls->lookahead.token = TK_EOS; /* no look-ahead token */ @@ -167,8 +170,7 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, ls->linenumber = 1; ls->lastline = 1; ls->source = source; - ls->envn = luaS_new(L, LUA_ENV); /* create env name */ - luaS_fix(ls->envn); /* never collect this name */ + ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */ luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ } @@ -181,78 +183,70 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, */ - -static int check_next (LexState *ls, const char *set) { - if (ls->current == '\0' || !strchr(set, ls->current)) - return 0; - save_and_next(ls); - return 1; -} - - -/* -** change all characters 'from' in buffer to 'to' -*/ -static void buffreplace (LexState *ls, char from, char to) { - size_t n = luaZ_bufflen(ls->buff); - char *p = luaZ_buffer(ls->buff); - while (n--) - if (p[n] == from) p[n] = to; +static int check_next1 (LexState *ls, int c) { + if (ls->current == c) { + next(ls); + return 1; + } + else return 0; } -#if !defined(getlocaledecpoint) -#define getlocaledecpoint() (localeconv()->decimal_point[0]) -#endif - - -#define buff2d(b,e) luaO_str2d(luaZ_buffer(b), luaZ_bufflen(b) - 1, e) - /* -** in case of format error, try to change decimal point separator to -** the one defined in the current locale and check again +** Check whether current char is in set 'set' (with two chars) and +** saves it */ -static void trydecpoint (LexState *ls, SemInfo *seminfo) { - char old = ls->decpoint; - ls->decpoint = getlocaledecpoint(); - buffreplace(ls, old, ls->decpoint); /* try new decimal separator */ - if (!buff2d(ls->buff, &seminfo->r)) { - /* format error with correct decimal point: no more options */ - buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ - lexerror(ls, "malformed number", TK_NUMBER); +static int check_next2 (LexState *ls, const char *set) { + lua_assert(set[2] == '\0'); + if (ls->current == set[0] || ls->current == set[1]) { + save_and_next(ls); + return 1; } + else return 0; } /* LUA_NUMBER */ /* -** this function is quite liberal in what it accepts, as 'luaO_str2d' +** this function is quite liberal in what it accepts, as 'luaO_str2num' ** will reject ill-formed numerals. */ -static void read_numeral (LexState *ls, SemInfo *seminfo) { +static int read_numeral (LexState *ls, SemInfo *seminfo) { + TValue obj; const char *expo = "Ee"; int first = ls->current; lua_assert(lisdigit(ls->current)); save_and_next(ls); - if (first == '0' && check_next(ls, "Xx")) /* hexadecimal? */ + if (first == '0' && check_next2(ls, "xX")) /* hexadecimal? */ expo = "Pp"; for (;;) { - if (check_next(ls, expo)) /* exponent part? */ - check_next(ls, "+-"); /* optional exponent sign */ - if (lisxdigit(ls->current) || ls->current == '.') + if (check_next2(ls, expo)) /* exponent part? */ + check_next2(ls, "-+"); /* optional exponent sign */ + if (lisxdigit(ls->current)) + save_and_next(ls); + else if (ls->current == '.') save_and_next(ls); - else break; + else break; } save(ls, '\0'); - buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ - if (!buff2d(ls->buff, &seminfo->r)) /* format error? */ - trydecpoint(ls, seminfo); /* try to update decimal point separator */ + if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ + lexerror(ls, "malformed number", TK_FLT); + if (ttisinteger(&obj)) { + seminfo->i = ivalue(&obj); + return TK_INT; + } + else { + lua_assert(ttisfloat(&obj)); + seminfo->r = fltvalue(&obj); + return TK_FLT; + } } /* -** skip a sequence '[=*[' or ']=*]' and return its number of '='s or -** -1 if sequence is malformed +** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return +** its number of '='s; otherwise, return a negative number (-1 iff there +** are no '='s after initial bracket) */ static int skip_sep (LexState *ls) { int count = 0; @@ -268,18 +262,22 @@ static int skip_sep (LexState *ls) { static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { - save_and_next(ls); /* skip 2nd `[' */ + int line = ls->linenumber; /* initial line (for error message) */ + save_and_next(ls); /* skip 2nd '[' */ if (currIsNewline(ls)) /* string starts with a newline? */ inclinenumber(ls); /* skip it */ for (;;) { switch (ls->current) { - case EOZ: - lexerror(ls, (seminfo) ? "unfinished long string" : - "unfinished long comment", TK_EOS); + case EOZ: { /* error */ + const char *what = (seminfo ? "string" : "comment"); + const char *msg = luaO_pushfstring(ls->L, + "unfinished long %s (starting at line %d)", what, line); + lexerror(ls, msg, TK_EOS); break; /* to avoid warnings */ + } case ']': { if (skip_sep(ls) == sep) { - save_and_next(ls); /* skip 2nd `]' */ + save_and_next(ls); /* skip 2nd ']' */ goto endloop; } break; @@ -302,40 +300,65 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { } -static void escerror (LexState *ls, int *c, int n, const char *msg) { - int i; - luaZ_resetbuffer(ls->buff); /* prepare error message */ - save(ls, '\\'); - for (i = 0; i < n && c[i] != EOZ; i++) - save(ls, c[i]); - lexerror(ls, msg, TK_STRING); +static void esccheck (LexState *ls, int c, const char *msg) { + if (!c) { + if (ls->current != EOZ) + save_and_next(ls); /* add current to buffer for error message */ + lexerror(ls, msg, TK_STRING); + } +} + + +static int gethexa (LexState *ls) { + save_and_next(ls); + esccheck (ls, lisxdigit(ls->current), "hexadecimal digit expected"); + return luaO_hexavalue(ls->current); } static int readhexaesc (LexState *ls) { - int c[3], i; /* keep input for error message */ - int r = 0; /* result accumulator */ - c[0] = 'x'; /* for error message */ - for (i = 1; i < 3; i++) { /* read two hexadecimal digits */ - c[i] = next(ls); - if (!lisxdigit(c[i])) - escerror(ls, c, i + 1, "hexadecimal digit expected"); - r = (r << 4) + luaO_hexavalue(c[i]); + int r = gethexa(ls); + r = (r << 4) + gethexa(ls); + luaZ_buffremove(ls->buff, 2); /* remove saved chars from buffer */ + return r; +} + + +static unsigned long readutf8esc (LexState *ls) { + unsigned long r; + int i = 4; /* chars to be removed: '\', 'u', '{', and first digit */ + save_and_next(ls); /* skip 'u' */ + esccheck(ls, ls->current == '{', "missing '{'"); + r = gethexa(ls); /* must have at least one digit */ + while ((save_and_next(ls), lisxdigit(ls->current))) { + i++; + r = (r << 4) + luaO_hexavalue(ls->current); + esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large"); } + esccheck(ls, ls->current == '}', "missing '}'"); + next(ls); /* skip '}' */ + luaZ_buffremove(ls->buff, i); /* remove saved chars from buffer */ return r; } +static void utf8esc (LexState *ls) { + char buff[UTF8BUFFSZ]; + int n = luaO_utf8esc(buff, readutf8esc(ls)); + for (; n > 0; n--) /* add 'buff' to string */ + save(ls, buff[UTF8BUFFSZ - n]); +} + + static int readdecesc (LexState *ls) { - int c[3], i; + int i; int r = 0; /* result accumulator */ for (i = 0; i < 3 && lisdigit(ls->current); i++) { /* read up to 3 digits */ - c[i] = ls->current; - r = 10*r + c[i] - '0'; - next(ls); + r = 10*r + ls->current - '0'; + save_and_next(ls); } - if (r > UCHAR_MAX) - escerror(ls, c, i, "decimal escape too large"); + esccheck(ls, r <= UCHAR_MAX, "decimal escape too large"); + luaZ_buffremove(ls->buff, i); /* remove read digits from buffer */ return r; } @@ -353,7 +376,7 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { break; /* to avoid warnings */ case '\\': { /* escape sequences */ int c; /* final character to be saved */ - next(ls); /* do not save the `\' */ + save_and_next(ls); /* keep '\\' for error messages */ switch (ls->current) { case 'a': c = '\a'; goto read_save; case 'b': c = '\b'; goto read_save; @@ -363,12 +386,14 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { case 't': c = '\t'; goto read_save; case 'v': c = '\v'; goto read_save; case 'x': c = readhexaesc(ls); goto read_save; + case 'u': utf8esc(ls); goto no_save; case '\n': case '\r': inclinenumber(ls); c = '\n'; goto only_save; case '\\': case '\"': case '\'': c = ls->current; goto read_save; case EOZ: goto no_save; /* will raise an error next loop */ case 'z': { /* zap following span of spaces */ + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ next(ls); /* skip the 'z' */ while (lisspace(ls->current)) { if (currIsNewline(ls)) inclinenumber(ls); @@ -377,15 +402,18 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { goto no_save; } default: { - if (!lisdigit(ls->current)) - escerror(ls, &ls->current, 1, "invalid escape sequence"); - /* digital escape \ddd */ - c = readdecesc(ls); + esccheck(ls, lisdigit(ls->current), "invalid escape sequence"); + c = readdecesc(ls); /* digital escape '\ddd' */ goto only_save; } } - read_save: next(ls); /* read next character */ - only_save: save(ls, c); /* save 'c' */ + read_save: + next(ls); + /* go through */ + only_save: + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ + save(ls, c); + /* go through */ no_save: break; } default: @@ -417,7 +445,7 @@ static int llex (LexState *ls, SemInfo *seminfo) { next(ls); if (ls->current == '[') { /* long comment? */ int sep = skip_sep(ls); - luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ + luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */ if (sep >= 0) { read_long_string(ls, NULL, sep); /* skip long comment */ luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ @@ -435,33 +463,41 @@ static int llex (LexState *ls, SemInfo *seminfo) { read_long_string(ls, seminfo, sep); return TK_STRING; } - else if (sep == -1) return '['; - else lexerror(ls, "invalid long string delimiter", TK_STRING); + else if (sep != -1) /* '[=...' missing second bracket */ + lexerror(ls, "invalid long string delimiter", TK_STRING); + return '['; } case '=': { next(ls); - if (ls->current != '=') return '='; - else { next(ls); return TK_EQ; } + if (check_next1(ls, '=')) return TK_EQ; + else return '='; } case '<': { next(ls); - if (ls->current != '=') return '<'; - else { next(ls); return TK_LE; } + if (check_next1(ls, '=')) return TK_LE; + else if (check_next1(ls, '<')) return TK_SHL; + else return '<'; } case '>': { next(ls); - if (ls->current != '=') return '>'; - else { next(ls); return TK_GE; } + if (check_next1(ls, '=')) return TK_GE; + else if (check_next1(ls, '>')) return TK_SHR; + else return '>'; + } + case '/': { + next(ls); + if (check_next1(ls, '/')) return TK_IDIV; + else return '/'; } case '~': { next(ls); - if (ls->current != '=') return '~'; - else { next(ls); return TK_NE; } + if (check_next1(ls, '=')) return TK_NE; + else return '~'; } case ':': { next(ls); - if (ls->current != ':') return ':'; - else { next(ls); return TK_DBCOLON; } + if (check_next1(ls, ':')) return TK_DBCOLON; + else return ':'; } case '"': case '\'': { /* short literal strings */ read_string(ls, ls->current, seminfo); @@ -469,18 +505,17 @@ static int llex (LexState *ls, SemInfo *seminfo) { } case '.': { /* '.', '..', '...', or number */ save_and_next(ls); - if (check_next(ls, ".")) { - if (check_next(ls, ".")) + if (check_next1(ls, '.')) { + if (check_next1(ls, '.')) return TK_DOTS; /* '...' */ else return TK_CONCAT; /* '..' */ } else if (!lisdigit(ls->current)) return '.'; - /* else go through */ + else return read_numeral(ls, seminfo); } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { - read_numeral(ls, seminfo); - return TK_NUMBER; + return read_numeral(ls, seminfo); } case EOZ: { return TK_EOS; @@ -495,7 +530,7 @@ static int llex (LexState *ls, SemInfo *seminfo) { luaZ_bufflen(ls->buff)); seminfo->ts = ts; if (isreserved(ts)) /* reserved word? */ - return ts->tsv.extra - 1 + FIRST_RESERVED; + return ts->extra - 1 + FIRST_RESERVED; else { return TK_NAME; } diff --git a/depends/lua/src/lmathlib.c b/depends/lua/src/lmathlib.c index fe9fc5423..94815f129 100644 --- a/depends/lua/src/lmathlib.c +++ b/depends/lua/src/lmathlib.c @@ -1,16 +1,18 @@ /* -** $Id: lmathlib.c,v 1.83.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lmathlib.c,v 1.117 2015/10/02 15:39:23 roberto Exp $ ** Standard mathematical library ** See Copyright Notice in lua.h */ +#define lmathlib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include -#define lmathlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -18,13 +20,30 @@ #undef PI -#define PI ((lua_Number)(3.1415926535897932384626433832795)) -#define RADIANS_PER_DEGREE ((lua_Number)(PI/180.0)) - +#define PI (l_mathop(3.141592653589793238462643383279502884)) + + +#if !defined(l_rand) /* { */ +#if defined(LUA_USE_POSIX) +#define l_rand() random() +#define l_srand(x) srandom(x) +#define L_RANDMAX 2147483647 /* (2^31 - 1), following POSIX */ +#else +#define l_rand() rand() +#define l_srand(x) srand(x) +#define L_RANDMAX RAND_MAX +#endif +#endif /* } */ static int math_abs (lua_State *L) { - lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); + if (lua_isinteger(L, 1)) { + lua_Integer n = lua_tointeger(L, 1); + if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n); + lua_pushinteger(L, n); + } + else + lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); return 1; } @@ -33,31 +52,16 @@ static int math_sin (lua_State *L) { return 1; } -static int math_sinh (lua_State *L) { - lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); - return 1; -} - static int math_cos (lua_State *L) { lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1))); return 1; } -static int math_cosh (lua_State *L) { - lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); - return 1; -} - static int math_tan (lua_State *L) { lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1))); return 1; } -static int math_tanh (lua_State *L) { - lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); - return 1; -} - static int math_asin (lua_State *L) { lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1))); return 1; @@ -69,49 +73,106 @@ static int math_acos (lua_State *L) { } static int math_atan (lua_State *L) { - lua_pushnumber(L, l_mathop(atan)(luaL_checknumber(L, 1))); + lua_Number y = luaL_checknumber(L, 1); + lua_Number x = luaL_optnumber(L, 2, 1); + lua_pushnumber(L, l_mathop(atan2)(y, x)); return 1; } -static int math_atan2 (lua_State *L) { - lua_pushnumber(L, l_mathop(atan2)(luaL_checknumber(L, 1), - luaL_checknumber(L, 2))); + +static int math_toint (lua_State *L) { + int valid; + lua_Integer n = lua_tointegerx(L, 1, &valid); + if (valid) + lua_pushinteger(L, n); + else { + luaL_checkany(L, 1); + lua_pushnil(L); /* value is not convertible to integer */ + } return 1; } -static int math_ceil (lua_State *L) { - lua_pushnumber(L, l_mathop(ceil)(luaL_checknumber(L, 1))); - return 1; + +static void pushnumint (lua_State *L, lua_Number d) { + lua_Integer n; + if (lua_numbertointeger(d, &n)) /* does 'd' fit in an integer? */ + lua_pushinteger(L, n); /* result is integer */ + else + lua_pushnumber(L, d); /* result is float */ } + static int math_floor (lua_State *L) { - lua_pushnumber(L, l_mathop(floor)(luaL_checknumber(L, 1))); + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own floor */ + else { + lua_Number d = l_mathop(floor)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } + return 1; +} + + +static int math_ceil (lua_State *L) { + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own ceil */ + else { + lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } return 1; } + static int math_fmod (lua_State *L) { - lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), - luaL_checknumber(L, 2))); + if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) { + lua_Integer d = lua_tointeger(L, 2); + if ((lua_Unsigned)d + 1u <= 1u) { /* special cases: -1 or 0 */ + luaL_argcheck(L, d != 0, 2, "zero"); + lua_pushinteger(L, 0); /* avoid overflow with 0x80000... / -1 */ + } + else + lua_pushinteger(L, lua_tointeger(L, 1) % d); + } + else + lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), + luaL_checknumber(L, 2))); return 1; } + +/* +** next function does not use 'modf', avoiding problems with 'double*' +** (which is not compatible with 'float*') when lua_Number is not +** 'double'. +*/ static int math_modf (lua_State *L) { - lua_Number ip; - lua_Number fp = l_mathop(modf)(luaL_checknumber(L, 1), &ip); - lua_pushnumber(L, ip); - lua_pushnumber(L, fp); + if (lua_isinteger(L ,1)) { + lua_settop(L, 1); /* number is its own integer part */ + lua_pushnumber(L, 0); /* no fractional part */ + } + else { + lua_Number n = luaL_checknumber(L, 1); + /* integer part (rounds toward zero) */ + lua_Number ip = (n < 0) ? l_mathop(ceil)(n) : l_mathop(floor)(n); + pushnumint(L, ip); + /* fractional part (test needed for inf/-inf) */ + lua_pushnumber(L, (n == ip) ? l_mathop(0.0) : (n - ip)); + } return 2; } + static int math_sqrt (lua_State *L) { lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1))); return 1; } -static int math_pow (lua_State *L) { - lua_Number x = luaL_checknumber(L, 1); - lua_Number y = luaL_checknumber(L, 2); - lua_pushnumber(L, l_mathop(pow)(x, y)); + +static int math_ult (lua_State *L) { + lua_Integer a = luaL_checkinteger(L, 1); + lua_Integer b = luaL_checkinteger(L, 2); + lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b); return 1; } @@ -122,145 +183,208 @@ static int math_log (lua_State *L) { res = l_mathop(log)(x); else { lua_Number base = luaL_checknumber(L, 2); - if (base == (lua_Number)10.0) res = l_mathop(log10)(x); +#if !defined(LUA_USE_C89) + if (base == 2.0) res = l_mathop(log2)(x); else +#endif + if (base == 10.0) res = l_mathop(log10)(x); else res = l_mathop(log)(x)/l_mathop(log)(base); } lua_pushnumber(L, res); return 1; } -#if defined(LUA_COMPAT_LOG10) -static int math_log10 (lua_State *L) { - lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); - return 1; -} -#endif - static int math_exp (lua_State *L) { lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1))); return 1; } static int math_deg (lua_State *L) { - lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE); + lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI)); return 1; } static int math_rad (lua_State *L) { - lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE); - return 1; -} - -static int math_frexp (lua_State *L) { - int e; - lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); - lua_pushinteger(L, e); - return 2; -} - -static int math_ldexp (lua_State *L) { - lua_Number x = luaL_checknumber(L, 1); - int ep = luaL_checkint(L, 2); - lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0))); return 1; } - static int math_min (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ - lua_Number dmin = luaL_checknumber(L, 1); + int imin = 1; /* index of current minimum value */ int i; - for (i=2; i<=n; i++) { - lua_Number d = luaL_checknumber(L, i); - if (d < dmin) - dmin = d; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, i, imin, LUA_OPLT)) + imin = i; } - lua_pushnumber(L, dmin); + lua_pushvalue(L, imin); return 1; } static int math_max (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ - lua_Number dmax = luaL_checknumber(L, 1); + int imax = 1; /* index of current maximum value */ int i; - for (i=2; i<=n; i++) { - lua_Number d = luaL_checknumber(L, i); - if (d > dmax) - dmax = d; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, imax, i, LUA_OPLT)) + imax = i; } - lua_pushnumber(L, dmax); + lua_pushvalue(L, imax); return 1; } - +/* +** This function uses 'double' (instead of 'lua_Number') to ensure that +** all bits from 'l_rand' can be represented, and that 'RANDMAX + 1.0' +** will keep full precision (ensuring that 'r' is always less than 1.0.) +*/ static int math_random (lua_State *L) { - /* the `%' avoids the (rare) case of r==1, and is needed also because on - some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ - lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; + lua_Integer low, up; + double r = (double)l_rand() * (1.0 / ((double)L_RANDMAX + 1.0)); switch (lua_gettop(L)) { /* check number of arguments */ case 0: { /* no arguments */ - lua_pushnumber(L, r); /* Number between 0 and 1 */ - break; + lua_pushnumber(L, (lua_Number)r); /* Number between 0 and 1 */ + return 1; } case 1: { /* only upper limit */ - lua_Number u = luaL_checknumber(L, 1); - luaL_argcheck(L, (lua_Number)1.0 <= u, 1, "interval is empty"); - lua_pushnumber(L, l_mathop(floor)(r*u) + (lua_Number)(1.0)); /* [1, u] */ + low = 1; + up = luaL_checkinteger(L, 1); break; } case 2: { /* lower and upper limits */ - lua_Number l = luaL_checknumber(L, 1); - lua_Number u = luaL_checknumber(L, 2); - luaL_argcheck(L, l <= u, 2, "interval is empty"); - lua_pushnumber(L, l_mathop(floor)(r*(u-l+1)) + l); /* [l, u] */ + low = luaL_checkinteger(L, 1); + up = luaL_checkinteger(L, 2); break; } default: return luaL_error(L, "wrong number of arguments"); } + /* random integer in the interval [low, up] */ + luaL_argcheck(L, low <= up, 1, "interval is empty"); + luaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1, + "interval too large"); + r *= (double)(up - low) + 1.0; + lua_pushinteger(L, (lua_Integer)r + low); return 1; } static int math_randomseed (lua_State *L) { - srand(luaL_checkunsigned(L, 1)); - (void)rand(); /* discard first value to avoid undesirable correlations */ + l_srand((unsigned int)(lua_Integer)luaL_checknumber(L, 1)); + (void)l_rand(); /* discard first value to avoid undesirable correlations */ return 0; } +static int math_type (lua_State *L) { + if (lua_type(L, 1) == LUA_TNUMBER) { + if (lua_isinteger(L, 1)) + lua_pushliteral(L, "integer"); + else + lua_pushliteral(L, "float"); + } + else { + luaL_checkany(L, 1); + lua_pushnil(L); + } + return 1; +} + + +/* +** {================================================================== +** Deprecated functions (for compatibility only) +** =================================================================== +*/ +#if defined(LUA_COMPAT_MATHLIB) + +static int math_cosh (lua_State *L) { + lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sinh (lua_State *L) { + lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tanh (lua_State *L) { + lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_pow (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number y = luaL_checknumber(L, 2); + lua_pushnumber(L, l_mathop(pow)(x, y)); + return 1; +} + +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + int ep = (int)luaL_checkinteger(L, 2); + lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + return 1; +} + +static int math_log10 (lua_State *L) { + lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); + return 1; +} + +#endif +/* }================================================================== */ + + + static const luaL_Reg mathlib[] = { {"abs", math_abs}, {"acos", math_acos}, {"asin", math_asin}, - {"atan2", math_atan2}, {"atan", math_atan}, {"ceil", math_ceil}, - {"cosh", math_cosh}, {"cos", math_cos}, {"deg", math_deg}, {"exp", math_exp}, + {"tointeger", math_toint}, {"floor", math_floor}, {"fmod", math_fmod}, - {"frexp", math_frexp}, - {"ldexp", math_ldexp}, -#if defined(LUA_COMPAT_LOG10) - {"log10", math_log10}, -#endif + {"ult", math_ult}, {"log", math_log}, {"max", math_max}, {"min", math_min}, {"modf", math_modf}, - {"pow", math_pow}, {"rad", math_rad}, {"random", math_random}, {"randomseed", math_randomseed}, - {"sinh", math_sinh}, {"sin", math_sin}, {"sqrt", math_sqrt}, - {"tanh", math_tanh}, {"tan", math_tan}, + {"type", math_type}, +#if defined(LUA_COMPAT_MATHLIB) + {"atan2", math_atan}, + {"cosh", math_cosh}, + {"sinh", math_sinh}, + {"tanh", math_tanh}, + {"pow", math_pow}, + {"frexp", math_frexp}, + {"ldexp", math_ldexp}, + {"log10", math_log10}, +#endif + /* placeholders */ + {"pi", NULL}, + {"huge", NULL}, + {"maxinteger", NULL}, + {"mininteger", NULL}, {NULL, NULL} }; @@ -272,8 +396,12 @@ LUAMOD_API int luaopen_math (lua_State *L) { luaL_newlib(L, mathlib); lua_pushnumber(L, PI); lua_setfield(L, -2, "pi"); - lua_pushnumber(L, HUGE_VAL); + lua_pushnumber(L, (lua_Number)HUGE_VAL); lua_setfield(L, -2, "huge"); + lua_pushinteger(L, LUA_MAXINTEGER); + lua_setfield(L, -2, "maxinteger"); + lua_pushinteger(L, LUA_MININTEGER); + lua_setfield(L, -2, "mininteger"); return 1; } diff --git a/depends/lua/src/lmem.c b/depends/lua/src/lmem.c index ee343e3e0..0a0476cc7 100644 --- a/depends/lua/src/lmem.c +++ b/depends/lua/src/lmem.c @@ -1,15 +1,17 @@ /* -** $Id: lmem.c,v 1.84.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lmem.c,v 1.91 2015/03/06 19:45:54 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ - -#include - #define lmem_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "ldebug.h" @@ -24,15 +26,15 @@ /* ** About the realloc function: ** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); -** (`osize' is the old size, `nsize' is the new size) +** ('osize' is the old size, 'nsize' is the new size) ** -** * frealloc(ud, NULL, x, s) creates a new block of size `s' (no +** * frealloc(ud, NULL, x, s) creates a new block of size 's' (no ** matter 'x'). ** -** * frealloc(ud, p, x, 0) frees the block `p' +** * frealloc(ud, p, x, 0) frees the block 'p' ** (in this specific case, frealloc must return NULL); ** particularly, frealloc(ud, NULL, 0, 0) does nothing -** (which is equivalent to free(NULL) in ANSI C) +** (which is equivalent to free(NULL) in ISO C) ** ** frealloc returns NULL if it cannot create or reallocate the area ** (any reallocation to an equal or smaller size cannot fail!) @@ -83,9 +85,8 @@ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { #endif newblock = (*g->frealloc)(g->ud, block, osize, nsize); if (newblock == NULL && nsize > 0) { - api_check(L, nsize > realosize, - "realloc cannot fail when shrinking a block"); - if (g->gcrunning) { + lua_assert(nsize > realosize); /* cannot fail when shrinking a block */ + if (g->version) { /* is state fully built? */ luaC_fullgc(L, 1); /* try to free some memory... */ newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ } diff --git a/depends/lua/src/loadlib.c b/depends/lua/src/loadlib.c index bedbea3e9..79119287a 100644 --- a/depends/lua/src/loadlib.c +++ b/depends/lua/src/loadlib.c @@ -1,5 +1,5 @@ /* -** $Id: loadlib.c,v 1.111.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: loadlib.c,v 1.127 2015/11/23 11:30:45 roberto Exp $ ** Dynamic library loader for Lua ** See Copyright Notice in lua.h ** @@ -8,22 +8,16 @@ ** systems. */ +#define loadlib_c +#define LUA_LIB -/* -** if needed, includes windows header before everything else -*/ -#if defined(_WIN32) -#include -#endif +#include "lprefix.h" +#include #include #include - -#define loadlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -31,21 +25,21 @@ /* -** LUA_PATH and LUA_CPATH are the names of the environment +** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment ** variables that Lua check to set its paths. */ -#if !defined(LUA_PATH) -#define LUA_PATH "LUA_PATH" +#if !defined(LUA_PATH_VAR) +#define LUA_PATH_VAR "LUA_PATH" #endif -#if !defined(LUA_CPATH) -#define LUA_CPATH "LUA_CPATH" +#if !defined(LUA_CPATH_VAR) +#define LUA_CPATH_VAR "LUA_CPATH" #endif #define LUA_PATHSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR -#define LUA_PATHVERSION LUA_PATH LUA_PATHSUFFIX -#define LUA_CPATHVERSION LUA_CPATH LUA_PATHSUFFIX +#define LUA_PATHVARVERSION LUA_PATH_VAR LUA_PATHSUFFIX +#define LUA_CPATHVARVERSION LUA_CPATH_VAR LUA_PATHSUFFIX /* ** LUA_PATH_SEP is the character that separates templates in a path. @@ -92,29 +86,45 @@ #define LUA_OFSEP "_" -/* table (in the registry) that keeps handles for all loaded C libraries */ -#define CLIBS "_CLIBS" +/* +** unique key for table in the registry that keeps handles +** for all loaded C libraries +*/ +static const int CLIBS = 0; #define LIB_FAIL "open" - -/* error codes for ll_loadfunc */ -#define ERRLIB 1 -#define ERRFUNC 2 - #define setprogdir(L) ((void)0) /* ** system-dependent functions */ -static void ll_unloadlib (void *lib); -static void *ll_load (lua_State *L, const char *path, int seeglb); -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); +/* +** unload library 'lib' +*/ +static void lsys_unloadlib (void *lib); + +/* +** load C library in file 'path'. If 'seeglb', load with all names in +** the library global. +** Returns the library; in case of error, returns NULL plus an +** error string in the stack. +*/ +static void *lsys_load (lua_State *L, const char *path, int seeglb); + +/* +** Try to find a function named 'sym' in library 'lib'. +** Returns the function; in case of error, returns NULL plus an +** error string in the stack. +*/ +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym); -#if defined(LUA_USE_DLOPEN) + + +#if defined(LUA_USE_DLOPEN) /* { */ /* ** {======================================================================== ** This is an implementation of loadlib based on the dlfcn interface. @@ -126,20 +136,32 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); #include -static void ll_unloadlib (void *lib) { +/* +** Macro to convert pointer-to-void* to pointer-to-function. This cast +** is undefined according to ISO C, but POSIX assumes that it works. +** (The '__extension__' in gnu compilers is only to avoid warnings.) +*/ +#if defined(__GNUC__) +#define cast_func(p) (__extension__ (lua_CFunction)(p)) +#else +#define cast_func(p) ((lua_CFunction)(p)) +#endif + + +static void lsys_unloadlib (void *lib) { dlclose(lib); } -static void *ll_load (lua_State *L, const char *path, int seeglb) { +static void *lsys_load (lua_State *L, const char *path, int seeglb) { void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)); if (lib == NULL) lua_pushstring(L, dlerror()); return lib; } -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { - lua_CFunction f = (lua_CFunction)dlsym(lib, sym); +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = cast_func(dlsym(lib, sym)); if (f == NULL) lua_pushstring(L, dlerror()); return f; } @@ -148,13 +170,15 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { -#elif defined(LUA_DL_DLL) +#elif defined(LUA_DL_DLL) /* }{ */ /* ** {====================================================================== ** This is an implementation of loadlib for Windows using native functions. ** ======================================================================= */ +#include + #undef setprogdir /* @@ -190,12 +214,12 @@ static void pusherror (lua_State *L) { lua_pushfstring(L, "system error %d\n", error); } -static void ll_unloadlib (void *lib) { +static void lsys_unloadlib (void *lib) { FreeLibrary((HMODULE)lib); } -static void *ll_load (lua_State *L, const char *path, int seeglb) { +static void *lsys_load (lua_State *L, const char *path, int seeglb) { HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS); (void)(seeglb); /* not used: symbols are 'global' by default */ if (lib == NULL) pusherror(L); @@ -203,7 +227,7 @@ static void *ll_load (lua_State *L, const char *path, int seeglb) { } -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { lua_CFunction f = (lua_CFunction)GetProcAddress((HMODULE)lib, sym); if (f == NULL) pusherror(L); return f; @@ -212,7 +236,7 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { /* }====================================================== */ -#else +#else /* }{ */ /* ** {====================================================== ** Fallback for other systems @@ -226,31 +250,34 @@ static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { #define DLMSG "dynamic libraries not enabled; check your Lua installation" -static void ll_unloadlib (void *lib) { +static void lsys_unloadlib (void *lib) { (void)(lib); /* not used */ } -static void *ll_load (lua_State *L, const char *path, int seeglb) { +static void *lsys_load (lua_State *L, const char *path, int seeglb) { (void)(path); (void)(seeglb); /* not used */ lua_pushliteral(L, DLMSG); return NULL; } -static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { (void)(lib); (void)(sym); /* not used */ lua_pushliteral(L, DLMSG); return NULL; } /* }====================================================== */ -#endif +#endif /* } */ -static void *ll_checkclib (lua_State *L, const char *path) { +/* +** return registry.CLIBS[path] +*/ +static void *checkclib (lua_State *L, const char *path) { void *plib; - lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_rawgetp(L, LUA_REGISTRYINDEX, &CLIBS); lua_getfield(L, -1, path); plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ lua_pop(L, 2); /* pop CLIBS table and 'plib' */ @@ -258,8 +285,12 @@ static void *ll_checkclib (lua_State *L, const char *path) { } -static void ll_addtoclib (lua_State *L, const char *path, void *plib) { - lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); +/* +** registry.CLIBS[path] = plib -- for queries +** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries +*/ +static void addtoclib (lua_State *L, const char *path, void *plib) { + lua_rawgetp(L, LUA_REGISTRYINDEX, &CLIBS); lua_pushlightuserdata(L, plib); lua_pushvalue(L, -1); lua_setfield(L, -3, path); /* CLIBS[path] = plib */ @@ -269,33 +300,49 @@ static void ll_addtoclib (lua_State *L, const char *path, void *plib) { /* -** __gc tag method for CLIBS table: calls 'll_unloadlib' for all lib +** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib ** handles in list CLIBS */ static int gctm (lua_State *L) { - int n = luaL_len(L, 1); + lua_Integer n = luaL_len(L, 1); for (; n >= 1; n--) { /* for each handle, in reverse order */ lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ - ll_unloadlib(lua_touserdata(L, -1)); + lsys_unloadlib(lua_touserdata(L, -1)); lua_pop(L, 1); /* pop handle */ } return 0; } -static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { - void *reg = ll_checkclib(L, path); /* check loaded C libraries */ + +/* error codes for 'lookforfunc' */ +#define ERRLIB 1 +#define ERRFUNC 2 + +/* +** Look for a C function named 'sym' in a dynamically loaded library +** 'path'. +** First, check whether the library is already loaded; if not, try +** to load it. +** Then, if 'sym' is '*', return true (as library has been loaded). +** Otherwise, look for symbol 'sym' in the library and push a +** C function with that symbol. +** Return 0 and 'true' or a function in the stack; in case of +** errors, return an error code and an error message in the stack. +*/ +static int lookforfunc (lua_State *L, const char *path, const char *sym) { + void *reg = checkclib(L, path); /* check loaded C libraries */ if (reg == NULL) { /* must load library? */ - reg = ll_load(L, path, *sym == '*'); + reg = lsys_load(L, path, *sym == '*'); /* global symbols if 'sym'=='*' */ if (reg == NULL) return ERRLIB; /* unable to load library */ - ll_addtoclib(L, path, reg); + addtoclib(L, path, reg); } if (*sym == '*') { /* loading only library (no function)? */ lua_pushboolean(L, 1); /* return 'true' */ return 0; /* no errors */ } else { - lua_CFunction f = ll_sym(L, reg, sym); + lua_CFunction f = lsys_sym(L, reg, sym); if (f == NULL) return ERRFUNC; /* unable to find function */ lua_pushcfunction(L, f); /* else create new function */ @@ -307,7 +354,7 @@ static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { static int ll_loadlib (lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *init = luaL_checkstring(L, 2); - int stat = ll_loadfunc(L, path, init); + int stat = lookforfunc(L, path, init); if (stat == 0) /* no errors? */ return 1; /* return the loaded function */ else { /* error; error message is on stack top */ @@ -360,7 +407,7 @@ static const char *searchpath (lua_State *L, const char *name, lua_remove(L, -2); /* remove path template */ if (readable(filename)) /* does file exist and is readable? */ return filename; /* return that file name */ - lua_pushfstring(L, "\n\tno file " LUA_QS, filename); + lua_pushfstring(L, "\n\tno file '%s'", filename); lua_remove(L, -2); /* remove file name */ luaL_addvalue(&msg); /* concatenate error msg. entry */ } @@ -390,7 +437,7 @@ static const char *findfile (lua_State *L, const char *name, lua_getfield(L, lua_upvalueindex(1), pname); path = lua_tostring(L, -1); if (path == NULL) - luaL_error(L, LUA_QL("package.%s") " must be a string", pname); + luaL_error(L, "'package.%s' must be a string", pname); return searchpath(L, name, path, ".", dirsep); } @@ -401,8 +448,7 @@ static int checkload (lua_State *L, int stat, const char *filename) { return 2; /* return open function and file name */ } else - return luaL_error(L, "error loading module " LUA_QS - " from file " LUA_QS ":\n\t%s", + return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s", lua_tostring(L, 1), filename, lua_tostring(L, -1)); } @@ -416,21 +462,29 @@ static int searcher_Lua (lua_State *L) { } +/* +** Try to find a load function for module 'modname' at file 'filename'. +** First, change '.' to '_' in 'modname'; then, if 'modname' has +** the form X-Y (that is, it has an "ignore mark"), build a function +** name "luaopen_X" and look for it. (For compatibility, if that +** fails, it also tries "luaopen_Y".) If there is no ignore mark, +** look for a function named "luaopen_modname". +*/ static int loadfunc (lua_State *L, const char *filename, const char *modname) { - const char *funcname; + const char *openfunc; const char *mark; modname = luaL_gsub(L, modname, ".", LUA_OFSEP); mark = strchr(modname, *LUA_IGMARK); if (mark) { int stat; - funcname = lua_pushlstring(L, modname, mark - modname); - funcname = lua_pushfstring(L, LUA_POF"%s", funcname); - stat = ll_loadfunc(L, filename, funcname); + openfunc = lua_pushlstring(L, modname, mark - modname); + openfunc = lua_pushfstring(L, LUA_POF"%s", openfunc); + stat = lookforfunc(L, filename, openfunc); if (stat != ERRFUNC) return stat; modname = mark + 1; /* else go ahead and try old-style name */ } - funcname = lua_pushfstring(L, LUA_POF"%s", modname); - return ll_loadfunc(L, filename, funcname); + openfunc = lua_pushfstring(L, LUA_POF"%s", modname); + return lookforfunc(L, filename, openfunc); } @@ -455,8 +509,7 @@ static int searcher_Croot (lua_State *L) { if (stat != ERRFUNC) return checkload(L, 0, filename); /* real error */ else { /* open function not found */ - lua_pushfstring(L, "\n\tno module " LUA_QS " in file " LUA_QS, - name, filename); + lua_pushfstring(L, "\n\tno module '%s' in file '%s'", name, filename); return 1; } } @@ -468,8 +521,7 @@ static int searcher_Croot (lua_State *L) { static int searcher_preload (lua_State *L) { const char *name = luaL_checkstring(L, 1); lua_getfield(L, LUA_REGISTRYINDEX, "_PRELOAD"); - lua_getfield(L, -1, name); - if (lua_isnil(L, -1)) /* not found? */ + if (lua_getfield(L, -1, name) == LUA_TNIL) /* not found? */ lua_pushfstring(L, "\n\tno field package.preload['%s']", name); return 1; } @@ -479,17 +531,15 @@ static void findloader (lua_State *L, const char *name) { int i; luaL_Buffer msg; /* to build error message */ luaL_buffinit(L, &msg); - lua_getfield(L, lua_upvalueindex(1), "searchers"); /* will be at index 3 */ - if (!lua_istable(L, 3)) - luaL_error(L, LUA_QL("package.searchers") " must be a table"); + /* push 'package.searchers' to index 3 in the stack */ + if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE) + luaL_error(L, "'package.searchers' must be a table"); /* iterate over available searchers to find a loader */ for (i = 1; ; i++) { - lua_rawgeti(L, 3, i); /* get a searcher */ - if (lua_isnil(L, -1)) { /* no more searchers? */ + if (lua_rawgeti(L, 3, i) == LUA_TNIL) { /* no more searchers? */ lua_pop(L, 1); /* remove nil */ luaL_pushresult(&msg); /* create error message */ - luaL_error(L, "module " LUA_QS " not found:%s", - name, lua_tostring(L, -1)); + luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); } lua_pushstring(L, name); lua_call(L, 1, 2); /* call it */ @@ -520,8 +570,7 @@ static int ll_require (lua_State *L) { lua_call(L, 2, 1); /* run loader to load module */ if (!lua_isnil(L, -1)) /* non-nil return? */ lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ - lua_getfield(L, 2, name); - if (lua_isnil(L, -1)) { /* module did not set a value? */ + if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */ lua_pushboolean(L, 1); /* use true as result */ lua_pushvalue(L, -1); /* extra copy to be returned */ lua_setfield(L, 2, name); /* _LOADED[name] = true */ @@ -548,7 +597,7 @@ static void set_env (lua_State *L) { if (lua_getstack(L, 1, &ar) == 0 || lua_getinfo(L, "f", &ar) == 0 || /* get calling function */ lua_iscfunction(L, -1)) - luaL_error(L, LUA_QL("module") " not called from a Lua function"); + luaL_error(L, "'module' not called from a Lua function"); lua_pushvalue(L, -2); /* copy new environment table to top */ lua_setupvalue(L, -2, 1); lua_pop(L, 1); /* remove function */ @@ -587,9 +636,8 @@ static int ll_module (lua_State *L) { int lastarg = lua_gettop(L); /* last parameter */ luaL_pushmodule(L, modname, 1); /* get/create module table */ /* check whether table already has a _NAME field */ - lua_getfield(L, -1, "_NAME"); - if (!lua_isnil(L, -1)) /* is table an initialized module? */ - lua_pop(L, 1); + if (lua_getfield(L, -1, "_NAME") != LUA_TNIL) + lua_pop(L, 1); /* table is an initialized module */ else { /* no; initialize it */ lua_pop(L, 1); modinit(L, modname); @@ -659,6 +707,12 @@ static const luaL_Reg pk_funcs[] = { #if defined(LUA_COMPAT_MODULE) {"seeall", ll_seeall}, #endif + /* placeholders */ + {"preload", NULL}, + {"cpath", NULL}, + {"path", NULL}, + {"searchers", NULL}, + {"loaded", NULL}, {NULL, NULL} }; @@ -678,42 +732,50 @@ static void createsearcherstable (lua_State *L) { int i; /* create 'searchers' table */ lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); - /* fill it with pre-defined searchers */ + /* fill it with predefined searchers */ for (i=0; searchers[i] != NULL; i++) { lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ lua_pushcclosure(L, searchers[i], 1); lua_rawseti(L, -2, i+1); } +#if defined(LUA_COMPAT_LOADERS) + lua_pushvalue(L, -1); /* make a copy of 'searchers' table */ + lua_setfield(L, -3, "loaders"); /* put it in field 'loaders' */ +#endif + lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ } -LUAMOD_API int luaopen_package (lua_State *L) { - /* create table CLIBS to keep track of loaded C libraries */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); - lua_createtable(L, 0, 1); /* metatable for CLIBS */ +/* +** create table CLIBS to keep track of loaded C libraries, +** setting a finalizer to close all libraries when closing state. +*/ +static void createclibstable (lua_State *L) { + lua_newtable(L); /* create CLIBS table */ + lua_createtable(L, 0, 1); /* create metatable for CLIBS */ lua_pushcfunction(L, gctm); lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ lua_setmetatable(L, -2); - /* create `package' table */ - luaL_newlib(L, pk_funcs); + lua_rawsetp(L, LUA_REGISTRYINDEX, &CLIBS); /* set CLIBS table in registry */ +} + + +LUAMOD_API int luaopen_package (lua_State *L) { + createclibstable(L); + luaL_newlib(L, pk_funcs); /* create 'package' table */ createsearcherstable(L); -#if defined(LUA_COMPAT_LOADERS) - lua_pushvalue(L, -1); /* make a copy of 'searchers' table */ - lua_setfield(L, -3, "loaders"); /* put it in field `loaders' */ -#endif - lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ /* set field 'path' */ - setpath(L, "path", LUA_PATHVERSION, LUA_PATH, LUA_PATH_DEFAULT); + setpath(L, "path", LUA_PATHVARVERSION, LUA_PATH_VAR, LUA_PATH_DEFAULT); /* set field 'cpath' */ - setpath(L, "cpath", LUA_CPATHVERSION, LUA_CPATH, LUA_CPATH_DEFAULT); + setpath(L, "cpath", LUA_CPATHVARVERSION, LUA_CPATH_VAR, LUA_CPATH_DEFAULT); /* store config information */ lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); lua_setfield(L, -2, "config"); - /* set field `loaded' */ + /* set field 'loaded' */ luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); lua_setfield(L, -2, "loaded"); - /* set field `preload' */ + /* set field 'preload' */ luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); lua_setfield(L, -2, "preload"); lua_pushglobaltable(L); diff --git a/depends/lua/src/lobject.c b/depends/lua/src/lobject.c index 882d994d4..0c6e7a9e7 100644 --- a/depends/lua/src/lobject.c +++ b/depends/lua/src/lobject.c @@ -1,17 +1,22 @@ /* -** $Id: lobject.c,v 2.58.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lobject.c,v 2.111 2016/05/20 14:07:48 roberto Exp $ ** Some generic functions over Lua objects ** See Copyright Notice in lua.h */ +#define lobject_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include #include #include #include #include -#define lobject_c -#define LUA_CORE - #include "lua.h" #include "lctype.h" @@ -36,8 +41,12 @@ LUAI_DDEF const TValue luaO_nilobject_ = {NILCONSTANT}; int luaO_int2fb (unsigned int x) { int e = 0; /* exponent */ if (x < 8) return x; - while (x >= 0x10) { - x = (x+1) >> 1; + while (x >= (8 << 4)) { /* coarse steps */ + x = (x + 0xf) >> 4; /* x = ceil(x / 16) */ + e += 4; + } + while (x >= (8 << 1)) { /* fine steps */ + x = (x + 1) >> 1; /* x = ceil(x / 2) */ e++; } return ((e+1) << 3) | (cast_int(x) - 8); @@ -46,14 +55,15 @@ int luaO_int2fb (unsigned int x) { /* converts back */ int luaO_fb2int (int x) { - int e = (x >> 3) & 0x1f; - if (e == 0) return x; - else return ((x & 7) + 8) << (e - 1); + return (x < 8) ? x : ((x & 7) + 8) << ((x >> 3) - 1); } +/* +** Computes ceil(log2(x)) +*/ int luaO_ceillog2 (unsigned int x) { - static const lu_byte log_2[256] = { + static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */ 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, @@ -70,29 +80,90 @@ int luaO_ceillog2 (unsigned int x) { } -lua_Number luaO_arith (int op, lua_Number v1, lua_Number v2) { +static lua_Integer intarith (lua_State *L, int op, lua_Integer v1, + lua_Integer v2) { switch (op) { - case LUA_OPADD: return luai_numadd(NULL, v1, v2); - case LUA_OPSUB: return luai_numsub(NULL, v1, v2); - case LUA_OPMUL: return luai_nummul(NULL, v1, v2); - case LUA_OPDIV: return luai_numdiv(NULL, v1, v2); - case LUA_OPMOD: return luai_nummod(NULL, v1, v2); - case LUA_OPPOW: return luai_numpow(NULL, v1, v2); - case LUA_OPUNM: return luai_numunm(NULL, v1); + case LUA_OPADD: return intop(+, v1, v2); + case LUA_OPSUB:return intop(-, v1, v2); + case LUA_OPMUL:return intop(*, v1, v2); + case LUA_OPMOD: return luaV_mod(L, v1, v2); + case LUA_OPIDIV: return luaV_div(L, v1, v2); + case LUA_OPBAND: return intop(&, v1, v2); + case LUA_OPBOR: return intop(|, v1, v2); + case LUA_OPBXOR: return intop(^, v1, v2); + case LUA_OPSHL: return luaV_shiftl(v1, v2); + case LUA_OPSHR: return luaV_shiftl(v1, -v2); + case LUA_OPUNM: return intop(-, 0, v1); + case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1); default: lua_assert(0); return 0; } } -int luaO_hexavalue (int c) { - if (lisdigit(c)) return c - '0'; - else return ltolower(c) - 'a' + 10; +static lua_Number numarith (lua_State *L, int op, lua_Number v1, + lua_Number v2) { + switch (op) { + case LUA_OPADD: return luai_numadd(L, v1, v2); + case LUA_OPSUB: return luai_numsub(L, v1, v2); + case LUA_OPMUL: return luai_nummul(L, v1, v2); + case LUA_OPDIV: return luai_numdiv(L, v1, v2); + case LUA_OPPOW: return luai_numpow(L, v1, v2); + case LUA_OPIDIV: return luai_numidiv(L, v1, v2); + case LUA_OPUNM: return luai_numunm(L, v1); + case LUA_OPMOD: { + lua_Number m; + luai_nummod(L, v1, v2, m); + return m; + } + default: lua_assert(0); return 0; + } } -#if !defined(lua_strx2number) +void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, + TValue *res) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: + case LUA_OPBNOT: { /* operate only on integers */ + lua_Integer i1; lua_Integer i2; + if (tointeger(p1, &i1) && tointeger(p2, &i2)) { + setivalue(res, intarith(L, op, i1, i2)); + return; + } + else break; /* go to the end */ + } + case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */ + lua_Number n1; lua_Number n2; + if (tonumber(p1, &n1) && tonumber(p2, &n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return; + } + else break; /* go to the end */ + } + default: { /* other operations */ + lua_Number n1; lua_Number n2; + if (ttisinteger(p1) && ttisinteger(p2)) { + setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2))); + return; + } + else if (tonumber(p1, &n1) && tonumber(p2, &n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return; + } + else break; /* go to the end */ + } + } + /* could not perform raw operation; try metamethod */ + lua_assert(L != NULL); /* should not fail when folding (compile time) */ + luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD)); +} -#include + +int luaO_hexavalue (int c) { + if (lisdigit(c)) return c - '0'; + else return (ltolower(c) - 'a') + 10; +} static int isneg (const char **s) { @@ -102,122 +173,285 @@ static int isneg (const char **s) { } -static lua_Number readhexa (const char **s, lua_Number r, int *count) { - for (; lisxdigit(cast_uchar(**s)); (*s)++) { /* read integer part */ - r = (r * cast_num(16.0)) + cast_num(luaO_hexavalue(cast_uchar(**s))); - (*count)++; - } - return r; -} +/* +** {================================================================== +** Lua's implementation for 'lua_strx2number' +** =================================================================== +*/ + +#if !defined(lua_strx2number) + +/* maximum number of significant digits to read (to avoid overflows + even with single floats) */ +#define MAXSIGDIG 30 /* ** convert an hexadecimal numeric string to a number, following ** C99 specification for 'strtod' */ static lua_Number lua_strx2number (const char *s, char **endptr) { - lua_Number r = 0.0; - int e = 0, i = 0; - int neg = 0; /* 1 if number is negative */ + int dot = lua_getlocaledecpoint(); + lua_Number r = 0.0; /* result (accumulator) */ + int sigdig = 0; /* number of significant digits */ + int nosigdig = 0; /* number of non-significant digits */ + int e = 0; /* exponent correction */ + int neg; /* 1 if number is negative */ + int hasdot = 0; /* true after seen a dot */ *endptr = cast(char *, s); /* nothing is valid yet */ while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ neg = isneg(&s); /* check signal */ if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ return 0.0; /* invalid format (no '0x') */ - s += 2; /* skip '0x' */ - r = readhexa(&s, r, &i); /* read integer part */ - if (*s == '.') { - s++; /* skip dot */ - r = readhexa(&s, r, &e); /* read fractional part */ + for (s += 2; ; s++) { /* skip '0x' and read numeral */ + if (*s == dot) { + if (hasdot) break; /* second dot? stop loop */ + else hasdot = 1; + } + else if (lisxdigit(cast_uchar(*s))) { + if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */ + nosigdig++; + else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ + r = (r * cast_num(16.0)) + luaO_hexavalue(*s); + else e++; /* too many digits; ignore, but still count for exponent */ + if (hasdot) e--; /* decimal digit? correct exponent */ + } + else break; /* neither a dot nor a digit */ } - if (i == 0 && e == 0) - return 0.0; /* invalid format (no digit) */ - e *= -4; /* each fractional digit divides value by 2^-4 */ + if (nosigdig + sigdig == 0) /* no digits? */ + return 0.0; /* invalid format */ *endptr = cast(char *, s); /* valid up to here */ + e *= 4; /* each digit multiplies/divides value by 2^4 */ if (*s == 'p' || *s == 'P') { /* exponent part? */ - int exp1 = 0; - int neg1; + int exp1 = 0; /* exponent value */ + int neg1; /* exponent signal */ s++; /* skip 'p' */ neg1 = isneg(&s); /* signal */ if (!lisdigit(cast_uchar(*s))) - goto ret; /* must have at least one digit */ + return 0.0; /* invalid; must have at least one digit */ while (lisdigit(cast_uchar(*s))) /* read exponent */ exp1 = exp1 * 10 + *(s++) - '0'; if (neg1) exp1 = -exp1; e += exp1; + *endptr = cast(char *, s); /* valid up to here */ } - *endptr = cast(char *, s); /* valid up to here */ - ret: if (neg) r = -r; return l_mathop(ldexp)(r, e); } #endif +/* }====================================================== */ + +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif -int luaO_str2d (const char *s, size_t len, lua_Number *result) { +static const char *l_str2dloc (const char *s, lua_Number *result, int mode) { char *endptr; - if (strpbrk(s, "nN")) /* reject 'inf' and 'nan' */ - return 0; - else if (strpbrk(s, "xX")) /* hexa? */ - *result = lua_strx2number(s, &endptr); + *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */ + : lua_str2number(s, &endptr); + if (endptr == s) return NULL; /* nothing recognized? */ + while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */ + return (*endptr == '\0') ? endptr : NULL; /* OK if no trailing characters */ +} + + +/* +** Convert string 's' to a Lua number (put in 'result'). Return NULL +** on fail or the address of the ending '\0' on success. +** 'pmode' points to (and 'mode' contains) special things in the string: +** - 'x'/'X' means an hexadecimal numeral +** - 'n'/'N' means 'inf' or 'nan' (which should be rejected) +** - '.' just optimizes the search for the common case (nothing special) +** This function accepts both the current locale or a dot as the radix +** mark. If the convertion fails, it may mean number has a dot but +** locale accepts something else. In that case, the code copies 's' +** to a buffer (because 's' is read-only), changes the dot to the +** current locale radix mark, and tries to convert again. +*/ +static const char *l_str2d (const char *s, lua_Number *result) { + const char *endptr; + const char *pmode = strpbrk(s, ".xXnN"); + int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0; + if (mode == 'n') /* reject 'inf' and 'nan' */ + return NULL; + endptr = l_str2dloc(s, result, mode); /* try to convert */ + if (endptr == NULL) { /* failed? may be a different locale */ + char buff[L_MAXLENNUM + 1]; + char *pdot = (char*)strchr(s, '.'); + if (strlen(s) > L_MAXLENNUM || pdot == NULL) + return NULL; /* string too long or no dot; fail */ + strcpy(buff, s); /* copy string to buffer */ + buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */ + endptr = l_str2dloc(buff, result, mode); /* try again */ + if (endptr != NULL) + endptr = s + (endptr - buff); /* make relative to 's' */ + } + return endptr; +} + + +#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10) +#define MAXLASTD cast_int(LUA_MAXINTEGER % 10) + +static const char *l_str2int (const char *s, lua_Integer *result) { + lua_Unsigned a = 0; + int empty = 1; + int neg; + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); + if (s[0] == '0' && + (s[1] == 'x' || s[1] == 'X')) { /* hex? */ + s += 2; /* skip '0x' */ + for (; lisxdigit(cast_uchar(*s)); s++) { + a = a * 16 + luaO_hexavalue(*s); + empty = 0; + } + } + else { /* decimal */ + for (; lisdigit(cast_uchar(*s)); s++) { + int d = *s - '0'; + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return NULL; /* do not accept it (as integer) */ + a = a * 10 + d; + empty = 0; + } + } + while (lisspace(cast_uchar(*s))) s++; /* skip trailing spaces */ + if (empty || *s != '\0') return NULL; /* something wrong in the numeral */ + else { + *result = l_castU2S((neg) ? 0u - a : a); + return s; + } +} + + +size_t luaO_str2num (const char *s, TValue *o) { + lua_Integer i; lua_Number n; + const char *e; + if ((e = l_str2int(s, &i)) != NULL) { /* try as an integer */ + setivalue(o, i); + } + else if ((e = l_str2d(s, &n)) != NULL) { /* else try as a float */ + setfltvalue(o, n); + } else - *result = lua_str2number(s, &endptr); - if (endptr == s) return 0; /* nothing recognized */ - while (lisspace(cast_uchar(*endptr))) endptr++; - return (endptr == s + len); /* OK if no trailing characters */ + return 0; /* conversion failed */ + return (e - s) + 1; /* success; return string size */ +} + + +int luaO_utf8esc (char *buff, unsigned long x) { + int n = 1; /* number of bytes put in buffer (backwards) */ + lua_assert(x <= 0x10FFFF); + if (x < 0x80) /* ascii? */ + buff[UTF8BUFFSZ - 1] = cast(char, x); + else { /* need continuation bytes */ + unsigned int mfb = 0x3f; /* maximum that fits in first byte */ + do { /* add continuation bytes */ + buff[UTF8BUFFSZ - (n++)] = cast(char, 0x80 | (x & 0x3f)); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8BUFFSZ - n] = cast(char, (~mfb << 1) | x); /* add first byte */ + } + return n; } +/* maximum length of the conversion of a number to a string */ +#define MAXNUMBER2STR 50 + + +/* +** Convert a number object to a string +*/ +void luaO_tostring (lua_State *L, StkId obj) { + char buff[MAXNUMBER2STR]; + size_t len; + lua_assert(ttisnumber(obj)); + if (ttisinteger(obj)) + len = lua_integer2str(buff, sizeof(buff), ivalue(obj)); + else { + len = lua_number2str(buff, sizeof(buff), fltvalue(obj)); +#if !defined(LUA_COMPAT_FLOATSTRING) + if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ + buff[len++] = lua_getlocaledecpoint(); + buff[len++] = '0'; /* adds '.0' to result */ + } +#endif + } + setsvalue2s(L, obj, luaS_newlstr(L, buff, len)); +} + static void pushstr (lua_State *L, const char *str, size_t l) { - setsvalue2s(L, L->top++, luaS_newlstr(L, str, l)); + setsvalue2s(L, L->top, luaS_newlstr(L, str, l)); + luaD_inctop(L); } -/* this function handles only `%d', `%c', %f, %p, and `%s' formats */ +/* +** this function handles only '%d', '%c', '%f', '%p', and '%s' + conventional formats, plus Lua-specific '%I' and '%U' +*/ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { int n = 0; for (;;) { const char *e = strchr(fmt, '%'); if (e == NULL) break; - luaD_checkstack(L, 2); /* fmt + item */ pushstr(L, fmt, e - fmt); switch (*(e+1)) { - case 's': { + case 's': { /* zero-terminated string */ const char *s = va_arg(argp, char *); if (s == NULL) s = "(null)"; pushstr(L, s, strlen(s)); break; } - case 'c': { - char buff; - buff = cast(char, va_arg(argp, int)); - pushstr(L, &buff, 1); + case 'c': { /* an 'int' as a character */ + char buff = cast(char, va_arg(argp, int)); + if (lisprint(cast_uchar(buff))) + pushstr(L, &buff, 1); + else /* non-printable character; print its code */ + luaO_pushfstring(L, "<\\%d>", cast_uchar(buff)); break; } - case 'd': { - setnvalue(L->top++, cast_num(va_arg(argp, int))); - break; + case 'd': { /* an 'int' */ + setivalue(L->top, va_arg(argp, int)); + goto top2str; + } + case 'I': { /* a 'lua_Integer' */ + setivalue(L->top, cast(lua_Integer, va_arg(argp, l_uacInt))); + goto top2str; } - case 'f': { - setnvalue(L->top++, cast_num(va_arg(argp, l_uacNumber))); + case 'f': { /* a 'lua_Number' */ + setfltvalue(L->top, cast_num(va_arg(argp, l_uacNumber))); + top2str: /* convert the top element to a string */ + luaD_inctop(L); + luaO_tostring(L, L->top - 1); break; } - case 'p': { - char buff[4*sizeof(void *) + 8]; /* should be enough space for a `%p' */ - int l = sprintf(buff, "%p", va_arg(argp, void *)); + case 'p': { /* a pointer */ + char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */ + int l = l_sprintf(buff, sizeof(buff), "%p", va_arg(argp, void *)); pushstr(L, buff, l); break; } + case 'U': { /* an 'int' as a UTF-8 sequence */ + char buff[UTF8BUFFSZ]; + int l = luaO_utf8esc(buff, cast(long, va_arg(argp, long))); + pushstr(L, buff + UTF8BUFFSZ - l, l); + break; + } case '%': { pushstr(L, "%", 1); break; } default: { - luaG_runerror(L, - "invalid option " LUA_QL("%%%c") " to " LUA_QL("lua_pushfstring"), - *(e + 1)); + luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'", + *(e + 1)); } } n += 2; diff --git a/depends/lua/src/lopcodes.c b/depends/lua/src/lopcodes.c index 4190dc762..a1cbef857 100644 --- a/depends/lua/src/lopcodes.c +++ b/depends/lua/src/lopcodes.c @@ -1,13 +1,16 @@ /* -** $Id: lopcodes.c,v 1.49.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lopcodes.c,v 1.55 2015/01/05 13:48:33 roberto Exp $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ - #define lopcodes_c #define LUA_CORE +#include "lprefix.h" + + +#include #include "lopcodes.h" @@ -31,10 +34,17 @@ LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = { "ADD", "SUB", "MUL", - "DIV", "MOD", "POW", + "DIV", + "IDIV", + "BAND", + "BOR", + "BXOR", + "SHL", + "SHR", "UNM", + "BNOT", "NOT", "LEN", "CONCAT", @@ -79,10 +89,17 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_IDIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BAND */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BOR */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BXOR */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SHL */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SHR */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_BNOT */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ diff --git a/depends/lua/src/loslib.c b/depends/lua/src/loslib.c index 052ba1744..481065550 100644 --- a/depends/lua/src/loslib.c +++ b/depends/lua/src/loslib.c @@ -1,9 +1,14 @@ /* -** $Id: loslib.c,v 1.40.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: loslib.c,v 1.64 2016/04/18 13:06:55 roberto Exp $ ** Standard Operating System library ** See Copyright Notice in lua.h */ +#define loslib_c +#define LUA_LIB + +#include "lprefix.h" + #include #include @@ -11,9 +16,6 @@ #include #include -#define loslib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -21,60 +23,119 @@ /* -** list of valid conversion specifiers for the 'strftime' function +** {================================================================== +** List of valid conversion specifiers for the 'strftime' function; +** options are grouped by length; group of length 2 start with '||'. +** =================================================================== */ -#if !defined(LUA_STRFTIMEOPTIONS) - -#if !defined(LUA_USE_POSIX) -#define LUA_STRFTIMEOPTIONS { "aAbBcdHIjmMpSUwWxXyYz%", "" } -#else -#define LUA_STRFTIMEOPTIONS \ - { "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%", "" \ - "", "E", "cCxXyY", \ - "O", "deHImMSuUVwWy" } -#endif +#if !defined(LUA_STRFTIMEOPTIONS) /* { */ +/* options for ANSI C 89 */ +#define L_STRFTIMEC89 "aAbBcdHIjmMpSUwWxXyYZ%" + +/* options for ISO C 99 and POSIX */ +#define L_STRFTIMEC99 "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ + "||" "EcECExEXEyEY" "OdOeOHOIOmOMOSOuOUOVOwOWOy" + +/* options for Windows */ +#define L_STRFTIMEWIN "aAbBcdHIjmMpSUwWxXyYzZ%" \ + "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" + +#if defined(LUA_USE_WINDOWS) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEWIN +#elif defined(LUA_USE_C89) +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC89 +#else /* C99 specification */ +#define LUA_STRFTIMEOPTIONS L_STRFTIMEC99 #endif +#endif /* } */ +/* }================================================================== */ /* -** By default, Lua uses tmpnam except when POSIX is available, where it -** uses mkstemp. +** {================================================================== +** Configuration for time-related stuff +** =================================================================== */ -#if defined(LUA_USE_MKSTEMP) -#include -#define LUA_TMPNAMBUFSIZE 32 -#define lua_tmpnam(b,e) { \ - strcpy(b, "/tmp/lua_XXXXXX"); \ - e = mkstemp(b); \ - if (e != -1) close(e); \ - e = (e == -1); } -#elif !defined(lua_tmpnam) +#if !defined(l_time_t) /* { */ +/* +** type to represent time_t in Lua +*/ +#define l_timet lua_Integer +#define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) -#define LUA_TMPNAMBUFSIZE L_tmpnam -#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } +static time_t l_checktime (lua_State *L, int arg) { + lua_Integer t = luaL_checkinteger(L, arg); + luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); + return (time_t)t; +} -#endif +#endif /* } */ +#if !defined(l_gmtime) /* { */ /* ** By default, Lua uses gmtime/localtime, except when POSIX is available, ** where it uses gmtime_r/localtime_r */ -#if defined(LUA_USE_GMTIME_R) + +#if defined(LUA_USE_POSIX) /* { */ #define l_gmtime(t,r) gmtime_r(t,r) #define l_localtime(t,r) localtime_r(t,r) -#elif !defined(l_gmtime) +#else /* }{ */ + +/* ISO C definitions */ +#define l_gmtime(t,r) ((void)(r)->tm_sec, gmtime(t)) +#define l_localtime(t,r) ((void)(r)->tm_sec, localtime(t)) + +#endif /* } */ + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Configuration for 'tmpnam': +** By default, Lua uses tmpnam except when POSIX is available, where +** it uses mkstemp. +** =================================================================== +*/ +#if !defined(lua_tmpnam) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#include -#define l_gmtime(t,r) ((void)r, gmtime(t)) -#define l_localtime(t,r) ((void)r, localtime(t)) +#define LUA_TMPNAMBUFSIZE 32 +#if !defined(LUA_TMPNAMTEMPLATE) +#define LUA_TMPNAMTEMPLATE "/tmp/lua_XXXXXX" #endif +#define lua_tmpnam(b,e) { \ + strcpy(b, LUA_TMPNAMTEMPLATE); \ + e = mkstemp(b); \ + if (e != -1) close(e); \ + e = (e == -1); } + +#else /* }{ */ + +/* ISO C definitions */ +#define LUA_TMPNAMBUFSIZE L_tmpnam +#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } + +#endif /* } */ + +#endif /* } */ +/* }================================================================== */ + + static int os_execute (lua_State *L) { @@ -145,45 +206,67 @@ static void setboolfield (lua_State *L, const char *key, int value) { lua_setfield(L, -2, key); } + +/* +** Set all fields from structure 'tm' in the table on top of the stack +*/ +static void setallfields (lua_State *L, struct tm *stm) { + setfield(L, "sec", stm->tm_sec); + setfield(L, "min", stm->tm_min); + setfield(L, "hour", stm->tm_hour); + setfield(L, "day", stm->tm_mday); + setfield(L, "month", stm->tm_mon + 1); + setfield(L, "year", stm->tm_year + 1900); + setfield(L, "wday", stm->tm_wday + 1); + setfield(L, "yday", stm->tm_yday + 1); + setboolfield(L, "isdst", stm->tm_isdst); +} + + static int getboolfield (lua_State *L, const char *key) { int res; - lua_getfield(L, -1, key); - res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1); + res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1); lua_pop(L, 1); return res; } -static int getfield (lua_State *L, const char *key, int d) { - int res, isnum; - lua_getfield(L, -1, key); - res = (int)lua_tointegerx(L, -1, &isnum); - if (!isnum) { - if (d < 0) - return luaL_error(L, "field " LUA_QS " missing in date table", key); +/* maximum value for date fields (to avoid arithmetic overflows with 'int') */ +#if !defined(L_MAXDATEFIELD) +#define L_MAXDATEFIELD (INT_MAX / 2) +#endif + +static int getfield (lua_State *L, const char *key, int d, int delta) { + int isnum; + int t = lua_getfield(L, -1, key); /* get field and its type */ + lua_Integer res = lua_tointegerx(L, -1, &isnum); + if (!isnum) { /* field is not an integer? */ + if (t != LUA_TNIL) /* some other value? */ + return luaL_error(L, "field '%s' is not an integer", key); + else if (d < 0) /* absent field; no default? */ + return luaL_error(L, "field '%s' missing in date table", key); res = d; } + else { + if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD)) + return luaL_error(L, "field '%s' is out-of-bound", key); + res -= delta; + } lua_pop(L, 1); - return res; + return (int)res; } static const char *checkoption (lua_State *L, const char *conv, char *buff) { - static const char *const options[] = LUA_STRFTIMEOPTIONS; - unsigned int i; - for (i = 0; i < sizeof(options)/sizeof(options[0]); i += 2) { - if (*conv != '\0' && strchr(options[i], *conv) != NULL) { - buff[1] = *conv; - if (*options[i + 1] == '\0') { /* one-char conversion specifier? */ - buff[2] = '\0'; /* end buffer */ - return conv + 1; - } - else if (*(conv + 1) != '\0' && - strchr(options[i + 1], *(conv + 1)) != NULL) { - buff[2] = *(conv + 1); /* valid two-char conversion specifier */ - buff[3] = '\0'; /* end buffer */ - return conv + 2; - } + const char *option; + int oplen = 1; + for (option = LUA_STRFTIMEOPTIONS; *option != '\0'; option += oplen) { + if (*option == '|') /* next block? */ + oplen++; /* next length */ + else if (memcmp(conv, option, oplen) == 0) { /* match? */ + memcpy(buff, conv, oplen); /* copy valid option to buffer */ + buff[oplen] = '\0'; + return conv + oplen; /* return next item */ } } luaL_argerror(L, 1, @@ -192,44 +275,40 @@ static const char *checkoption (lua_State *L, const char *conv, char *buff) { } +/* maximum size for an individual 'strftime' item */ +#define SIZETIMEFMT 250 + + static int os_date (lua_State *L) { const char *s = luaL_optstring(L, 1, "%c"); - time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); + time_t t = luaL_opt(L, l_checktime, 2, time(NULL)); struct tm tmr, *stm; if (*s == '!') { /* UTC? */ stm = l_gmtime(&t, &tmr); - s++; /* skip `!' */ + s++; /* skip '!' */ } else stm = l_localtime(&t, &tmr); if (stm == NULL) /* invalid date? */ - lua_pushnil(L); - else if (strcmp(s, "*t") == 0) { + luaL_error(L, "time result cannot be represented in this installation"); + if (strcmp(s, "*t") == 0) { lua_createtable(L, 0, 9); /* 9 = number of fields */ - setfield(L, "sec", stm->tm_sec); - setfield(L, "min", stm->tm_min); - setfield(L, "hour", stm->tm_hour); - setfield(L, "day", stm->tm_mday); - setfield(L, "month", stm->tm_mon+1); - setfield(L, "year", stm->tm_year+1900); - setfield(L, "wday", stm->tm_wday+1); - setfield(L, "yday", stm->tm_yday+1); - setboolfield(L, "isdst", stm->tm_isdst); + setallfields(L, stm); } else { - char cc[4]; + char cc[4]; /* buffer for individual conversion specifiers */ luaL_Buffer b; cc[0] = '%'; luaL_buffinit(L, &b); while (*s) { - if (*s != '%') /* no conversion specifier? */ + if (*s != '%') /* not a conversion specifier? */ luaL_addchar(&b, *s++); else { size_t reslen; - char buff[200]; /* should be big enough for any conversion result */ - s = checkoption(L, s + 1, cc); - reslen = strftime(buff, sizeof(buff), cc, stm); - luaL_addlstring(&b, buff, reslen); + char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); + s = checkoption(L, s + 1, cc + 1); /* copy specifier to 'cc' */ + reslen = strftime(buff, SIZETIMEFMT, cc, stm); + luaL_addsize(&b, reslen); } } luaL_pushresult(&b); @@ -246,26 +325,27 @@ static int os_time (lua_State *L) { struct tm ts; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); /* make sure table is at the top */ - ts.tm_sec = getfield(L, "sec", 0); - ts.tm_min = getfield(L, "min", 0); - ts.tm_hour = getfield(L, "hour", 12); - ts.tm_mday = getfield(L, "day", -1); - ts.tm_mon = getfield(L, "month", -1) - 1; - ts.tm_year = getfield(L, "year", -1) - 1900; + ts.tm_sec = getfield(L, "sec", 0, 0); + ts.tm_min = getfield(L, "min", 0, 0); + ts.tm_hour = getfield(L, "hour", 12, 0); + ts.tm_mday = getfield(L, "day", -1, 0); + ts.tm_mon = getfield(L, "month", -1, 1); + ts.tm_year = getfield(L, "year", -1, 1900); ts.tm_isdst = getboolfield(L, "isdst"); t = mktime(&ts); + setallfields(L, &ts); /* update fields with normalized values */ } - if (t == (time_t)(-1)) - lua_pushnil(L); - else - lua_pushnumber(L, (lua_Number)t); + if (t != (time_t)(l_timet)t || t == (time_t)(-1)) + luaL_error(L, "time result cannot be represented in this installation"); + l_pushtime(L, t); return 1; } static int os_difftime (lua_State *L) { - lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), - (time_t)(luaL_optnumber(L, 2, 0)))); + time_t t1 = l_checktime(L, 1); + time_t t2 = l_checktime(L, 2); + lua_pushnumber(L, (lua_Number)difftime(t1, t2)); return 1; } @@ -289,7 +369,7 @@ static int os_exit (lua_State *L) { if (lua_isboolean(L, 1)) status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE); else - status = luaL_optint(L, 1, EXIT_SUCCESS); + status = (int)luaL_optinteger(L, 1, EXIT_SUCCESS); if (lua_toboolean(L, 2)) lua_close(L); if (L) exit(status); /* 'if' to avoid warnings for unreachable 'return' */ diff --git a/depends/lua/src/lparser.c b/depends/lua/src/lparser.c index 9e1a9ca2c..22530a57b 100644 --- a/depends/lua/src/lparser.c +++ b/depends/lua/src/lparser.c @@ -1,15 +1,17 @@ /* -** $Id: lparser.c,v 2.130.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lparser.c,v 2.153 2016/05/13 19:10:16 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ - -#include - #define lparser_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "lcode.h" @@ -35,17 +37,21 @@ #define hasmultret(k) ((k) == VCALL || (k) == VVARARG) +/* because all strings are unified by the scanner, the parser + can use pointer equality for string equality */ +#define eqstr(a,b) ((a) == (b)) + /* ** nodes for block list (list of active blocks) */ typedef struct BlockCnt { struct BlockCnt *previous; /* chain */ - short firstlabel; /* index of first label in this block */ - short firstgoto; /* index of first pending goto in this block */ + int firstlabel; /* index of first label in this block */ + int firstgoto; /* index of first pending goto in this block */ lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ - lu_byte isloop; /* true if `block' is a loop */ + lu_byte isloop; /* true if 'block' is a loop */ } BlockCnt; @@ -57,19 +63,9 @@ static void statement (LexState *ls); static void expr (LexState *ls, expdesc *v); -static void anchor_token (LexState *ls) { - /* last token from outer function must be EOS */ - lua_assert(ls->fs != NULL || ls->t.token == TK_EOS); - if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { - TString *ts = ls->t.seminfo.ts; - luaX_newstring(ls, getstr(ts), ts->tsv.len); - } -} - - /* semantic error */ static l_noret semerror (LexState *ls, const char *msg) { - ls->t.token = 0; /* remove 'near to' from final message */ + ls->t.token = 0; /* remove "near " from final message */ luaX_syntaxerror(ls, msg); } @@ -168,7 +164,8 @@ static int registerlocalvar (LexState *ls, TString *varname) { int oldsize = f->sizelocvars; luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, LocVar, SHRT_MAX, "local variables"); - while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; f->locvars[fs->nlocvars].varname = varname; luaC_objbarrier(ls->L, f, varname); return fs->nlocvars++; @@ -222,7 +219,7 @@ static int searchupvalue (FuncState *fs, TString *name) { int i; Upvaldesc *up = fs->f->upvalues; for (i = 0; i < fs->nups; i++) { - if (luaS_eqstr(up[i].name, name)) return i; + if (eqstr(up[i].name, name)) return i; } return -1; /* not found */ } @@ -234,7 +231,8 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, Upvaldesc, MAXUPVAL, "upvalues"); - while (oldsize < f->sizeupvalues) f->upvalues[oldsize++].name = NULL; + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; f->upvalues[fs->nups].instack = (v->k == VLOCAL); f->upvalues[fs->nups].idx = cast_byte(v->u.info); f->upvalues[fs->nups].name = name; @@ -246,7 +244,7 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { static int searchvar (FuncState *fs, TString *n) { int i; for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { - if (luaS_eqstr(n, getlocvar(fs, i)->varname)) + if (eqstr(n, getlocvar(fs, i)->varname)) return i; } return -1; /* not found */ @@ -259,7 +257,8 @@ static int searchvar (FuncState *fs, TString *n) { */ static void markupval (FuncState *fs, int level) { BlockCnt *bl = fs->bl; - while (bl->nactvar > level) bl = bl->previous; + while (bl->nactvar > level) + bl = bl->previous; bl->upval = 1; } @@ -268,27 +267,26 @@ static void markupval (FuncState *fs, int level) { Find variable with given name 'n'. If it is an upvalue, add this upvalue into all intermediate functions. */ -static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { +static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) /* no more levels? */ - return VVOID; /* default is global */ + init_exp(var, VVOID, 0); /* default is global */ else { int v = searchvar(fs, n); /* look up locals at current level */ if (v >= 0) { /* found? */ init_exp(var, VLOCAL, v); /* variable is local */ if (!base) markupval(fs, v); /* local will be used as an upval */ - return VLOCAL; } else { /* not found as local at current level; try upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */ if (idx < 0) { /* not found? */ - if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */ - return VVOID; /* not found; is a global */ + singlevaraux(fs->prev, n, var, 0); /* try upper levels */ + if (var->k == VVOID) /* not found? */ + return; /* it is a global */ /* else was LOCAL or UPVAL */ idx = newupvalue(fs, n, var); /* will be a new upvalue */ } - init_exp(var, VUPVAL, idx); - return VUPVAL; + init_exp(var, VUPVAL, idx); /* new or old upvalue */ } } } @@ -297,10 +295,11 @@ static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; - if (singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ + singlevaraux(fs, varname, var, 1); + if (var->k == VVOID) { /* global name? */ expdesc key; singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ - lua_assert(var->k == VLOCAL || var->k == VUPVAL); + lua_assert(var->k != VVOID); /* this one must exist */ codestring(ls, &key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ } @@ -342,11 +341,11 @@ static void closegoto (LexState *ls, int g, Labeldesc *label) { FuncState *fs = ls->fs; Labellist *gl = &ls->dyd->gt; Labeldesc *gt = &gl->arr[g]; - lua_assert(luaS_eqstr(gt->name, label->name)); + lua_assert(eqstr(gt->name, label->name)); if (gt->nactvar < label->nactvar) { TString *vname = getlocvar(fs, gt->nactvar)->varname; const char *msg = luaO_pushfstring(ls->L, - " at line %d jumps into the scope of local " LUA_QS, + " at line %d jumps into the scope of local '%s'", getstr(gt->name), gt->line, getstr(vname)); semerror(ls, msg); } @@ -369,7 +368,7 @@ static int findlabel (LexState *ls, int g) { /* check labels in current block for a match */ for (i = bl->firstlabel; i < dyd->label.n; i++) { Labeldesc *lb = &dyd->label.arr[i]; - if (luaS_eqstr(lb->name, gt->name)) { /* correct label? */ + if (eqstr(lb->name, gt->name)) { /* correct label? */ if (gt->nactvar > lb->nactvar && (bl->upval || dyd->label.n > bl->firstlabel)) luaK_patchclose(ls->fs, gt->pc, lb->nactvar); @@ -390,7 +389,7 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, l->arr[n].line = line; l->arr[n].nactvar = ls->fs->nactvar; l->arr[n].pc = pc; - l->n++; + l->n = n + 1; return n; } @@ -403,7 +402,7 @@ static void findgotos (LexState *ls, Labeldesc *lb) { Labellist *gl = &ls->dyd->gt; int i = ls->fs->bl->firstgoto; while (i < gl->n) { - if (luaS_eqstr(gl->arr[i].name, lb->name)) + if (eqstr(gl->arr[i].name, lb->name)) closegoto(ls, i, lb); else i++; @@ -412,7 +411,7 @@ static void findgotos (LexState *ls, Labeldesc *lb) { /* -** "export" pending gotos to outer level, to check them against +** export pending gotos to outer level, to check them against ** outer labels; if the block being exited has upvalues, and ** the goto exits the scope of any variable (which can be the ** upvalue), close those variables being exited. @@ -448,7 +447,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { /* -** create a label named "break" to resolve break statements +** create a label named 'break' to resolve break statements */ static void breaklabel (LexState *ls) { TString *n = luaS_new(ls->L, "break"); @@ -463,7 +462,7 @@ static void breaklabel (LexState *ls) { static l_noret undefgoto (LexState *ls, Labeldesc *gt) { const char *msg = isreserved(gt->name) ? "<%s> at line %d not inside a loop" - : "no visible label " LUA_QS " for at line %d"; + : "no visible label '%s' for at line %d"; msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); semerror(ls, msg); } @@ -503,7 +502,8 @@ static Proto *addprototype (LexState *ls) { if (fs->np >= f->sizep) { int oldsize = f->sizep; luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); - while (oldsize < f->sizep) f->p[oldsize++] = NULL; + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; } f->p[fs->np++] = clp = luaF_newproto(L); luaC_objbarrier(L, f, clp); @@ -525,7 +525,6 @@ static void codeclosure (LexState *ls, expdesc *v) { static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { - lua_State *L = ls->L; Proto *f; fs->prev = ls->fs; /* linked list of funcstates */ fs->ls = ls; @@ -544,10 +543,6 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { f = fs->f; f->source = ls->source; f->maxstacksize = 2; /* registers 0/1 are always valid */ - fs->h = luaH_new(L); - /* anchor table of constants (to avoid being collected) */ - sethvalue2s(L, L->top, fs->h); - incr_top(L); enterblock(fs, bl, 0); } @@ -572,9 +567,6 @@ static void close_func (LexState *ls) { f->sizeupvalues = fs->nups; lua_assert(fs->bl == NULL); ls->fs = fs->prev; - /* last token read was anchored in defunct function; must re-anchor it */ - anchor_token(ls); - L->top--; /* pop table of constants */ luaC_checkGC(L); } @@ -588,7 +580,7 @@ static void close_func (LexState *ls) { /* ** check whether current token is in the follow set of a block. ** 'until' closes syntactical blocks, but do not close scope, -** so it handled in separate. +** so it is handled in separate. */ static int block_follow (LexState *ls, int withuntil) { switch (ls->t.token) { @@ -602,7 +594,7 @@ static int block_follow (LexState *ls, int withuntil) { static void statlist (LexState *ls) { - /* statlist -> { stat [`;'] } */ + /* statlist -> { stat [';'] } */ while (!block_follow(ls, 1)) { if (ls->t.token == TK_RETURN) { statement(ls); @@ -643,14 +635,14 @@ static void yindex (LexState *ls, expdesc *v) { struct ConsControl { expdesc v; /* last list item read */ expdesc *t; /* table descriptor */ - int nh; /* total number of `record' elements */ + int nh; /* total number of 'record' elements */ int na; /* total number of array elements */ int tostore; /* number of array elements pending to be stored */ }; static void recfield (LexState *ls, struct ConsControl *cc) { - /* recfield -> (NAME | `['exp1`]') = exp1 */ + /* recfield -> (NAME | '['exp1']') = exp1 */ FuncState *fs = ls->fs; int reg = ls->fs->freereg; expdesc key, val; @@ -757,12 +749,12 @@ static void constructor (LexState *ls, expdesc *t) { static void parlist (LexState *ls) { - /* parlist -> [ param { `,' param } ] */ + /* parlist -> [ param { ',' param } ] */ FuncState *fs = ls->fs; Proto *f = fs->f; int nparams = 0; f->is_vararg = 0; - if (ls->t.token != ')') { /* is `parlist' not empty? */ + if (ls->t.token != ')') { /* is 'parlist' not empty? */ do { switch (ls->t.token) { case TK_NAME: { /* param -> NAME */ @@ -770,12 +762,12 @@ static void parlist (LexState *ls) { nparams++; break; } - case TK_DOTS: { /* param -> `...' */ + case TK_DOTS: { /* param -> '...' */ luaX_next(ls); - f->is_vararg = 1; + f->is_vararg = 2; /* declared vararg */ break; } - default: luaX_syntaxerror(ls, " or " LUA_QL("...") " expected"); + default: luaX_syntaxerror(ls, " or '...' expected"); } } while (!f->is_vararg && testnext(ls, ',')); } @@ -786,7 +778,7 @@ static void parlist (LexState *ls) { static void body (LexState *ls, expdesc *e, int ismethod, int line) { - /* body -> `(' parlist `)' block END */ + /* body -> '(' parlist ')' block END */ FuncState new_fs; BlockCnt bl; new_fs.f = addprototype(ls); @@ -808,7 +800,7 @@ static void body (LexState *ls, expdesc *e, int ismethod, int line) { static int explist (LexState *ls, expdesc *v) { - /* explist -> expr { `,' expr } */ + /* explist -> expr { ',' expr } */ int n = 1; /* at least one expression */ expr(ls, v); while (testnext(ls, ',')) { @@ -825,7 +817,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { expdesc args; int base, nparams; switch (ls->t.token) { - case '(': { /* funcargs -> `(' [ explist ] `)' */ + case '(': { /* funcargs -> '(' [ explist ] ')' */ luaX_next(ls); if (ls->t.token == ')') /* arg list is empty? */ args.k = VVOID; @@ -842,7 +834,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { } case TK_STRING: { /* funcargs -> STRING */ codestring(ls, &args, ls->t.seminfo.ts); - luaX_next(ls); /* must use `seminfo' before `next' */ + luaX_next(ls); /* must use 'seminfo' before 'next' */ break; } default: { @@ -908,14 +900,14 @@ static void suffixedexp (LexState *ls, expdesc *v) { fieldsel(ls, v); break; } - case '[': { /* `[' exp1 `]' */ + case '[': { /* '[' exp1 ']' */ expdesc key; luaK_exp2anyregup(fs, v); yindex(ls, &key); luaK_indexed(fs, v, &key); break; } - case ':': { /* `:' NAME funcargs */ + case ':': { /* ':' NAME funcargs */ expdesc key; luaX_next(ls); checkname(ls, &key); @@ -935,14 +927,19 @@ static void suffixedexp (LexState *ls, expdesc *v) { static void simpleexp (LexState *ls, expdesc *v) { - /* simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... | + /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | constructor | FUNCTION body | suffixedexp */ switch (ls->t.token) { - case TK_NUMBER: { - init_exp(v, VKNUM, 0); + case TK_FLT: { + init_exp(v, VKFLT, 0); v->u.nval = ls->t.seminfo.r; break; } + case TK_INT: { + init_exp(v, VKINT, 0); + v->u.ival = ls->t.seminfo.i; + break; + } case TK_STRING: { codestring(ls, v, ls->t.seminfo.ts); break; @@ -962,7 +959,8 @@ static void simpleexp (LexState *ls, expdesc *v) { case TK_DOTS: { /* vararg */ FuncState *fs = ls->fs; check_condition(ls, fs->f->is_vararg, - "cannot use " LUA_QL("...") " outside a vararg function"); + "cannot use '...' outside a vararg function"); + fs->f->is_vararg = 1; /* function actually uses vararg */ init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); break; } @@ -988,6 +986,7 @@ static UnOpr getunopr (int op) { switch (op) { case TK_NOT: return OPR_NOT; case '-': return OPR_MINUS; + case '~': return OPR_BNOT; case '#': return OPR_LEN; default: return OPR_NOUNOPR; } @@ -999,9 +998,15 @@ static BinOpr getbinopr (int op) { case '+': return OPR_ADD; case '-': return OPR_SUB; case '*': return OPR_MUL; - case '/': return OPR_DIV; case '%': return OPR_MOD; case '^': return OPR_POW; + case '/': return OPR_DIV; + case TK_IDIV: return OPR_IDIV; + case '&': return OPR_BAND; + case '|': return OPR_BOR; + case '~': return OPR_BXOR; + case TK_SHL: return OPR_SHL; + case TK_SHR: return OPR_SHR; case TK_CONCAT: return OPR_CONCAT; case TK_NE: return OPR_NE; case TK_EQ: return OPR_EQ; @@ -1020,19 +1025,24 @@ static const struct { lu_byte left; /* left priority for each binary operator */ lu_byte right; /* right priority */ } priority[] = { /* ORDER OPR */ - {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `*' `/' `%' */ - {10, 9}, {5, 4}, /* ^, .. (right associative) */ - {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ - {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ - {2, 2}, {1, 1} /* and, or */ + {10, 10}, {10, 10}, /* '+' '-' */ + {11, 11}, {11, 11}, /* '*' '%' */ + {14, 13}, /* '^' (right associative) */ + {11, 11}, {11, 11}, /* '/' '//' */ + {6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */ + {7, 7}, {7, 7}, /* '<<' '>>' */ + {9, 8}, /* '..' (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ + {2, 2}, {1, 1} /* and, or */ }; -#define UNARY_PRIORITY 8 /* priority for unary operators */ +#define UNARY_PRIORITY 12 /* priority for unary operators */ /* ** subexpr -> (simpleexp | unop subexpr) { binop subexpr } -** where `binop' is any binary operator with a priority higher than `limit' +** where 'binop' is any binary operator with a priority higher than 'limit' */ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { BinOpr op; @@ -1046,7 +1056,7 @@ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { luaK_prefix(ls->fs, uop, v, line); } else simpleexp(ls, v); - /* expand while operators have priorities higher than `limit' */ + /* expand while operators have priorities higher than 'limit' */ op = getbinopr(ls->t.token); while (op != OPR_NOBINOPR && priority[op].left > limit) { expdesc v2; @@ -1146,7 +1156,7 @@ static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { "C levels"); assignment(ls, &nv, nvars+1); } - else { /* assignment -> `=' explist */ + else { /* assignment -> '=' explist */ int nexps; checknext(ls, '='); nexps = explist(ls, &e); @@ -1170,7 +1180,7 @@ static int cond (LexState *ls) { /* cond -> exp */ expdesc v; expr(ls, &v); /* read condition */ - if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */ + if (v.k == VNIL) v.k = VFALSE; /* 'falses' are all equal here */ luaK_goiftrue(ls->fs, &v); return v.f; } @@ -1195,9 +1205,9 @@ static void gotostat (LexState *ls, int pc) { static void checkrepeated (FuncState *fs, Labellist *ll, TString *label) { int i; for (i = fs->bl->firstlabel; i < ll->n; i++) { - if (luaS_eqstr(label, ll->arr[i].name)) { + if (eqstr(label, ll->arr[i].name)) { const char *msg = luaO_pushfstring(fs->ls->L, - "label " LUA_QS " already defined on line %d", + "label '%s' already defined on line %d", getstr(label), ll->arr[i].line); semerror(fs->ls, msg); } @@ -1220,7 +1230,7 @@ static void labelstat (LexState *ls, TString *label, int line) { checkrepeated(fs, ll, label); /* check for repeated labels */ checknext(ls, TK_DBCOLON); /* skip double colon */ /* create new entry for this label */ - l = newlabelentry(ls, ll, label, line, fs->pc); + l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs)); skipnoopstat(ls); /* skip other no-op statements */ if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ /* assume that locals are already out of scope */ @@ -1321,7 +1331,7 @@ static void fornum (LexState *ls, TString *varname, int line) { if (testnext(ls, ',')) exp1(ls); /* optional step */ else { /* default step = 1 */ - luaK_codek(fs, fs->freereg, luaK_numberK(fs, 1)); + luaK_codek(fs, fs->freereg, luaK_intK(fs, 1)); luaK_reserveregs(fs, 1); } forbody(ls, base, line, 1, 1); @@ -1359,15 +1369,15 @@ static void forstat (LexState *ls, int line) { TString *varname; BlockCnt bl; enterblock(fs, &bl, 1); /* scope for loop and control variables */ - luaX_next(ls); /* skip `for' */ + luaX_next(ls); /* skip 'for' */ varname = str_checkname(ls); /* first variable name */ switch (ls->t.token) { case '=': fornum(ls, varname, line); break; case ',': case TK_IN: forlist(ls, varname); break; - default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected"); + default: luaX_syntaxerror(ls, "'=' or 'in' expected"); } check_match(ls, TK_END, TK_FOR, line); - leaveblock(fs); /* loop scope (`break' jumps to this point) */ + leaveblock(fs); /* loop scope ('break' jumps to this point) */ } @@ -1397,7 +1407,7 @@ static void test_then_block (LexState *ls, int *escapelist) { enterblock(fs, &bl, 0); jf = v.f; } - statlist(ls); /* `then' part */ + statlist(ls); /* 'then' part */ leaveblock(fs); if (ls->t.token == TK_ELSE || ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ @@ -1414,7 +1424,7 @@ static void ifstat (LexState *ls, int line) { while (ls->t.token == TK_ELSEIF) test_then_block(ls, &escapelist); /* ELSEIF cond THEN block */ if (testnext(ls, TK_ELSE)) - block(ls); /* `else' part */ + block(ls); /* 'else' part */ check_match(ls, TK_END, TK_IF, line); luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ } @@ -1432,7 +1442,7 @@ static void localfunc (LexState *ls) { static void localstat (LexState *ls) { - /* stat -> LOCAL NAME {`,' NAME} [`=' explist] */ + /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ int nvars = 0; int nexps; expdesc e; @@ -1452,7 +1462,7 @@ static void localstat (LexState *ls) { static int funcname (LexState *ls, expdesc *v) { - /* funcname -> NAME {fieldsel} [`:' NAME] */ + /* funcname -> NAME {fieldsel} [':' NAME] */ int ismethod = 0; singlevar(ls, v); while (ls->t.token == '.') @@ -1473,7 +1483,7 @@ static void funcstat (LexState *ls, int line) { ismethod = funcname(ls, &v); body(ls, &b, ismethod, line); luaK_storevar(ls->fs, &v, &b); - luaK_fixline(ls->fs, line); /* definition `happens' in the first line */ + luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ } @@ -1488,7 +1498,7 @@ static void exprstat (LexState *ls) { } else { /* stat -> func */ check_condition(ls, v.v.k == VCALL, "syntax error"); - SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + SETARG_C(getinstruction(fs, &v.v), 1); /* call statement uses no results */ } } @@ -1505,8 +1515,8 @@ static void retstat (LexState *ls) { if (hasmultret(e.k)) { luaK_setmultret(fs, &e); if (e.k == VCALL && nret == 1) { /* tail call? */ - SET_OPCODE(getcode(fs,&e), OP_TAILCALL); - lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); } first = fs->nactvar; nret = LUA_MULTRET; /* return all values */ @@ -1515,8 +1525,8 @@ static void retstat (LexState *ls) { if (nret == 1) /* only one single value? */ first = luaK_exp2anyreg(fs, &e); else { - luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */ - first = fs->nactvar; /* return all `active' values */ + luaK_exp2nextreg(fs, &e); /* values must go to the stack */ + first = fs->nactvar; /* return all active values */ lua_assert(nret == fs->freereg - first); } } @@ -1605,7 +1615,7 @@ static void mainfunc (LexState *ls, FuncState *fs) { BlockCnt bl; expdesc v; open_func(ls, fs, &bl); - fs->f->is_vararg = 1; /* main function is always vararg */ + fs->f->is_vararg = 2; /* main function is always declared vararg */ init_exp(&v, VLOCAL, 0); /* create and... */ newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ luaX_next(ls); /* read first token */ @@ -1615,16 +1625,19 @@ static void mainfunc (LexState *ls, FuncState *fs) { } -Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, - Dyndata *dyd, const char *name, int firstchar) { +LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar) { LexState lexstate; FuncState funcstate; - Closure *cl = luaF_newLclosure(L, 1); /* create main closure */ - /* anchor closure (to avoid being collected) */ - setclLvalue(L, L->top, cl); - incr_top(L); - funcstate.f = cl->l.p = luaF_newproto(L); + LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */ + setclLvalue(L, L->top, cl); /* anchor it (to avoid being collected) */ + luaD_inctop(L); + lexstate.h = luaH_new(L); /* create table for scanner */ + sethvalue(L, L->top, lexstate.h); /* anchor it */ + luaD_inctop(L); + funcstate.f = cl->p = luaF_newproto(L); funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ + lua_assert(iswhite(funcstate.f)); /* do not need barrier here */ lexstate.buff = buff; lexstate.dyd = dyd; dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; @@ -1633,6 +1646,7 @@ Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); /* all scopes should be correctly finished */ lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); - return cl; /* it's on the stack too */ + L->top--; /* remove scanner's table */ + return cl; /* closure is on the stack, too */ } diff --git a/depends/lua/src/lstate.c b/depends/lua/src/lstate.c index c7f2672be..9194ac341 100644 --- a/depends/lua/src/lstate.c +++ b/depends/lua/src/lstate.c @@ -1,16 +1,18 @@ /* -** $Id: lstate.c,v 2.99.1.2 2013/11/08 17:45:31 roberto Exp $ +** $Id: lstate.c,v 2.133 2015/11/13 12:16:51 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ +#define lstate_c +#define LUA_CORE + +#include "lprefix.h" + #include #include -#define lstate_c -#define LUA_CORE - #include "lua.h" #include "lapi.h" @@ -30,18 +32,11 @@ #define LUAI_GCPAUSE 200 /* 200% */ #endif -#if !defined(LUAI_GCMAJOR) -#define LUAI_GCMAJOR 200 /* 200% */ -#endif - #if !defined(LUAI_GCMUL) #define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ #endif -#define MEMERRMSG "not enough memory" - - /* ** a macro to help the creation of a unique random seed when a state is ** created; the seed is used to randomize hashes. @@ -57,9 +52,7 @@ ** thread state + extra space */ typedef struct LX { -#if defined(LUAI_EXTRASPACE) - char buff[LUAI_EXTRASPACE]; -#endif + lu_byte extra_[LUA_EXTRASPACE]; lua_State l; } LX; @@ -78,13 +71,12 @@ typedef struct LG { /* -** Compute an initial seed as random as possible. In ANSI, rely on -** Address Space Layout Randomization (if present) to increase -** randomness.. +** Compute an initial seed as random as possible. Rely on Address Space +** Layout Randomization (if present) to increase randomness.. */ #define addbuff(b,p,e) \ { size_t t = cast(size_t, e); \ - memcpy(buff + p, &t, sizeof(t)); p += sizeof(t); } + memcpy(b + p, &t, sizeof(t)); p += sizeof(t); } static unsigned int makeseed (lua_State *L) { char buff[4 * sizeof(size_t)]; @@ -101,10 +93,14 @@ static unsigned int makeseed (lua_State *L) { /* ** set GCdebt to a new value keeping the value (totalbytes + GCdebt) -** invariant +** invariant (and avoiding underflows in 'totalbytes') */ void luaE_setdebt (global_State *g, l_mem debt) { - g->totalbytes -= (debt - g->GCdebt); + l_mem tb = gettotalbytes(g); + lua_assert(tb > 0); + if (debt < tb - MAX_LMEM) + debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ + g->totalbytes = tb - debt; g->GCdebt = debt; } @@ -115,10 +111,14 @@ CallInfo *luaE_extendCI (lua_State *L) { L->ci->next = ci; ci->previous = L->ci; ci->next = NULL; + L->nci++; return ci; } +/* +** free all CallInfo structures not in use by a thread +*/ void luaE_freeCI (lua_State *L) { CallInfo *ci = L->ci; CallInfo *next = ci->next; @@ -126,6 +126,24 @@ void luaE_freeCI (lua_State *L) { while ((ci = next) != NULL) { next = ci->next; luaM_free(L, ci); + L->nci--; + } +} + + +/* +** free half of the CallInfo structures not in use by a thread +*/ +void luaE_shrinkCI (lua_State *L) { + CallInfo *ci = L->ci; + CallInfo *next2; /* next's next */ + /* while there are two nexts */ + while (ci->next != NULL && (next2 = ci->next->next) != NULL) { + luaM_free(L, ci->next); /* free next */ + L->nci--; + ci->next = next2; /* remove 'next' from the list */ + next2->previous = ci; + ci = next2; /* keep next's next */ } } @@ -155,6 +173,7 @@ static void freestack (lua_State *L) { return; /* stack not completely built yet */ L->ci = &L->base_ci; /* free the entire 'ci' list */ luaE_freeCI(L); + lua_assert(L->nci == 0); luaM_freearray(L, L->stack, L->stacksize); /* free stack array */ } @@ -163,34 +182,32 @@ static void freestack (lua_State *L) { ** Create registry table and its predefined values */ static void init_registry (lua_State *L, global_State *g) { - TValue mt; + TValue temp; /* create registry */ Table *registry = luaH_new(L); sethvalue(L, &g->l_registry, registry); luaH_resize(L, registry, LUA_RIDX_LAST, 0); /* registry[LUA_RIDX_MAINTHREAD] = L */ - setthvalue(L, &mt, L); - luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &mt); + setthvalue(L, &temp, L); /* temp = L */ + luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &temp); /* registry[LUA_RIDX_GLOBALS] = table of globals */ - sethvalue(L, &mt, luaH_new(L)); - luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt); + sethvalue(L, &temp, luaH_new(L)); /* temp = new table (global table) */ + luaH_setint(L, registry, LUA_RIDX_GLOBALS, &temp); } /* -** open parts of the state that may cause memory-allocation errors +** open parts of the state that may cause memory-allocation errors. +** ('g->version' != NULL flags that the state was completely build) */ static void f_luaopen (lua_State *L, void *ud) { global_State *g = G(L); UNUSED(ud); stack_init(L, L); /* init stack */ init_registry(L, g); - luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + luaS_init(L); luaT_init(L); luaX_init(L); - /* pre-create memory-error message */ - g->memerrmsg = luaS_newliteral(L, MEMERRMSG); - luaS_fix(g->memerrmsg); /* it should never be collected */ g->gcrunning = 1; /* allow gc */ g->version = lua_version(NULL); luai_userstateopen(L); @@ -198,14 +215,16 @@ static void f_luaopen (lua_State *L, void *ud) { /* -** preinitialize a state with consistent values without allocating +** preinitialize a thread with consistent values without allocating ** any memory (to avoid errors) */ -static void preinit_state (lua_State *L, global_State *g) { +static void preinit_thread (lua_State *L, global_State *g) { G(L) = g; L->stack = NULL; L->ci = NULL; + L->nci = 0; L->stacksize = 0; + L->twups = L; /* thread has no upvalues */ L->errorJmp = NULL; L->nCcalls = 0; L->hook = NULL; @@ -227,7 +246,6 @@ static void close_state (lua_State *L) { if (g->version) /* closing a fully built state? */ luai_userstateclose(L); luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); - luaZ_freebuffer(L, &g->buff); freestack(L); lua_assert(gettotalbytes(g) == sizeof(LG)); (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ @@ -235,17 +253,28 @@ static void close_state (lua_State *L) { LUA_API lua_State *lua_newthread (lua_State *L) { + global_State *g = G(L); lua_State *L1; lua_lock(L); luaC_checkGC(L); - L1 = &luaC_newobj(L, LUA_TTHREAD, sizeof(LX), NULL, offsetof(LX, l))->th; + /* create new thread */ + L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l; + L1->marked = luaC_white(g); + L1->tt = LUA_TTHREAD; + /* link it on list 'allgc' */ + L1->next = g->allgc; + g->allgc = obj2gco(L1); + /* anchor it on L stack */ setthvalue(L, L->top, L1); api_incr_top(L); - preinit_state(L1, G(L)); + preinit_thread(L1, g); L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; resethookcount(L1); + /* initialize L1 extra space */ + memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), + LUA_EXTRASPACE); luai_userstatethread(L, L1); stack_init(L1, L); /* init stack */ lua_unlock(L); @@ -273,36 +302,31 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { g = &l->g; L->next = NULL; L->tt = LUA_TTHREAD; - g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); + g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); - g->gckind = KGC_NORMAL; - preinit_state(L, g); + preinit_thread(L, g); g->frealloc = f; g->ud = ud; g->mainthread = L; g->seed = makeseed(L); - g->uvhead.u.l.prev = &g->uvhead; - g->uvhead.u.l.next = &g->uvhead; g->gcrunning = 0; /* no GC while building state */ g->GCestimate = 0; - g->strt.size = 0; - g->strt.nuse = 0; + g->strt.size = g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(&g->l_registry); - luaZ_initbuffer(L, &g->buff); g->panic = NULL; g->version = NULL; g->gcstate = GCSpause; - g->allgc = NULL; - g->finobj = NULL; - g->tobefnz = NULL; - g->sweepgc = g->sweepfin = NULL; + g->gckind = KGC_NORMAL; + g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL; + g->sweepgc = NULL; g->gray = g->grayagain = NULL; g->weak = g->ephemeron = g->allweak = NULL; + g->twups = NULL; g->totalbytes = sizeof(LG); g->GCdebt = 0; + g->gcfinnum = 0; g->gcpause = LUAI_GCPAUSE; - g->gcmajorinc = LUAI_GCMAJOR; g->gcstepmul = LUAI_GCMUL; for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { diff --git a/depends/lua/src/lstring.c b/depends/lua/src/lstring.c index af96c89c1..9351766fd 100644 --- a/depends/lua/src/lstring.c +++ b/depends/lua/src/lstring.c @@ -1,23 +1,30 @@ /* -** $Id: lstring.c,v 2.26.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstring.c,v 2.56 2015/11/23 11:32:51 roberto Exp $ ** String table (keeps all strings handled by Lua) ** See Copyright Notice in lua.h */ - -#include - #define lstring_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" +#include "ldebug.h" +#include "ldo.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" +#define MEMERRMSG "not enough memory" + + /* ** Lua will use at most ~(2^LUAI_HASHLIMIT) bytes from a string to ** compute its hash @@ -31,99 +38,126 @@ ** equality for long strings */ int luaS_eqlngstr (TString *a, TString *b) { - size_t len = a->tsv.len; - lua_assert(a->tsv.tt == LUA_TLNGSTR && b->tsv.tt == LUA_TLNGSTR); + size_t len = a->u.lnglen; + lua_assert(a->tt == LUA_TLNGSTR && b->tt == LUA_TLNGSTR); return (a == b) || /* same instance or... */ - ((len == b->tsv.len) && /* equal length and ... */ + ((len == b->u.lnglen) && /* equal length and ... */ (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ } -/* -** equality for strings -*/ -int luaS_eqstr (TString *a, TString *b) { - return (a->tsv.tt == b->tsv.tt) && - (a->tsv.tt == LUA_TSHRSTR ? eqshrstr(a, b) : luaS_eqlngstr(a, b)); -} - - unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { unsigned int h = seed ^ cast(unsigned int, l); - size_t l1; size_t step = (l >> LUAI_HASHLIMIT) + 1; - for (l1 = l; l1 >= step; l1 -= step) - h = h ^ ((h<<5) + (h>>2) + cast_byte(str[l1 - 1])); + for (; l >= step; l -= step) + h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); return h; } +unsigned int luaS_hashlongstr (TString *ts) { + lua_assert(ts->tt == LUA_TLNGSTR); + if (ts->extra == 0) { /* no hash? */ + ts->hash = luaS_hash(getstr(ts), ts->u.lnglen, ts->hash); + ts->extra = 1; /* now it has its hash */ + } + return ts->hash; +} + + /* ** resizes the string table */ void luaS_resize (lua_State *L, int newsize) { int i; stringtable *tb = &G(L)->strt; - /* cannot resize while GC is traversing strings */ - luaC_runtilstate(L, ~bitmask(GCSsweepstring)); - if (newsize > tb->size) { - luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *); - for (i = tb->size; i < newsize; i++) tb->hash[i] = NULL; + if (newsize > tb->size) { /* grow table if needed */ + luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); + for (i = tb->size; i < newsize; i++) + tb->hash[i] = NULL; } - /* rehash */ - for (i=0; isize; i++) { - GCObject *p = tb->hash[i]; + for (i = 0; i < tb->size; i++) { /* rehash */ + TString *p = tb->hash[i]; tb->hash[i] = NULL; while (p) { /* for each node in the list */ - GCObject *next = gch(p)->next; /* save next */ - unsigned int h = lmod(gco2ts(p)->hash, newsize); /* new position */ - gch(p)->next = tb->hash[h]; /* chain it */ + TString *hnext = p->u.hnext; /* save next */ + unsigned int h = lmod(p->hash, newsize); /* new position */ + p->u.hnext = tb->hash[h]; /* chain it */ tb->hash[h] = p; - resetoldbit(p); /* see MOVE OLD rule */ - p = next; + p = hnext; } } - if (newsize < tb->size) { - /* shrinking slice must be empty */ + if (newsize < tb->size) { /* shrink table if needed */ + /* vanishing slice should be empty */ lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL); - luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *); + luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); } tb->size = newsize; } +/* +** Clear API string cache. (Entries cannot be empty, so fill them with +** a non-collectable string.) +*/ +void luaS_clearcache (global_State *g) { + int i, j; + for (i = 0; i < STRCACHE_N; i++) + for (j = 0; j < STRCACHE_M; j++) { + if (iswhite(g->strcache[i][j])) /* will entry be collected? */ + g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */ + } +} + + +/* +** Initialize the string table and the string cache +*/ +void luaS_init (lua_State *L) { + global_State *g = G(L); + int i, j; + luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + /* pre-create memory-error message */ + g->memerrmsg = luaS_newliteral(L, MEMERRMSG); + luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */ + for (i = 0; i < STRCACHE_N; i++) /* fill cache with valid strings */ + for (j = 0; j < STRCACHE_M; j++) + g->strcache[i][j] = g->memerrmsg; +} + + + /* ** creates a new string object */ -static TString *createstrobj (lua_State *L, const char *str, size_t l, - int tag, unsigned int h, GCObject **list) { +static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { TString *ts; + GCObject *o; size_t totalsize; /* total size of TString object */ - totalsize = sizeof(TString) + ((l + 1) * sizeof(char)); - ts = &luaC_newobj(L, tag, totalsize, list, 0)->ts; - ts->tsv.len = l; - ts->tsv.hash = h; - ts->tsv.extra = 0; - memcpy(ts+1, str, l*sizeof(char)); - ((char *)(ts+1))[l] = '\0'; /* ending 0 */ + totalsize = sizelstring(l); + o = luaC_newobj(L, tag, totalsize); + ts = gco2ts(o); + ts->hash = h; + ts->extra = 0; + getstr(ts)[l] = '\0'; /* ending 0 */ return ts; } -/* -** creates a new short string, inserting it into string table -*/ -static TString *newshrstr (lua_State *L, const char *str, size_t l, - unsigned int h) { - GCObject **list; /* (pointer to) list where it will be inserted */ +TString *luaS_createlngstrobj (lua_State *L, size_t l) { + TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed); + ts->u.lnglen = l; + return ts; +} + + +void luaS_remove (lua_State *L, TString *ts) { stringtable *tb = &G(L)->strt; - TString *s; - if (tb->nuse >= cast(lu_int32, tb->size) && tb->size <= MAX_INT/2) - luaS_resize(L, tb->size*2); /* too crowded */ - list = &tb->hash[lmod(h, tb->size)]; - s = createstrobj(L, str, l, LUA_TSHRSTR, h, list); - tb->nuse++; - return s; + TString **p = &tb->hash[lmod(ts->hash, tb->size)]; + while (*p != ts) /* find previous element */ + p = &(*p)->u.hnext; + *p = (*p)->u.hnext; /* remove element from its list */ + tb->nuse--; } @@ -131,22 +165,31 @@ static TString *newshrstr (lua_State *L, const char *str, size_t l, ** checks whether short string exists and reuses it or creates a new one */ static TString *internshrstr (lua_State *L, const char *str, size_t l) { - GCObject *o; + TString *ts; global_State *g = G(L); unsigned int h = luaS_hash(str, l, g->seed); - for (o = g->strt.hash[lmod(h, g->strt.size)]; - o != NULL; - o = gch(o)->next) { - TString *ts = rawgco2ts(o); - if (h == ts->tsv.hash && - l == ts->tsv.len && + TString **list = &g->strt.hash[lmod(h, g->strt.size)]; + lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ + for (ts = *list; ts != NULL; ts = ts->u.hnext) { + if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { - if (isdead(G(L), o)) /* string is dead (but was not collected yet)? */ - changewhite(o); /* resurrect it */ + /* found! */ + if (isdead(g, ts)) /* dead (but not collected yet)? */ + changewhite(ts); /* resurrect it */ return ts; } } - return newshrstr(L, str, l, h); /* not found; create a new string */ + if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) { + luaS_resize(L, g->strt.size * 2); + list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */ + } + ts = createstrobj(L, l, LUA_TSHRSTR, h); + memcpy(getstr(ts), str, l * sizeof(char)); + ts->shrlen = cast_byte(l); + ts->u.hnext = *list; + *list = ts; + g->strt.nuse++; + return ts; } @@ -157,29 +200,49 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { if (l <= LUAI_MAXSHORTLEN) /* short string? */ return internshrstr(L, str, l); else { - if (l + 1 > (MAX_SIZET - sizeof(TString))/sizeof(char)) + TString *ts; + if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char)) luaM_toobig(L); - return createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed, NULL); + ts = luaS_createlngstrobj(L, l); + memcpy(getstr(ts), str, l * sizeof(char)); + return ts; } } /* -** new zero-terminated string +** Create or reuse a zero-terminated string, first checking in the +** cache (using the string address as a key). The cache can contain +** only zero-terminated strings, so it is safe to use 'strcmp' to +** check hits. */ TString *luaS_new (lua_State *L, const char *str) { - return luaS_newlstr(L, str, strlen(str)); + unsigned int i = point2uint(str) % STRCACHE_N; /* hash */ + int j; + TString **p = G(L)->strcache[i]; + for (j = 0; j < STRCACHE_M; j++) { + if (strcmp(str, getstr(p[j])) == 0) /* hit? */ + return p[j]; /* that is it */ + } + /* normal route */ + for (j = STRCACHE_M - 1; j > 0; j--) + p[j] = p[j - 1]; /* move out last element */ + /* new element is first in the list */ + p[0] = luaS_newlstr(L, str, strlen(str)); + return p[0]; } -Udata *luaS_newudata (lua_State *L, size_t s, Table *e) { +Udata *luaS_newudata (lua_State *L, size_t s) { Udata *u; - if (s > MAX_SIZET - sizeof(Udata)) + GCObject *o; + if (s > MAX_SIZE - sizeof(Udata)) luaM_toobig(L); - u = &luaC_newobj(L, LUA_TUSERDATA, sizeof(Udata) + s, NULL, 0)->u; - u->uv.len = s; - u->uv.metatable = NULL; - u->uv.env = e; + o = luaC_newobj(L, LUA_TUSERDATA, sizeludata(s)); + u = gco2u(o); + u->len = s; + u->metatable = NULL; + setuservalue(L, u, luaO_nilobject); return u; } diff --git a/depends/lua/src/lstrlib.c b/depends/lua/src/lstrlib.c index 9261fd220..a4a356a3f 100644 --- a/depends/lua/src/lstrlib.c +++ b/depends/lua/src/lstrlib.c @@ -1,19 +1,24 @@ /* -** $Id: lstrlib.c,v 1.178.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lstrlib.c,v 1.251 2016/05/20 14:13:21 roberto Exp $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ +#define lstrlib_c +#define LUA_LIB + +#include "lprefix.h" + #include +#include +#include +#include #include #include #include #include -#define lstrlib_c -#define LUA_LIB - #include "lua.h" #include "lauxlib.h" @@ -22,17 +27,29 @@ /* ** maximum number of captures that a pattern can do during -** pattern-matching. This limit is arbitrary. +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. */ #if !defined(LUA_MAXCAPTURES) #define LUA_MAXCAPTURES 32 #endif -/* macro to `unsign' a character */ +/* macro to 'unsign' a character */ #define uchar(c) ((unsigned char)(c)) +/* +** Some sizes are better limited to fit in 'int', but must also fit in +** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.) +*/ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +#define MAXSIZE \ + (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) + + + static int str_len (lua_State *L) { size_t l; @@ -43,22 +60,22 @@ static int str_len (lua_State *L) { /* translate a relative string position: negative means back from end */ -static size_t posrelat (ptrdiff_t pos, size_t len) { - if (pos >= 0) return (size_t)pos; +static lua_Integer posrelat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; else if (0u - (size_t)pos > len) return 0; - else return len - ((size_t)-pos) + 1; + else return (lua_Integer)len + pos + 1; } static int str_sub (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); - size_t start = posrelat(luaL_checkinteger(L, 2), l); - size_t end = posrelat(luaL_optinteger(L, 3, -1), l); + lua_Integer start = posrelat(luaL_checkinteger(L, 2), l); + lua_Integer end = posrelat(luaL_optinteger(L, 3, -1), l); if (start < 1) start = 1; - if (end > l) end = l; + if (end > (lua_Integer)l) end = l; if (start <= end) - lua_pushlstring(L, s + start - 1, end - start + 1); + lua_pushlstring(L, s + start - 1, (size_t)(end - start) + 1); else lua_pushliteral(L, ""); return 1; } @@ -102,25 +119,23 @@ static int str_upper (lua_State *L) { } -/* reasonable limit to avoid arithmetic overflow */ -#define MAXSIZE ((~(size_t)0) >> 1) - static int str_rep (lua_State *L) { size_t l, lsep; const char *s = luaL_checklstring(L, 1, &l); - int n = luaL_checkint(L, 2); + lua_Integer n = luaL_checkinteger(L, 2); const char *sep = luaL_optlstring(L, 3, "", &lsep); if (n <= 0) lua_pushliteral(L, ""); - else if (l + lsep < l || l + lsep >= MAXSIZE / n) /* may overflow? */ + else if (l + lsep < l || l + lsep > MAXSIZE / n) /* may overflow? */ return luaL_error(L, "resulting string too large"); else { - size_t totallen = n * l + (n - 1) * lsep; + size_t totallen = (size_t)n * l + (size_t)(n - 1) * lsep; luaL_Buffer b; char *p = luaL_buffinitsize(L, &b, totallen); while (n-- > 1) { /* first n-1 copies (followed by separator) */ memcpy(p, s, l * sizeof(char)); p += l; - if (lsep > 0) { /* avoid empty 'memcpy' (may be expensive) */ - memcpy(p, sep, lsep * sizeof(char)); p += lsep; + if (lsep > 0) { /* empty 'memcpy' is not that cheap */ + memcpy(p, sep, lsep * sizeof(char)); + p += lsep; } } memcpy(p, s, l * sizeof(char)); /* last copy (not followed by separator) */ @@ -133,15 +148,15 @@ static int str_rep (lua_State *L) { static int str_byte (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); - size_t posi = posrelat(luaL_optinteger(L, 2, 1), l); - size_t pose = posrelat(luaL_optinteger(L, 3, posi), l); + lua_Integer posi = posrelat(luaL_optinteger(L, 2, 1), l); + lua_Integer pose = posrelat(luaL_optinteger(L, 3, posi), l); int n, i; if (posi < 1) posi = 1; - if (pose > l) pose = l; + if (pose > (lua_Integer)l) pose = l; if (posi > pose) return 0; /* empty interval; return no values */ - n = (int)(pose - posi + 1); - if (posi + n <= pose) /* (size_t -> int) overflow? */ + if (pose - posi >= INT_MAX) /* arithmetic overflow? */ return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; luaL_checkstack(L, n, "string slice too long"); for (i=0; ip_end) - luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); + luaL_error(ms->L, "malformed pattern (ends with '%%')"); return p+1; } case '[': { if (*p == '^') p++; - do { /* look for a `]' */ + do { /* look for a ']' */ if (p == ms->p_end) - luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); + luaL_error(ms->L, "malformed pattern (missing ']')"); if (*(p++) == L_ESC && p < ms->p_end) - p++; /* skip escapes (e.g. `%]') */ + p++; /* skip escapes (e.g. '%]') */ } while (*p != ']'); return p+1; } @@ -287,7 +303,7 @@ static int matchbracketclass (int c, const char *p, const char *ec) { int sig = 1; if (*(p+1) == '^') { sig = 0; - p++; /* skip the `^' */ + p++; /* skip the '^' */ } while (++p < ec) { if (*p == L_ESC) { @@ -325,8 +341,7 @@ static int singlematch (MatchState *ms, const char *s, const char *p, static const char *matchbalance (MatchState *ms, const char *s, const char *p) { if (p >= ms->p_end - 1) - luaL_error(ms->L, "malformed pattern " - "(missing arguments to " LUA_QL("%%b") ")"); + luaL_error(ms->L, "malformed pattern (missing arguments to '%%b')"); if (*s != *p) return NULL; else { int b = *p; @@ -425,7 +440,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { break; } case '$': { - if ((p + 1) != ms->p_end) /* is the `$' the last char in pattern? */ + if ((p + 1) != ms->p_end) /* is the '$' the last char in pattern? */ goto dflt; /* no; go to default */ s = (s == ms->src_end) ? s : NULL; /* check end of string */ break; @@ -443,8 +458,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { const char *ep; char previous; p += 2; if (*p != '[') - luaL_error(ms->L, "missing " LUA_QL("[") " after " - LUA_QL("%%f") " in pattern"); + luaL_error(ms->L, "missing '[' after '%%f' in pattern"); ep = classend(ms, p); /* points to what is next */ previous = (s == ms->src_init) ? '\0' : *(s - 1); if (!matchbracketclass(uchar(previous), p, ep - 1) && @@ -490,7 +504,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { } case '+': /* 1 or more repetitions */ s++; /* 1 match already done */ - /* go through */ + /* FALLTHROUGH */ case '*': /* 0 or more repetitions */ s = max_expand(ms, s, p, ep); break; @@ -514,16 +528,16 @@ static const char *match (MatchState *ms, const char *s, const char *p) { static const char *lmemfind (const char *s1, size_t l1, const char *s2, size_t l2) { if (l2 == 0) return s1; /* empty strings are everywhere */ - else if (l2 > l1) return NULL; /* avoids a negative `l1' */ + else if (l2 > l1) return NULL; /* avoids a negative 'l1' */ else { - const char *init; /* to search for a `*s2' inside `s1' */ - l2--; /* 1st char will be checked by `memchr' */ - l1 = l1-l2; /* `s2' cannot be found after that */ + const char *init; /* to search for a '*s2' inside 's1' */ + l2--; /* 1st char will be checked by 'memchr' */ + l1 = l1-l2; /* 's2' cannot be found after that */ while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { init++; /* 1st char is already checked */ if (memcmp(init, s2+1, l2) == 0) return init-1; - else { /* correct `l1' and `s1' to try again */ + else { /* correct 'l1' and 's1' to try again */ l1 -= init-s1; s1 = init; } @@ -539,13 +553,13 @@ static void push_onecapture (MatchState *ms, int i, const char *s, if (i == 0) /* ms->level == 0, too */ lua_pushlstring(ms->L, s, e - s); /* add whole match */ else - luaL_error(ms->L, "invalid capture index"); + luaL_error(ms->L, "invalid capture index %%%d", i + 1); } else { ptrdiff_t l = ms->capture[i].len; if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); if (l == CAP_POSITION) - lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1); + lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1); else lua_pushlstring(ms->L, ms->capture[i].init, l); } @@ -574,23 +588,39 @@ static int nospecials (const char *p, size_t l) { } +static void prepstate (MatchState *ms, lua_State *L, + const char *s, size_t ls, const char *p, size_t lp) { + ms->L = L; + ms->matchdepth = MAXCCALLS; + ms->src_init = s; + ms->src_end = s + ls; + ms->p_end = p + lp; +} + + +static void reprepstate (MatchState *ms) { + ms->level = 0; + lua_assert(ms->matchdepth == MAXCCALLS); +} + + static int str_find_aux (lua_State *L, int find) { size_t ls, lp; const char *s = luaL_checklstring(L, 1, &ls); const char *p = luaL_checklstring(L, 2, &lp); - size_t init = posrelat(luaL_optinteger(L, 3, 1), ls); + lua_Integer init = posrelat(luaL_optinteger(L, 3, 1), ls); if (init < 1) init = 1; - else if (init > ls + 1) { /* start after string's end? */ + else if (init > (lua_Integer)ls + 1) { /* start after string's end? */ lua_pushnil(L); /* cannot find anything */ return 1; } /* explicit request or no special characters? */ if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { /* do a plain search */ - const char *s2 = lmemfind(s + init - 1, ls - init + 1, p, lp); + const char *s2 = lmemfind(s + init - 1, ls - (size_t)init + 1, p, lp); if (s2) { - lua_pushinteger(L, s2 - s + 1); - lua_pushinteger(L, s2 - s + lp); + lua_pushinteger(L, (s2 - s) + 1); + lua_pushinteger(L, (s2 - s) + lp); return 2; } } @@ -601,18 +631,13 @@ static int str_find_aux (lua_State *L, int find) { if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s + ls; - ms.p_end = p + lp; + prepstate(&ms, L, s, ls, p, lp); do { const char *res; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); + reprepstate(&ms); if ((res=match(&ms, s1, p)) != NULL) { if (find) { - lua_pushinteger(L, s1 - s + 1); /* start */ + lua_pushinteger(L, (s1 - s) + 1); /* start */ lua_pushinteger(L, res - s); /* end */ return push_captures(&ms, NULL, 0) + 2; } @@ -636,29 +661,25 @@ static int str_match (lua_State *L) { } +/* state for 'gmatch' */ +typedef struct GMatchState { + const char *src; /* current position */ + const char *p; /* pattern */ + const char *lastmatch; /* end of last match */ + MatchState ms; /* match state */ +} GMatchState; + + static int gmatch_aux (lua_State *L) { - MatchState ms; - size_t ls, lp; - const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); - const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp); + GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); const char *src; - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s+ls; - ms.p_end = p + lp; - for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); - src <= ms.src_end; - src++) { + gm->ms.L = L; + for (src = gm->src; src <= gm->ms.src_end; src++) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - if ((e = match(&ms, src, p)) != NULL) { - lua_Integer newstart = e-s; - if (e == src) newstart++; /* empty match? go at least one position */ - lua_pushinteger(L, newstart); - lua_replace(L, lua_upvalueindex(3)); - return push_captures(&ms, src, e); + reprepstate(&gm->ms); + if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) { + gm->src = gm->lastmatch = e; + return push_captures(&gm->ms, src, e); } } return 0; /* not found */ @@ -666,10 +687,14 @@ static int gmatch_aux (lua_State *L) { static int gmatch (lua_State *L) { - luaL_checkstring(L, 1); - luaL_checkstring(L, 2); - lua_settop(L, 2); - lua_pushinteger(L, 0); + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + GMatchState *gm; + lua_settop(L, 2); /* keep them on closure to avoid being collected */ + gm = (GMatchState *)lua_newuserdata(L, sizeof(GMatchState)); + prepstate(&gm->ms, L, s, ls, p, lp); + gm->src = s; gm->p = p; gm->lastmatch = NULL; lua_pushcclosure(L, gmatch_aux, 3); return 1; } @@ -678,7 +703,8 @@ static int gmatch (lua_State *L) { static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { size_t l, i; - const char *news = lua_tolstring(ms->L, 3, &l); + lua_State *L = ms->L; + const char *news = lua_tolstring(L, 3, &l); for (i = 0; i < l; i++) { if (news[i] != L_ESC) luaL_addchar(b, news[i]); @@ -686,14 +712,15 @@ static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, i++; /* skip ESC */ if (!isdigit(uchar(news[i]))) { if (news[i] != L_ESC) - luaL_error(ms->L, "invalid use of " LUA_QL("%c") - " in replacement string", L_ESC); + luaL_error(L, "invalid use of '%c' in replacement string", L_ESC); luaL_addchar(b, news[i]); } else if (news[i] == '0') luaL_addlstring(b, s, e - s); else { push_onecapture(ms, news[i] - '1', s, e); + luaL_tolstring(L, -1, NULL); /* if number, convert it to string */ + lua_remove(L, -2); /* remove original value */ luaL_addvalue(b); /* add capture to accumulated result */ } } @@ -734,12 +761,13 @@ static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, static int str_gsub (lua_State *L) { size_t srcl, lp; - const char *src = luaL_checklstring(L, 1, &srcl); - const char *p = luaL_checklstring(L, 2, &lp); - int tr = lua_type(L, 3); - size_t max_s = luaL_optinteger(L, 4, srcl+1); + const char *src = luaL_checklstring(L, 1, &srcl); /* subject */ + const char *p = luaL_checklstring(L, 2, &lp); /* pattern */ + const char *lastmatch = NULL; /* end of last match */ + int tr = lua_type(L, 3); /* replacement type */ + lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */ int anchor = (*p == '^'); - size_t n = 0; + lua_Integer n = 0; /* replacement count */ MatchState ms; luaL_Buffer b; luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || @@ -749,25 +777,18 @@ static int str_gsub (lua_State *L) { if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = src; - ms.src_end = src+srcl; - ms.p_end = p + lp; + prepstate(&ms, L, src, srcl, p, lp); while (n < max_s) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - e = match(&ms, src, p); - if (e) { + reprepstate(&ms); /* (re)prepare state for new match */ + if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */ n++; - add_value(&ms, &b, src, e, tr); + add_value(&ms, &b, src, e, tr); /* add replacement to buffer */ + src = lastmatch = e; } - if (e && e>src) /* non empty match? */ - src = e; /* skip it */ - else if (src < ms.src_end) + else if (src < ms.src_end) /* otherwise, skip one character */ luaL_addchar(&b, *src++); - else break; + else break; /* end of subject */ if (anchor) break; } luaL_addlstring(&b, src, ms.src_end-src); @@ -786,65 +807,116 @@ static int str_gsub (lua_State *L) { ** ======================================================= */ +#if !defined(lua_number2strx) /* { */ + /* -** LUA_INTFRMLEN is the length modifier for integer conversions in -** 'string.format'; LUA_INTFRM_T is the integer type corresponding to -** the previous length +** Hexadecimal floating-point formatter */ -#if !defined(LUA_INTFRMLEN) /* { */ -#if defined(LUA_USE_LONGLONG) -#define LUA_INTFRMLEN "ll" -#define LUA_INTFRM_T long long +#include -#else +#define SIZELENMOD (sizeof(LUA_NUMBER_FRMLEN)/sizeof(char)) -#define LUA_INTFRMLEN "l" -#define LUA_INTFRM_T long -#endif -#endif /* } */ +/* +** Number of bits that goes into the first digit. It can be any value +** between 1 and 4; the following definition tries to align the number +** to nibble boundaries by making what is left after that first digit a +** multiple of 4. +*/ +#define L_NBFD ((l_mathlim(MANT_DIG) - 1)%4 + 1) /* -** LUA_FLTFRMLEN is the length modifier for float conversions in -** 'string.format'; LUA_FLTFRM_T is the float type corresponding to -** the previous length +** Add integer part of 'x' to buffer and return new 'x' */ -#if !defined(LUA_FLTFRMLEN) +static lua_Number adddigit (char *buff, int n, lua_Number x) { + lua_Number dd = l_mathop(floor)(x); /* get integer part from 'x' */ + int d = (int)dd; + buff[n] = (d < 10 ? d + '0' : d - 10 + 'a'); /* add to buffer */ + return x - dd; /* return what is left */ +} -#define LUA_FLTFRMLEN "" -#define LUA_FLTFRM_T double -#endif +static int num2straux (char *buff, int sz, lua_Number x) { + if (x != x || x == HUGE_VAL || x == -HUGE_VAL) /* inf or NaN? */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT, x); /* equal to '%g' */ + else if (x == 0) { /* can be -0... */ + /* create "0" or "-0" followed by exponent */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT "x0p+0", x); + } + else { + int e; + lua_Number m = l_mathop(frexp)(x, &e); /* 'x' fraction and exponent */ + int n = 0; /* character count */ + if (m < 0) { /* is number negative? */ + buff[n++] = '-'; /* add signal */ + m = -m; /* make it positive */ + } + buff[n++] = '0'; buff[n++] = 'x'; /* add "0x" */ + m = adddigit(buff, n++, m * (1 << L_NBFD)); /* add first digit */ + e -= L_NBFD; /* this digit goes before the radix point */ + if (m > 0) { /* more digits? */ + buff[n++] = lua_getlocaledecpoint(); /* add radix point */ + do { /* add as many digits as needed */ + m = adddigit(buff, n++, m * 16); + } while (m > 0); + } + n += l_sprintf(buff + n, sz - n, "p%+d", e); /* add exponent */ + lua_assert(n < sz); + return n; + } +} + + +static int lua_number2strx (lua_State *L, char *buff, int sz, + const char *fmt, lua_Number x) { + int n = num2straux(buff, sz, x); + if (fmt[SIZELENMOD] == 'A') { + int i; + for (i = 0; i < n; i++) + buff[i] = toupper(uchar(buff[i])); + } + else if (fmt[SIZELENMOD] != 'a') + luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); + return n; +} + +#endif /* } */ + + +/* +** Maximum size of each formatted item. This maximum size is produced +** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.', +** and '\0') + number of decimal digits to represent maxfloat (which +** is maximum exponent + 1). (99+3+1 then rounded to 120 for "extra +** expenses", such as locale-dependent stuff) +*/ +#define MAX_ITEM (120 + l_mathlim(MAX_10_EXP)) -/* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ -#define MAX_ITEM 512 /* valid flags in a format specification */ #define FLAGS "-+ #0" + /* -** maximum size of each format specification (such as '%-099.99d') -** (+10 accounts for %99.99x plus margin of error) +** maximum size of each format specification (such as "%-099.99d") */ -#define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) +#define MAX_FORMAT 32 -static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { - size_t l; - const char *s = luaL_checklstring(L, arg, &l); +static void addquoted (luaL_Buffer *b, const char *s, size_t len) { luaL_addchar(b, '"'); - while (l--) { + while (len--) { if (*s == '"' || *s == '\\' || *s == '\n') { luaL_addchar(b, '\\'); luaL_addchar(b, *s); } - else if (*s == '\0' || iscntrl(uchar(*s))) { + else if (iscntrl(uchar(*s))) { char buff[10]; if (!isdigit(uchar(*(s+1)))) - sprintf(buff, "\\%d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%d", (int)uchar(*s)); else - sprintf(buff, "\\%03d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%03d", (int)uchar(*s)); luaL_addstring(b, buff); } else @@ -854,6 +926,57 @@ static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { luaL_addchar(b, '"'); } + +/* +** Ensures the 'buff' string uses a dot as the radix character. +*/ +static void checkdp (char *buff, int nb) { + if (memchr(buff, '.', nb) == NULL) { /* no dot? */ + char point = lua_getlocaledecpoint(); /* try locale point */ + char *ppoint = (char*)memchr(buff, point, nb); + if (ppoint) *ppoint = '.'; /* change it to a dot */ + } +} + + +static void addliteral (lua_State *L, luaL_Buffer *b, int arg) { + switch (lua_type(L, arg)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(L, arg, &len); + addquoted(b, s, len); + break; + } + case LUA_TNUMBER: { + char *buff = luaL_prepbuffsize(b, MAX_ITEM); + int nb; + if (!lua_isinteger(L, arg)) { /* float? */ + lua_Number n = lua_tonumber(L, arg); /* write as hexa ('%a') */ + nb = lua_number2strx(L, buff, MAX_ITEM, "%" LUA_NUMBER_FRMLEN "a", n); + checkdp(buff, nb); /* ensure it uses a dot */ + } + else { /* integers */ + lua_Integer n = lua_tointeger(L, arg); + const char *format = (n == LUA_MININTEGER) /* corner case? */ + ? "0x%" LUA_INTEGER_FRMLEN "x" /* use hexa */ + : LUA_INTEGER_FMT; /* else use default format */ + nb = l_sprintf(buff, MAX_ITEM, format, n); + } + luaL_addsize(b, nb); + break; + } + case LUA_TNIL: case LUA_TBOOLEAN: { + luaL_tolstring(L, arg, NULL); + luaL_addvalue(b); + break; + } + default: { + luaL_argerror(L, arg, "value has no literal form"); + } + } +} + + static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ @@ -869,8 +992,8 @@ static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); *(form++) = '%'; - memcpy(form, strfrmt, (p - strfrmt + 1) * sizeof(char)); - form += p - strfrmt + 1; + memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); + form += (p - strfrmt) + 1; *form = '\0'; return p; } @@ -903,7 +1026,7 @@ static int str_format (lua_State *L) { else if (*++strfrmt == L_ESC) luaL_addchar(&b, *strfrmt++); /* %% */ else { /* format item */ - char form[MAX_FORMAT]; /* to store the format (`%...') */ + char form[MAX_FORMAT]; /* to store the format ('%...') */ char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */ int nb = 0; /* number of bytes in added item */ if (++arg > top) @@ -911,62 +1034,55 @@ static int str_format (lua_State *L) { strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { - nb = sprintf(buff, form, luaL_checkint(L, arg)); - break; - } - case 'd': case 'i': { - lua_Number n = luaL_checknumber(L, arg); - LUA_INTFRM_T ni = (LUA_INTFRM_T)n; - lua_Number diff = n - (lua_Number)ni; - luaL_argcheck(L, -1 < diff && diff < 1, arg, - "not a number in proper range"); - addlenmod(form, LUA_INTFRMLEN); - nb = sprintf(buff, form, ni); + nb = l_sprintf(buff, MAX_ITEM, form, (int)luaL_checkinteger(L, arg)); break; } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { - lua_Number n = luaL_checknumber(L, arg); - unsigned LUA_INTFRM_T ni = (unsigned LUA_INTFRM_T)n; - lua_Number diff = n - (lua_Number)ni; - luaL_argcheck(L, -1 < diff && diff < 1, arg, - "not a non-negative number in proper range"); - addlenmod(form, LUA_INTFRMLEN); - nb = sprintf(buff, form, ni); + lua_Integer n = luaL_checkinteger(L, arg); + addlenmod(form, LUA_INTEGER_FRMLEN); + nb = l_sprintf(buff, MAX_ITEM, form, n); break; } - case 'e': case 'E': case 'f': -#if defined(LUA_USE_AFORMAT) case 'a': case 'A': -#endif + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = lua_number2strx(L, buff, MAX_ITEM, form, + luaL_checknumber(L, arg)); + break; + case 'e': case 'E': case 'f': case 'g': case 'G': { - addlenmod(form, LUA_FLTFRMLEN); - nb = sprintf(buff, form, (LUA_FLTFRM_T)luaL_checknumber(L, arg)); + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = l_sprintf(buff, MAX_ITEM, form, luaL_checknumber(L, arg)); break; } case 'q': { - addquoted(L, &b, arg); + addliteral(L, &b, arg); break; } case 's': { size_t l; const char *s = luaL_tolstring(L, arg, &l); - if (!strchr(form, '.') && l >= 100) { - /* no precision and string is too long to be formatted; - keep original string */ - luaL_addvalue(&b); - break; - } + if (form[2] == '\0') /* no modifiers? */ + luaL_addvalue(&b); /* keep entire string */ else { - nb = sprintf(buff, form, s); - lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ - break; + luaL_argcheck(L, l == strlen(s), arg, "string contains zeros"); + if (!strchr(form, '.') && l >= 100) { + /* no precision and string is too long to be formatted */ + luaL_addvalue(&b); /* keep entire string */ + } + else { /* format the string into 'buff' */ + nb = l_sprintf(buff, MAX_ITEM, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + } } + break; } - default: { /* also treat cases `pnLlh' */ - return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " - LUA_QL("format"), *(strfrmt - 1)); + default: { /* also treat cases 'pnLlh' */ + return luaL_error(L, "invalid option '%%%c' to 'format'", + *(strfrmt - 1)); } } + lua_assert(nb < MAX_ITEM); luaL_addsize(&b, nb); } } @@ -977,6 +1093,450 @@ static int str_format (lua_State *L) { /* }====================================================== */ +/* +** {====================================================== +** PACK/UNPACK +** ======================================================= +*/ + + +/* value used for padding */ +#if !defined(LUAL_PACKPADBYTE) +#define LUAL_PACKPADBYTE 0x00 +#endif + +/* maximum size for the binary representation of an integer */ +#define MAXINTSIZE 16 + +/* number of bits in a character */ +#define NB CHAR_BIT + +/* mask for one character (NB 1's) */ +#define MC ((1 << NB) - 1) + +/* size of a lua_Integer */ +#define SZINT ((int)sizeof(lua_Integer)) + + +/* dummy union to get native endianness */ +static const union { + int dummy; + char little; /* true iff machine is little endian */ +} nativeendian = {1}; + + +/* dummy structure to get native alignment requirements */ +struct cD { + char c; + union { double d; void *p; lua_Integer i; lua_Number n; } u; +}; + +#define MAXALIGN (offsetof(struct cD, u)) + + +/* +** Union for serializing floats +*/ +typedef union Ftypes { + float f; + double d; + lua_Number n; + char buff[5 * sizeof(lua_Number)]; /* enough for any float type */ +} Ftypes; + + +/* +** information to pack/unpack stuff +*/ +typedef struct Header { + lua_State *L; + int islittle; + int maxalign; +} Header; + + +/* +** options for pack/unpack +*/ +typedef enum KOption { + Kint, /* signed integers */ + Kuint, /* unsigned integers */ + Kfloat, /* floating-point numbers */ + Kchar, /* fixed-length strings */ + Kstring, /* strings with prefixed length */ + Kzstr, /* zero-terminated strings */ + Kpadding, /* padding */ + Kpaddalign, /* padding for alignment */ + Knop /* no-op (configuration or spaces) */ +} KOption; + + +/* +** Read an integer numeral from string 'fmt' or return 'df' if +** there is no numeral +*/ +static int digit (int c) { return '0' <= c && c <= '9'; } + +static int getnum (const char **fmt, int df) { + if (!digit(**fmt)) /* no number? */ + return df; /* return default value */ + else { + int a = 0; + do { + a = a*10 + (*((*fmt)++) - '0'); + } while (digit(**fmt) && a <= ((int)MAXSIZE - 9)/10); + return a; + } +} + + +/* +** Read an integer numeral and raises an error if it is larger +** than the maximum size for integers. +*/ +static int getnumlimit (Header *h, const char **fmt, int df) { + int sz = getnum(fmt, df); + if (sz > MAXINTSIZE || sz <= 0) + luaL_error(h->L, "integral size (%d) out of limits [1,%d]", + sz, MAXINTSIZE); + return sz; +} + + +/* +** Initialize Header +*/ +static void initheader (lua_State *L, Header *h) { + h->L = L; + h->islittle = nativeendian.little; + h->maxalign = 1; +} + + +/* +** Read and classify next option. 'size' is filled with option's size. +*/ +static KOption getoption (Header *h, const char **fmt, int *size) { + int opt = *((*fmt)++); + *size = 0; /* default */ + switch (opt) { + case 'b': *size = sizeof(char); return Kint; + case 'B': *size = sizeof(char); return Kuint; + case 'h': *size = sizeof(short); return Kint; + case 'H': *size = sizeof(short); return Kuint; + case 'l': *size = sizeof(long); return Kint; + case 'L': *size = sizeof(long); return Kuint; + case 'j': *size = sizeof(lua_Integer); return Kint; + case 'J': *size = sizeof(lua_Integer); return Kuint; + case 'T': *size = sizeof(size_t); return Kuint; + case 'f': *size = sizeof(float); return Kfloat; + case 'd': *size = sizeof(double); return Kfloat; + case 'n': *size = sizeof(lua_Number); return Kfloat; + case 'i': *size = getnumlimit(h, fmt, sizeof(int)); return Kint; + case 'I': *size = getnumlimit(h, fmt, sizeof(int)); return Kuint; + case 's': *size = getnumlimit(h, fmt, sizeof(size_t)); return Kstring; + case 'c': + *size = getnum(fmt, -1); + if (*size == -1) + luaL_error(h->L, "missing size for format option 'c'"); + return Kchar; + case 'z': return Kzstr; + case 'x': *size = 1; return Kpadding; + case 'X': return Kpaddalign; + case ' ': break; + case '<': h->islittle = 1; break; + case '>': h->islittle = 0; break; + case '=': h->islittle = nativeendian.little; break; + case '!': h->maxalign = getnumlimit(h, fmt, MAXALIGN); break; + default: luaL_error(h->L, "invalid format option '%c'", opt); + } + return Knop; +} + + +/* +** Read, classify, and fill other details about the next option. +** 'psize' is filled with option's size, 'notoalign' with its +** alignment requirements. +** Local variable 'size' gets the size to be aligned. (Kpadal option +** always gets its full alignment, other options are limited by +** the maximum alignment ('maxalign'). Kchar option needs no alignment +** despite its size. +*/ +static KOption getdetails (Header *h, size_t totalsize, + const char **fmt, int *psize, int *ntoalign) { + KOption opt = getoption(h, fmt, psize); + int align = *psize; /* usually, alignment follows size */ + if (opt == Kpaddalign) { /* 'X' gets alignment from following option */ + if (**fmt == '\0' || getoption(h, fmt, &align) == Kchar || align == 0) + luaL_argerror(h->L, 1, "invalid next option for option 'X'"); + } + if (align <= 1 || opt == Kchar) /* need no alignment? */ + *ntoalign = 0; + else { + if (align > h->maxalign) /* enforce maximum alignment */ + align = h->maxalign; + if ((align & (align - 1)) != 0) /* is 'align' not a power of 2? */ + luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); + *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1); + } + return opt; +} + + +/* +** Pack integer 'n' with 'size' bytes and 'islittle' endianness. +** The final 'if' handles the case when 'size' is larger than +** the size of a Lua integer, correcting the extra sign-extension +** bytes if necessary (by default they would be zeros). +*/ +static void packint (luaL_Buffer *b, lua_Unsigned n, + int islittle, int size, int neg) { + char *buff = luaL_prepbuffsize(b, size); + int i; + buff[islittle ? 0 : size - 1] = (char)(n & MC); /* first byte */ + for (i = 1; i < size; i++) { + n >>= NB; + buff[islittle ? i : size - 1 - i] = (char)(n & MC); + } + if (neg && size > SZINT) { /* negative number need sign extension? */ + for (i = SZINT; i < size; i++) /* correct extra bytes */ + buff[islittle ? i : size - 1 - i] = (char)MC; + } + luaL_addsize(b, size); /* add result to buffer */ +} + + +/* +** Copy 'size' bytes from 'src' to 'dest', correcting endianness if +** given 'islittle' is different from native endianness. +*/ +static void copywithendian (volatile char *dest, volatile const char *src, + int size, int islittle) { + if (islittle == nativeendian.little) { + while (size-- != 0) + *(dest++) = *(src++); + } + else { + dest += size - 1; + while (size-- != 0) + *(dest--) = *(src++); + } +} + + +static int str_pack (lua_State *L) { + luaL_Buffer b; + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + int arg = 1; /* current argument to pack */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + lua_pushnil(L); /* mark to separate arguments from string buffer */ + luaL_buffinit(L, &b); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + totalsize += ntoalign + size; + while (ntoalign-- > 0) + luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ + arg++; + switch (opt) { + case Kint: { /* signed integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) { /* need overflow check? */ + lua_Integer lim = (lua_Integer)1 << ((size * NB) - 1); + luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow"); + } + packint(&b, (lua_Unsigned)n, h.islittle, size, (n < 0)); + break; + } + case Kuint: { /* unsigned integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) /* need overflow check? */ + luaL_argcheck(L, (lua_Unsigned)n < ((lua_Unsigned)1 << (size * NB)), + arg, "unsigned overflow"); + packint(&b, (lua_Unsigned)n, h.islittle, size, 0); + break; + } + case Kfloat: { /* floating-point options */ + volatile Ftypes u; + char *buff = luaL_prepbuffsize(&b, size); + lua_Number n = luaL_checknumber(L, arg); /* get argument */ + if (size == sizeof(u.f)) u.f = (float)n; /* copy it into 'u' */ + else if (size == sizeof(u.d)) u.d = (double)n; + else u.n = n; + /* move 'u' to final result, correcting endianness if needed */ + copywithendian(buff, u.buff, size, h.islittle); + luaL_addsize(&b, size); + break; + } + case Kchar: { /* fixed-size string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, len <= (size_t)size, arg, + "string longer than given size"); + luaL_addlstring(&b, s, len); /* add string */ + while (len++ < (size_t)size) /* pad extra space */ + luaL_addchar(&b, LUAL_PACKPADBYTE); + break; + } + case Kstring: { /* strings with length count */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, size >= (int)sizeof(size_t) || + len < ((size_t)1 << (size * NB)), + arg, "string length does not fit in given size"); + packint(&b, (lua_Unsigned)len, h.islittle, size, 0); /* pack length */ + luaL_addlstring(&b, s, len); + totalsize += len; + break; + } + case Kzstr: { /* zero-terminated string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros"); + luaL_addlstring(&b, s, len); + luaL_addchar(&b, '\0'); /* add zero at the end */ + totalsize += len + 1; + break; + } + case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ + case Kpaddalign: case Knop: + arg--; /* undo increment */ + break; + } + } + luaL_pushresult(&b); + return 1; +} + + +static int str_packsize (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + size += ntoalign; /* total space used by option */ + luaL_argcheck(L, totalsize <= MAXSIZE - size, 1, + "format result too large"); + totalsize += size; + switch (opt) { + case Kstring: /* strings with length count */ + case Kzstr: /* zero-terminated string */ + luaL_argerror(L, 1, "variable-length format"); + /* call never return, but to avoid warnings: *//* FALLTHROUGH */ + default: break; + } + } + lua_pushinteger(L, (lua_Integer)totalsize); + return 1; +} + + +/* +** Unpack an integer with 'size' bytes and 'islittle' endianness. +** If size is smaller than the size of a Lua integer and integer +** is signed, must do sign extension (propagating the sign to the +** higher bits); if size is larger than the size of a Lua integer, +** it must check the unread bytes to see whether they do not cause an +** overflow. +*/ +static lua_Integer unpackint (lua_State *L, const char *str, + int islittle, int size, int issigned) { + lua_Unsigned res = 0; + int i; + int limit = (size <= SZINT) ? size : SZINT; + for (i = limit - 1; i >= 0; i--) { + res <<= NB; + res |= (lua_Unsigned)(unsigned char)str[islittle ? i : size - 1 - i]; + } + if (size < SZINT) { /* real size smaller than lua_Integer? */ + if (issigned) { /* needs sign extension? */ + lua_Unsigned mask = (lua_Unsigned)1 << (size*NB - 1); + res = ((res ^ mask) - mask); /* do sign extension */ + } + } + else if (size > SZINT) { /* must check unread bytes */ + int mask = (!issigned || (lua_Integer)res >= 0) ? 0 : MC; + for (i = limit; i < size; i++) { + if ((unsigned char)str[islittle ? i : size - 1 - i] != mask) + luaL_error(L, "%d-byte integer does not fit into Lua Integer", size); + } + } + return (lua_Integer)res; +} + + +static int str_unpack (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); + size_t ld; + const char *data = luaL_checklstring(L, 2, &ld); + size_t pos = (size_t)posrelat(luaL_optinteger(L, 3, 1), ld) - 1; + int n = 0; /* number of results */ + luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign); + if ((size_t)ntoalign + size > ~pos || pos + ntoalign + size > ld) + luaL_argerror(L, 2, "data string too short"); + pos += ntoalign; /* skip alignment */ + /* stack space for item + next position */ + luaL_checkstack(L, 2, "too many results"); + n++; + switch (opt) { + case Kint: + case Kuint: { + lua_Integer res = unpackint(L, data + pos, h.islittle, size, + (opt == Kint)); + lua_pushinteger(L, res); + break; + } + case Kfloat: { + volatile Ftypes u; + lua_Number num; + copywithendian(u.buff, data + pos, size, h.islittle); + if (size == sizeof(u.f)) num = (lua_Number)u.f; + else if (size == sizeof(u.d)) num = (lua_Number)u.d; + else num = u.n; + lua_pushnumber(L, num); + break; + } + case Kchar: { + lua_pushlstring(L, data + pos, size); + break; + } + case Kstring: { + size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0); + luaL_argcheck(L, pos + len + size <= ld, 2, "data string too short"); + lua_pushlstring(L, data + pos + size, len); + pos += len; /* skip string */ + break; + } + case Kzstr: { + size_t len = (int)strlen(data + pos); + lua_pushlstring(L, data + pos, len); + pos += len + 1; /* skip string plus final '\0' */ + break; + } + case Kpaddalign: case Kpadding: case Knop: + n--; /* undo increment */ + break; + } + pos += size; + } + lua_pushinteger(L, pos + 1); /* next position */ + return n + 1; +} + +/* }====================================================== */ + + static const luaL_Reg strlib[] = { {"byte", str_byte}, {"char", str_char}, @@ -992,6 +1552,9 @@ static const luaL_Reg strlib[] = { {"reverse", str_reverse}, {"sub", str_sub}, {"upper", str_upper}, + {"pack", str_pack}, + {"packsize", str_packsize}, + {"unpack", str_unpack}, {NULL, NULL} }; diff --git a/depends/lua/src/ltable.c b/depends/lua/src/ltable.c index 5d76f97ec..7e15b71ba 100644 --- a/depends/lua/src/ltable.c +++ b/depends/lua/src/ltable.c @@ -1,27 +1,30 @@ /* -** $Id: ltable.c,v 2.72.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltable.c,v 2.117 2015/11/19 19:16:22 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ +#define ltable_c +#define LUA_CORE + +#include "lprefix.h" + /* ** Implementation of tables (aka arrays, objects, or hash tables). ** Tables keep its elements in two parts: an array part and a hash part. ** Non-negative integer keys are all candidates to be kept in the array -** part. The actual size of the array is the largest `n' such that at -** least half the slots between 0 and n are in use. +** part. The actual size of the array is the largest 'n' such that +** more than half the slots between 1 and n are in use. ** Hash uses a mix of chained scatter table with Brent's variation. ** A main invariant of these tables is that, if an element is not -** in its main position (i.e. the `original' position that its hash gives +** in its main position (i.e. the 'original' position that its hash gives ** to it), then the colliding element is in its own main position. ** Hence even when the load factor reaches 100%, performance remains good. */ -#include - -#define ltable_c -#define LUA_CORE +#include +#include #include "lua.h" @@ -37,21 +40,26 @@ /* -** max size of array part is 2^MAXBITS +** Maximum size of array part (MAXASIZE) is 2^MAXABITS. MAXABITS is +** the largest integer such that MAXASIZE fits in an unsigned int. */ -#if LUAI_BITSINT >= 32 -#define MAXBITS 30 -#else -#define MAXBITS (LUAI_BITSINT-2) -#endif +#define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1) +#define MAXASIZE (1u << MAXABITS) -#define MAXASIZE (1 << MAXBITS) +/* +** Maximum size of hash part is 2^MAXHBITS. MAXHBITS is the largest +** integer such that 2^MAXHBITS fits in a signed int. (Note that the +** maximum number of elements in a table, 2^MAXABITS + 2^MAXHBITS, still +** fits comfortably in an unsigned int.) +*/ +#define MAXHBITS (MAXABITS - 1) #define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) -#define hashstr(t,str) hashpow2(t, (str)->tsv.hash) +#define hashstr(t,str) hashpow2(t, (str)->hash) #define hashboolean(t,p) hashpow2(t, p) +#define hashint(t,i) hashpow2(t, i) /* @@ -61,7 +69,7 @@ #define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) -#define hashpointer(t,p) hashmod(t, IntPoint(p)) +#define hashpointer(t,p) hashmod(t, point2uint(p)) #define dummynode (&dummynode_) @@ -70,44 +78,54 @@ static const Node dummynode_ = { {NILCONSTANT}, /* value */ - {{NILCONSTANT, NULL}} /* key */ + {{NILCONSTANT, 0}} /* key */ }; /* -** hash for lua_Numbers +** Hash for floating-point numbers. +** The main computation should be just +** n = frexp(n, &i); return (n * INT_MAX) + i +** but there are some numerical subtleties. +** In a two-complement representation, INT_MAX does not has an exact +** representation as a float, but INT_MIN does; because the absolute +** value of 'frexp' is smaller than 1 (unless 'n' is inf/NaN), the +** absolute value of the product 'frexp * -INT_MIN' is smaller or equal +** to INT_MAX. Next, the use of 'unsigned int' avoids overflows when +** adding 'i'; the use of '~u' (instead of '-u') avoids problems with +** INT_MIN. */ -static Node *hashnum (const Table *t, lua_Number n) { +#if !defined(l_hashfloat) +static int l_hashfloat (lua_Number n) { int i; - luai_hashnum(i, n); - if (i < 0) { - if (cast(unsigned int, i) == 0u - i) /* use unsigned to avoid overflows */ - i = 0; /* handle INT_MIN */ - i = -i; /* must be a positive value */ + lua_Integer ni; + n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN); + if (!lua_numbertointeger(n, &ni)) { /* is 'n' inf/-inf/NaN? */ + lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL)); + return 0; + } + else { /* normal case */ + unsigned int u = cast(unsigned int, i) + cast(unsigned int, ni); + return cast_int(u <= cast(unsigned int, INT_MAX) ? u : ~u); } - return hashmod(t, i); } - +#endif /* -** returns the `main' position of an element in a table (that is, the index +** returns the 'main' position of an element in a table (that is, the index ** of its hash value) */ static Node *mainposition (const Table *t, const TValue *key) { switch (ttype(key)) { - case LUA_TNUMBER: - return hashnum(t, nvalue(key)); - case LUA_TLNGSTR: { - TString *s = rawtsvalue(key); - if (s->tsv.extra == 0) { /* no hash? */ - s->tsv.hash = luaS_hash(getstr(s), s->tsv.len, s->tsv.hash); - s->tsv.extra = 1; /* now it has its hash */ - } - return hashstr(t, rawtsvalue(key)); - } + case LUA_TNUMINT: + return hashint(t, ivalue(key)); + case LUA_TNUMFLT: + return hashmod(t, l_hashfloat(fltvalue(key))); case LUA_TSHRSTR: - return hashstr(t, rawtsvalue(key)); + return hashstr(t, tsvalue(key)); + case LUA_TLNGSTR: + return hashpow2(t, luaS_hashlongstr(tsvalue(key))); case LUA_TBOOLEAN: return hashboolean(t, bvalue(key)); case LUA_TLIGHTUSERDATA: @@ -115,67 +133,68 @@ static Node *mainposition (const Table *t, const TValue *key) { case LUA_TLCF: return hashpointer(t, fvalue(key)); default: + lua_assert(!ttisdeadkey(key)); return hashpointer(t, gcvalue(key)); } } /* -** returns the index for `key' if `key' is an appropriate key to live in -** the array part of the table, -1 otherwise. +** returns the index for 'key' if 'key' is an appropriate key to live in +** the array part of the table, 0 otherwise. */ -static int arrayindex (const TValue *key) { - if (ttisnumber(key)) { - lua_Number n = nvalue(key); - int k; - lua_number2int(k, n); - if (luai_numeq(cast_num(k), n)) - return k; +static unsigned int arrayindex (const TValue *key) { + if (ttisinteger(key)) { + lua_Integer k = ivalue(key); + if (0 < k && (lua_Unsigned)k <= MAXASIZE) + return cast(unsigned int, k); /* 'key' is an appropriate array index */ } - return -1; /* `key' did not match some condition */ + return 0; /* 'key' did not match some condition */ } /* -** returns the index of a `key' for table traversals. First goes all +** returns the index of a 'key' for table traversals. First goes all ** elements in the array part, then elements in the hash part. The -** beginning of a traversal is signaled by -1. +** beginning of a traversal is signaled by 0. */ -static int findindex (lua_State *L, Table *t, StkId key) { - int i; - if (ttisnil(key)) return -1; /* first iteration */ +static unsigned int findindex (lua_State *L, Table *t, StkId key) { + unsigned int i; + if (ttisnil(key)) return 0; /* first iteration */ i = arrayindex(key); - if (0 < i && i <= t->sizearray) /* is `key' inside array part? */ - return i-1; /* yes; that's the index (corrected to C) */ + if (i != 0 && i <= t->sizearray) /* is 'key' inside array part? */ + return i; /* yes; that's the index */ else { + int nx; Node *n = mainposition(t, key); - for (;;) { /* check whether `key' is somewhere in the chain */ - /* key may be dead already, but it is ok to use it in `next' */ + for (;;) { /* check whether 'key' is somewhere in the chain */ + /* key may be dead already, but it is ok to use it in 'next' */ if (luaV_rawequalobj(gkey(n), key) || (ttisdeadkey(gkey(n)) && iscollectable(key) && deadvalue(gkey(n)) == gcvalue(key))) { i = cast_int(n - gnode(t, 0)); /* key index in hash table */ /* hash elements are numbered after array ones */ - return i + t->sizearray; + return (i + 1) + t->sizearray; } - else n = gnext(n); - if (n == NULL) - luaG_runerror(L, "invalid key to " LUA_QL("next")); /* key not found */ + nx = gnext(n); + if (nx == 0) + luaG_runerror(L, "invalid key to 'next'"); /* key not found */ + else n += nx; } } } int luaH_next (lua_State *L, Table *t, StkId key) { - int i = findindex(L, t, key); /* find original element */ - for (i++; i < t->sizearray; i++) { /* try first array part */ + unsigned int i = findindex(L, t, key); /* find original element */ + for (; i < t->sizearray; i++) { /* try first array part */ if (!ttisnil(&t->array[i])) { /* a non-nil value? */ - setnvalue(key, cast_num(i+1)); + setivalue(key, i + 1); setobj2s(L, key+1, &t->array[i]); return 1; } } - for (i -= t->sizearray; i < sizenode(t); i++) { /* then hash part */ + for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) { /* hash part */ if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ setobj2s(L, key, gkey(gnode(t, i))); setobj2s(L, key+1, gval(gnode(t, i))); @@ -192,32 +211,38 @@ int luaH_next (lua_State *L, Table *t, StkId key) { ** ============================================================== */ - -static int computesizes (int nums[], int *narray) { +/* +** Compute the optimal size for the array part of table 't'. 'nums' is a +** "count array" where 'nums[i]' is the number of integers in the table +** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of +** integer keys in the table and leaves with the number of keys that +** will go to the array part; return the optimal size. +*/ +static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { int i; - int twotoi; /* 2^i */ - int a = 0; /* number of elements smaller than 2^i */ - int na = 0; /* number of elements to go to array part */ - int n = 0; /* optimal size for array part */ - for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { + unsigned int twotoi; /* 2^i (candidate for optimal size) */ + unsigned int a = 0; /* number of elements smaller than 2^i */ + unsigned int na = 0; /* number of elements to go to array part */ + unsigned int optimal = 0; /* optimal size for array part */ + /* loop while keys can fill more than half of total size */ + for (i = 0, twotoi = 1; *pna > twotoi / 2; i++, twotoi *= 2) { if (nums[i] > 0) { a += nums[i]; if (a > twotoi/2) { /* more than half elements present? */ - n = twotoi; /* optimal size (till now) */ - na = a; /* all elements smaller than n will go to array part */ + optimal = twotoi; /* optimal size (till now) */ + na = a; /* all elements up to 'optimal' will go to array part */ } } - if (a == *narray) break; /* all elements already counted */ } - *narray = n; - lua_assert(*narray/2 <= na && na <= *narray); - return na; + lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal); + *pna = na; + return optimal; } -static int countint (const TValue *key, int *nums) { - int k = arrayindex(key); - if (0 < k && k <= MAXASIZE) { /* is `key' an appropriate array index? */ +static int countint (const TValue *key, unsigned int *nums) { + unsigned int k = arrayindex(key); + if (k != 0) { /* is 'key' an appropriate array index? */ nums[luaO_ceillog2(k)]++; /* count as such */ return 1; } @@ -226,20 +251,26 @@ static int countint (const TValue *key, int *nums) { } -static int numusearray (const Table *t, int *nums) { +/* +** Count keys in array part of table 't': Fill 'nums[i]' with +** number of keys that will go into corresponding slice and return +** total number of non-nil keys. +*/ +static unsigned int numusearray (const Table *t, unsigned int *nums) { int lg; - int ttlg; /* 2^lg */ - int ause = 0; /* summation of `nums' */ - int i = 1; /* count to traverse all array keys */ - for (lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { /* for each slice */ - int lc = 0; /* counter */ - int lim = ttlg; + unsigned int ttlg; /* 2^lg */ + unsigned int ause = 0; /* summation of 'nums' */ + unsigned int i = 1; /* count to traverse all array keys */ + /* traverse each slice */ + for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) { + unsigned int lc = 0; /* counter */ + unsigned int lim = ttlg; if (lim > t->sizearray) { lim = t->sizearray; /* adjust upper limit */ if (i > lim) break; /* no more elements to count */ } - /* count elements in range (2^(lg-1), 2^lg] */ + /* count elements in range (2^(lg - 1), 2^lg] */ for (; i <= lim; i++) { if (!ttisnil(&t->array[i-1])) lc++; @@ -251,9 +282,9 @@ static int numusearray (const Table *t, int *nums) { } -static int numusehash (const Table *t, int *nums, int *pnasize) { +static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) { int totaluse = 0; /* total number of elements */ - int ause = 0; /* summation of `nums' */ + int ause = 0; /* elements added to 'nums' (can go to array part) */ int i = sizenode(t); while (i--) { Node *n = &t->node[i]; @@ -262,13 +293,13 @@ static int numusehash (const Table *t, int *nums, int *pnasize) { totaluse++; } } - *pnasize += ause; + *pna += ause; return totaluse; } -static void setarrayvector (lua_State *L, Table *t, int size) { - int i; +static void setarrayvector (lua_State *L, Table *t, unsigned int size) { + unsigned int i; luaM_reallocvector(L, t->array, t->sizearray, size, TValue); for (i=t->sizearray; iarray[i]); @@ -276,23 +307,23 @@ static void setarrayvector (lua_State *L, Table *t, int size) { } -static void setnodevector (lua_State *L, Table *t, int size) { +static void setnodevector (lua_State *L, Table *t, unsigned int size) { int lsize; if (size == 0) { /* no elements to hash part? */ - t->node = cast(Node *, dummynode); /* use common `dummynode' */ + t->node = cast(Node *, dummynode); /* use common 'dummynode' */ lsize = 0; } else { int i; lsize = luaO_ceillog2(size); - if (lsize > MAXBITS) + if (lsize > MAXHBITS) luaG_runerror(L, "table overflow"); size = twoto(lsize); t->node = luaM_newvector(L, size, Node); - for (i=0; isizearray; +void luaH_resize (lua_State *L, Table *t, unsigned int nasize, + unsigned int nhsize) { + unsigned int i; + int j; + unsigned int oldasize = t->sizearray; int oldhsize = t->lsizenode; Node *nold = t->node; /* save old hash ... */ if (nasize > oldasize) /* array part must grow? */ @@ -321,8 +354,8 @@ void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize) { luaM_reallocvector(L, t->array, oldasize, nasize, TValue); } /* re-insert elements from hash part */ - for (i = twoto(oldhsize) - 1; i >= 0; i--) { - Node *old = nold+i; + for (j = twoto(oldhsize) - 1; j >= 0; j--) { + Node *old = nold + j; if (!ttisnil(gval(old))) { /* doesn't need barrier/invalidate cache, as entry was already present in the table */ @@ -330,32 +363,35 @@ void luaH_resize (lua_State *L, Table *t, int nasize, int nhsize) { } } if (!isdummy(nold)) - luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old array */ + luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old hash */ } -void luaH_resizearray (lua_State *L, Table *t, int nasize) { +void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) { int nsize = isdummy(t->node) ? 0 : sizenode(t); luaH_resize(L, t, nasize, nsize); } - +/* +** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i +*/ static void rehash (lua_State *L, Table *t, const TValue *ek) { - int nasize, na; - int nums[MAXBITS+1]; /* nums[i] = number of keys with 2^(i-1) < k <= 2^i */ + unsigned int asize; /* optimal size for array part */ + unsigned int na; /* number of keys in the array part */ + unsigned int nums[MAXABITS + 1]; int i; int totaluse; - for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */ - nasize = numusearray(t, nums); /* count keys in array part */ - totaluse = nasize; /* all those keys are integer keys */ - totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ + for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */ + na = numusearray(t, nums); /* count keys in array part */ + totaluse = na; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &na); /* count keys in hash part */ /* count extra key */ - nasize += countint(ek, nums); + na += countint(ek, nums); totaluse++; /* compute new size for array part */ - na = computesizes(nums, &nasize); + asize = computesizes(nums, &na); /* resize the table to new computed sizes */ - luaH_resize(L, t, nasize, totaluse - na); + luaH_resize(L, t, asize, totaluse - na); } @@ -366,7 +402,8 @@ static void rehash (lua_State *L, Table *t, const TValue *ek) { Table *luaH_new (lua_State *L) { - Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h; + GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table)); + Table *t = gco2t(o); t->metatable = NULL; t->flags = cast_byte(~0); t->array = NULL; @@ -404,37 +441,51 @@ static Node *getfreepos (Table *t) { */ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { Node *mp; + TValue aux; if (ttisnil(key)) luaG_runerror(L, "table index is nil"); - else if (ttisnumber(key) && luai_numisnan(L, nvalue(key))) - luaG_runerror(L, "table index is NaN"); + else if (ttisfloat(key)) { + lua_Integer k; + if (luaV_tointeger(key, &k, 0)) { /* index is int? */ + setivalue(&aux, k); + key = &aux; /* insert it as an integer */ + } + else if (luai_numisnan(fltvalue(key))) + luaG_runerror(L, "table index is NaN"); + } mp = mainposition(t, key); if (!ttisnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */ Node *othern; - Node *n = getfreepos(t); /* get a free place */ - if (n == NULL) { /* cannot find a free place? */ + Node *f = getfreepos(t); /* get a free place */ + if (f == NULL) { /* cannot find a free place? */ rehash(L, t, key); /* grow table */ - /* whatever called 'newkey' take care of TM cache and GC barrier */ + /* whatever called 'newkey' takes care of TM cache */ return luaH_set(L, t, key); /* insert key into grown table */ } - lua_assert(!isdummy(n)); + lua_assert(!isdummy(f)); othern = mainposition(t, gkey(mp)); if (othern != mp) { /* is colliding node out of its main position? */ /* yes; move colliding node into free position */ - while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ - gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ - *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ - gnext(mp) = NULL; /* now `mp' is free */ + while (othern + gnext(othern) != mp) /* find previous */ + othern += gnext(othern); + gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */ + *f = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + if (gnext(mp) != 0) { + gnext(f) += cast_int(mp - f); /* correct 'next' */ + gnext(mp) = 0; /* now 'mp' is free */ + } setnilvalue(gval(mp)); } else { /* colliding node is in its own main position */ /* new node will go into free position */ - gnext(n) = gnext(mp); /* chain new position */ - gnext(mp) = n; - mp = n; + if (gnext(mp) != 0) + gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */ + else lua_assert(gnext(f) == 0); + gnext(mp) = cast_int(f - mp); + mp = f; } } - setobj2t(L, gkey(mp), key); - luaC_barrierback(L, obj2gco(t), key); + setnodekey(L, &mp->i_key, key); + luaC_barrierback(L, t, key); lua_assert(ttisnil(gval(mp))); return gval(mp); } @@ -443,18 +494,21 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { /* ** search function for integers */ -const TValue *luaH_getint (Table *t, int key) { +const TValue *luaH_getint (Table *t, lua_Integer key) { /* (1 <= key && key <= t->sizearray) */ - if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) - return &t->array[key-1]; + if (l_castS2U(key) - 1 < t->sizearray) + return &t->array[key - 1]; else { - lua_Number nk = cast_num(key); - Node *n = hashnum(t, nk); - do { /* check whether `key' is somewhere in the chain */ - if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) + Node *n = hashint(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (ttisinteger(gkey(n)) && ivalue(gkey(n)) == key) return gval(n); /* that's it */ - else n = gnext(n); - } while (n); + else { + int nx = gnext(n); + if (nx == 0) break; + n += nx; + } + } return luaO_nilobject; } } @@ -463,15 +517,50 @@ const TValue *luaH_getint (Table *t, int key) { /* ** search function for short strings */ -const TValue *luaH_getstr (Table *t, TString *key) { +const TValue *luaH_getshortstr (Table *t, TString *key) { Node *n = hashstr(t, key); - lua_assert(key->tsv.tt == LUA_TSHRSTR); - do { /* check whether `key' is somewhere in the chain */ - if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), key)) + lua_assert(key->tt == LUA_TSHRSTR); + for (;;) { /* check whether 'key' is somewhere in the chain */ + const TValue *k = gkey(n); + if (ttisshrstring(k) && eqshrstr(tsvalue(k), key)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return luaO_nilobject; /* not found */ + n += nx; + } + } +} + + +/* +** "Generic" get version. (Not that generic: not valid for integers, +** which may be in array part, nor for floats with integral values.) +*/ +static const TValue *getgeneric (Table *t, const TValue *key) { + Node *n = mainposition(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (luaV_rawequalobj(gkey(n), key)) return gval(n); /* that's it */ - else n = gnext(n); - } while (n); - return luaO_nilobject; + else { + int nx = gnext(n); + if (nx == 0) + return luaO_nilobject; /* not found */ + n += nx; + } + } +} + + +const TValue *luaH_getstr (Table *t, TString *key) { + if (key->tt == LUA_TSHRSTR) + return luaH_getshortstr(t, key); + else { /* for long strings, use generic case */ + TValue ko; + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko); + } } @@ -480,25 +569,17 @@ const TValue *luaH_getstr (Table *t, TString *key) { */ const TValue *luaH_get (Table *t, const TValue *key) { switch (ttype(key)) { - case LUA_TSHRSTR: return luaH_getstr(t, rawtsvalue(key)); + case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key)); + case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); case LUA_TNIL: return luaO_nilobject; - case LUA_TNUMBER: { - int k; - lua_Number n = nvalue(key); - lua_number2int(k, n); - if (luai_numeq(cast_num(k), n)) /* index is int? */ + case LUA_TNUMFLT: { + lua_Integer k; + if (luaV_tointeger(key, &k, 0)) /* index is int? */ return luaH_getint(t, k); /* use specialized version */ - /* else go through */ - } - default: { - Node *n = mainposition(t, key); - do { /* check whether `key' is somewhere in the chain */ - if (luaV_rawequalobj(gkey(n), key)) - return gval(n); /* that's it */ - else n = gnext(n); - } while (n); - return luaO_nilobject; - } + /* else... */ + } /* FALLTHROUGH */ + default: + return getgeneric(t, key); } } @@ -515,14 +596,14 @@ TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { } -void luaH_setint (lua_State *L, Table *t, int key, TValue *value) { +void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { const TValue *p = luaH_getint(t, key); TValue *cell; if (p != luaO_nilobject) cell = cast(TValue *, p); else { TValue k; - setnvalue(&k, cast_num(key)); + setivalue(&k, key); cell = luaH_newkey(L, t, &k); } setobj2t(L, cell, value); @@ -532,16 +613,16 @@ void luaH_setint (lua_State *L, Table *t, int key, TValue *value) { static int unbound_search (Table *t, unsigned int j) { unsigned int i = j; /* i is zero or a present index */ j++; - /* find `i' and `j' such that i is present and j is not */ + /* find 'i' and 'j' such that i is present and j is not */ while (!ttisnil(luaH_getint(t, j))) { i = j; - j *= 2; - if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ + if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */ /* table was built with bad purposes: resort to linear search */ i = 1; while (!ttisnil(luaH_getint(t, i))) i++; return i - 1; } + j *= 2; } /* now do a binary search between them */ while (j - i > 1) { @@ -554,7 +635,7 @@ static int unbound_search (Table *t, unsigned int j) { /* -** Try to find a boundary in table `t'. A `boundary' is an integer index +** Try to find a boundary in table 't'. A 'boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ int luaH_getn (Table *t) { diff --git a/depends/lua/src/ltablib.c b/depends/lua/src/ltablib.c index 6001224e3..98b2f8713 100644 --- a/depends/lua/src/ltablib.c +++ b/depends/lua/src/ltablib.c @@ -1,23 +1,61 @@ /* -** $Id: ltablib.c,v 1.65.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltablib.c,v 1.93 2016/02/25 19:41:54 roberto Exp $ ** Library for Table Manipulation ** See Copyright Notice in lua.h */ - -#include - #define ltablib_c #define LUA_LIB +#include "lprefix.h" + + +#include +#include +#include + #include "lua.h" #include "lauxlib.h" #include "lualib.h" -#define aux_getn(L,n) (luaL_checktype(L, n, LUA_TTABLE), luaL_len(L, n)) +/* +** Operations that an object must define to mimic a table +** (some functions only need some of them) +*/ +#define TAB_R 1 /* read */ +#define TAB_W 2 /* write */ +#define TAB_L 4 /* length */ +#define TAB_RW (TAB_R | TAB_W) /* read/write */ + + +#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) + +static int checkfield (lua_State *L, const char *key, int n) { + lua_pushstring(L, key); + return (lua_rawget(L, -n) != LUA_TNIL); +} + + +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +static void checktab (lua_State *L, int arg, int what) { + if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int n = 1; /* number of elements to pop */ + if (lua_getmetatable(L, arg) && /* must have metatable */ + (!(what & TAB_R) || checkfield(L, "__index", ++n)) && + (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && + (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + lua_pop(L, n); /* pop metatable and tested metamethods */ + } + else + luaL_checktype(L, arg, LUA_TTABLE); /* force an error */ + } +} #if defined(LUA_COMPAT_MAXN) @@ -39,65 +77,102 @@ static int maxn (lua_State *L) { static int tinsert (lua_State *L) { - int e = aux_getn(L, 1) + 1; /* first empty element */ - int pos; /* where to insert new element */ + lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */ + lua_Integer pos; /* where to insert new element */ switch (lua_gettop(L)) { case 2: { /* called with only 2 arguments */ pos = e; /* insert new element at the end */ break; } case 3: { - int i; - pos = luaL_checkint(L, 2); /* 2nd argument is the position */ + lua_Integer i; + pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); for (i = e; i > pos; i--) { /* move up elements */ - lua_rawgeti(L, 1, i-1); - lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ + lua_geti(L, 1, i - 1); + lua_seti(L, 1, i); /* t[i] = t[i - 1] */ } break; } default: { - return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); + return luaL_error(L, "wrong number of arguments to 'insert'"); } } - lua_rawseti(L, 1, pos); /* t[pos] = v */ + lua_seti(L, 1, pos); /* t[pos] = v */ return 0; } static int tremove (lua_State *L) { - int size = aux_getn(L, 1); - int pos = luaL_optint(L, 2, size); + lua_Integer size = aux_getn(L, 1, TAB_RW); + lua_Integer pos = luaL_optinteger(L, 2, size); if (pos != size) /* validate 'pos' if given */ luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); - lua_rawgeti(L, 1, pos); /* result = t[pos] */ + lua_geti(L, 1, pos); /* result = t[pos] */ for ( ; pos < size; pos++) { - lua_rawgeti(L, 1, pos+1); - lua_rawseti(L, 1, pos); /* t[pos] = t[pos+1] */ + lua_geti(L, 1, pos + 1); + lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ } lua_pushnil(L); - lua_rawseti(L, 1, pos); /* t[pos] = nil */ + lua_seti(L, 1, pos); /* t[pos] = nil */ return 1; } -static void addfield (lua_State *L, luaL_Buffer *b, int i) { - lua_rawgeti(L, 1, i); +/* +** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever +** possible, copy in increasing order, which is better for rehashing. +** "possible" means destination after original range, or smaller +** than origin, or copying to another table. +*/ +static int tmove (lua_State *L) { + lua_Integer f = luaL_checkinteger(L, 2); + lua_Integer e = luaL_checkinteger(L, 3); + lua_Integer t = luaL_checkinteger(L, 4); + int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + checktab(L, 1, TAB_R); + checktab(L, tt, TAB_W); + if (e >= f) { /* otherwise, nothing to move */ + lua_Integer n, i; + luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, + "too many elements to move"); + n = e - f + 1; /* number of elements to move */ + luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, + "destination wrap around"); + if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) { + for (i = 0; i < n; i++) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + else { + for (i = n - 1; i >= 0; i--) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + } + lua_pushvalue(L, tt); /* return destination table */ + return 1; +} + + +static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) { + lua_geti(L, 1, i); if (!lua_isstring(L, -1)) - luaL_error(L, "invalid value (%s) at index %d in table for " - LUA_QL("concat"), luaL_typename(L, -1), i); + luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", + luaL_typename(L, -1), i); luaL_addvalue(b); } static int tconcat (lua_State *L) { luaL_Buffer b; + lua_Integer last = aux_getn(L, 1, TAB_R); size_t lsep; - int i, last; const char *sep = luaL_optlstring(L, 2, "", &lsep); - luaL_checktype(L, 1, LUA_TTABLE); - i = luaL_optint(L, 3, 1); - last = luaL_opt(L, luaL_checkint, 4, luaL_len(L, 1)); + lua_Integer i = luaL_optinteger(L, 3, 1); + last = luaL_optinteger(L, 4, last); luaL_buffinit(L, &b); for (; i < last; i++) { addfield(L, &b, i); @@ -117,35 +192,31 @@ static int tconcat (lua_State *L) { */ static int pack (lua_State *L) { + int i; int n = lua_gettop(L); /* number of elements to pack */ lua_createtable(L, n, 1); /* create result table */ + lua_insert(L, 1); /* put it at index 1 */ + for (i = n; i >= 1; i--) /* assign elements */ + lua_seti(L, 1, i); lua_pushinteger(L, n); - lua_setfield(L, -2, "n"); /* t.n = number of elements */ - if (n > 0) { /* at least one element? */ - int i; - lua_pushvalue(L, 1); - lua_rawseti(L, -2, 1); /* insert first element */ - lua_replace(L, 1); /* move table into index 1 */ - for (i = n; i >= 2; i--) /* assign other elements */ - lua_rawseti(L, 1, i); - } + lua_setfield(L, 1, "n"); /* t.n = number of elements */ return 1; /* return table */ } static int unpack (lua_State *L) { - int i, e, n; - luaL_checktype(L, 1, LUA_TTABLE); - i = luaL_optint(L, 2, 1); - e = luaL_opt(L, luaL_checkint, 3, luaL_len(L, 1)); + lua_Unsigned n; + lua_Integer i = luaL_optinteger(L, 2, 1); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); if (i > e) return 0; /* empty range */ - n = e - i + 1; /* number of elements */ - if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ + n = (lua_Unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ + if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n))) return luaL_error(L, "too many results to unpack"); - lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ - while (i++ < e) /* push arg[i + 1...e] */ - lua_rawgeti(L, 1, i); - return n; + for (; i < e; i++) { /* push arg[i..e - 1] (to avoid overflows) */ + lua_geti(L, 1, i); + } + lua_geti(L, 1, e); /* push last element */ + return (int)n; } /* }====================================================== */ @@ -155,102 +226,197 @@ static int unpack (lua_State *L) { /* ** {====================================================== ** Quicksort -** (based on `Algorithms in MODULA-3', Robert Sedgewick; +** (based on 'Algorithms in MODULA-3', Robert Sedgewick; ** Addison-Wesley, 1993.) ** ======================================================= */ -static void set2 (lua_State *L, int i, int j) { - lua_rawseti(L, 1, i); - lua_rawseti(L, 1, j); +/* type for array indices */ +typedef unsigned int IdxT; + + +/* +** Produce a "random" 'unsigned int' to randomize pivot choice. This +** macro is used only when 'sort' detects a big imbalance in the result +** of a partition. (If you don't want/need this "randomness", ~0 is a +** good choice.) +*/ +#if !defined(l_randomizePivot) /* { */ + +#include + +/* size of 'e' measured in number of 'unsigned int's */ +#define sof(e) (sizeof(e) / sizeof(unsigned int)) + +/* +** Use 'time' and 'clock' as sources of "randomness". Because we don't +** know the types 'clock_t' and 'time_t', we cannot cast them to +** anything without risking overflows. A safe way to use their values +** is to copy them to an array of a known type and use the array values. +*/ +static unsigned int l_randomizePivot (void) { + clock_t c = clock(); + time_t t = time(NULL); + unsigned int buff[sof(c) + sof(t)]; + unsigned int i, rnd = 0; + memcpy(buff, &c, sof(c) * sizeof(unsigned int)); + memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int)); + for (i = 0; i < sof(buff); i++) + rnd += buff[i]; + return rnd; +} + +#endif /* } */ + + +/* arrays larger than 'RANLIMIT' may use randomized pivots */ +#define RANLIMIT 100u + + +static void set2 (lua_State *L, IdxT i, IdxT j) { + lua_seti(L, 1, i); + lua_seti(L, 1, j); } + +/* +** Return true iff value at stack index 'a' is less than the value at +** index 'b' (according to the order of the sort). +*/ static int sort_comp (lua_State *L, int a, int b) { - if (!lua_isnil(L, 2)) { /* function? */ + if (lua_isnil(L, 2)) /* no function? */ + return lua_compare(L, a, b, LUA_OPLT); /* a < b */ + else { /* function */ int res; - lua_pushvalue(L, 2); + lua_pushvalue(L, 2); /* push function */ lua_pushvalue(L, a-1); /* -1 to compensate function */ - lua_pushvalue(L, b-2); /* -2 to compensate function and `a' */ - lua_call(L, 2, 1); - res = lua_toboolean(L, -1); - lua_pop(L, 1); + lua_pushvalue(L, b-2); /* -2 to compensate function and 'a' */ + lua_call(L, 2, 1); /* call function */ + res = lua_toboolean(L, -1); /* get result */ + lua_pop(L, 1); /* pop result */ return res; } - else /* a < b? */ - return lua_compare(L, a, b, LUA_OPLT); } -static void auxsort (lua_State *L, int l, int u) { - while (l < u) { /* for tail recursion */ - int i, j; - /* sort elements a[l], a[(l+u)/2] and a[u] */ - lua_rawgeti(L, 1, l); - lua_rawgeti(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ - set2(L, l, u); /* swap a[l] - a[u] */ + +/* +** Does the partition: Pivot P is at the top of the stack. +** precondition: a[lo] <= P == a[up-1] <= a[up], +** so it only needs to do the partition from lo + 1 to up - 2. +** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up] +** returns 'i'. +*/ +static IdxT partition (lua_State *L, IdxT lo, IdxT up) { + IdxT i = lo; /* will be incremented before first use */ + IdxT j = up - 1; /* will be decremented before first use */ + /* loop invariant: a[lo .. i] <= P <= a[j .. up] */ + for (;;) { + /* next loop: repeat ++i while a[i] < P */ + while (lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (i == up - 1) /* a[i] < P but a[up - 1] == P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* after the loop, a[i] >= P and a[lo .. i - 1] < P */ + /* next loop: repeat --j while P < a[j] */ + while (lua_geti(L, 1, --j), sort_comp(L, -3, -1)) { + if (j < i) /* j < i but a[j] > P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */ + if (j < i) { /* no elements out of place? */ + /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */ + lua_pop(L, 1); /* pop a[j] */ + /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */ + set2(L, up - 1, i); + return i; + } + /* otherwise, swap a[i] - a[j] to restore invariant and repeat */ + set2(L, i, j); + } +} + + +/* +** Choose an element in the middle (2nd-3th quarters) of [lo,up] +** "randomized" by 'rnd' +*/ +static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) { + IdxT r4 = (up - lo) / 4; /* range/4 */ + IdxT p = rnd % (r4 * 2) + (lo + r4); + lua_assert(lo + r4 <= p && p <= up - r4); + return p; +} + + +/* +** QuickSort algorithm (recursive function) +*/ +static void auxsort (lua_State *L, IdxT lo, IdxT up, + unsigned int rnd) { + while (lo < up) { /* loop for tail recursion */ + IdxT p; /* Pivot index */ + IdxT n; /* to be used later */ + /* sort elements 'lo', 'p', and 'up' */ + lua_geti(L, 1, lo); + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[lo]? */ + set2(L, lo, up); /* swap a[lo] - a[up] */ else - lua_pop(L, 2); - if (u-l == 1) break; /* only 2 elements */ - i = (l+u)/2; - lua_rawgeti(L, 1, i); - lua_rawgeti(L, 1, l); - if (sort_comp(L, -2, -1)) /* a[i]= P */ - while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { - if (i>=u) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[i] */ - } - /* repeat --j until a[j] <= P */ - while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { - if (j<=l) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[j] */ - } - if (j n) /* partition too imbalanced? */ + rnd = l_randomizePivot(); /* try a new randomization */ + } /* tail call auxsort(L, lo, up, rnd) */ } + static int sort (lua_State *L) { - int n = aux_getn(L, 1); - luaL_checkstack(L, 40, ""); /* assume array is smaller than 2^40 */ - if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ - luaL_checktype(L, 2, LUA_TFUNCTION); - lua_settop(L, 2); /* make sure there is two arguments */ - auxsort(L, 1, n); + lua_Integer n = aux_getn(L, 1, TAB_RW); + if (n > 1) { /* non-trivial interval? */ + luaL_argcheck(L, n < INT_MAX, 1, "array too big"); + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); /* must be a function */ + lua_settop(L, 2); /* make sure there are two arguments */ + auxsort(L, 1, (IdxT)n, 0); + } return 0; } @@ -266,6 +432,7 @@ static const luaL_Reg tab_funcs[] = { {"pack", pack}, {"unpack", unpack}, {"remove", tremove}, + {"move", tmove}, {"sort", sort}, {NULL, NULL} }; diff --git a/depends/lua/src/ltm.c b/depends/lua/src/ltm.c index 69b4ed772..4650cc293 100644 --- a/depends/lua/src/ltm.c +++ b/depends/lua/src/ltm.c @@ -1,22 +1,27 @@ /* -** $Id: ltm.c,v 2.14.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: ltm.c,v 2.37 2016/02/26 19:20:15 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ - -#include - #define ltm_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" +#include "ldebug.h" +#include "ldo.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" +#include "lvm.h" static const char udatatypename[] = "userdata"; @@ -25,7 +30,7 @@ LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTAGS] = { "no value", "nil", "boolean", udatatypename, "number", "string", "table", "function", udatatypename, "thread", - "proto", "upval" /* these last two cases are used for tests only */ + "proto" /* this last case is used for tests only */ }; @@ -33,14 +38,16 @@ void luaT_init (lua_State *L) { static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__gc", "__mode", "__len", "__eq", - "__add", "__sub", "__mul", "__div", "__mod", - "__pow", "__unm", "__lt", "__le", + "__add", "__sub", "__mul", "__mod", "__pow", + "__div", "__idiv", + "__band", "__bor", "__bxor", "__shl", "__shr", + "__unm", "__bnot", "__lt", "__le", "__concat", "__call" }; int i; for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); - luaS_fix(G(L)->tmname[i]); /* never collect these names */ + luaC_fix(L, obj2gco(G(L)->tmname[i])); /* never collect these names */ } } @@ -50,7 +57,7 @@ void luaT_init (lua_State *L) { ** tag methods */ const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { - const TValue *tm = luaH_getstr(events, ename); + const TValue *tm = luaH_getshortstr(events, ename); lua_assert(event <= TM_EQ); if (ttisnil(tm)) { /* no tag method? */ events->flags |= cast_byte(1u<metatable; break; @@ -70,8 +77,89 @@ const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) { mt = uvalue(o)->metatable; break; default: - mt = G(L)->mt[ttypenv(o)]; + mt = G(L)->mt[ttnov(o)]; + } + return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : luaO_nilobject); +} + + +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const char *luaT_objtypename (lua_State *L, const TValue *o) { + Table *mt; + if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) || + (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) { + const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name")); + if (ttisstring(name)) /* is '__name' a string? */ + return getstr(tsvalue(name)); /* use it as type name */ } - return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); + return ttypename(ttnov(o)); /* else use standard type name */ +} + + +void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, TValue *p3, int hasres) { + ptrdiff_t result = savestack(L, p3); + StkId func = L->top; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + L->top += 3; + if (!hasres) /* no result? 'p3' is third argument */ + setobj2s(L, L->top++, p3); /* 3rd argument */ + /* metamethod may yield only when called from Lua code */ + if (isLua(L->ci)) + luaD_call(L, func, hasres); + else + luaD_callnoyield(L, func, hasres); + if (hasres) { /* if has result, move it to its place */ + p3 = restorestack(L, result); + setobjs2s(L, p3, --L->top); + } +} + + +int luaT_callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + if (ttisnil(tm)) + tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + if (ttisnil(tm)) return 0; + luaT_callTM(L, tm, p1, p2, res, 1); + return 1; +} + + +void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + if (!luaT_callbinTM(L, p1, p2, res, event)) { + switch (event) { + case TM_CONCAT: + luaG_concaterror(L, p1, p2); + /* call never returns, but to avoid warnings: *//* FALLTHROUGH */ + case TM_BAND: case TM_BOR: case TM_BXOR: + case TM_SHL: case TM_SHR: case TM_BNOT: { + lua_Number dummy; + if (tonumber(p1, &dummy) && tonumber(p2, &dummy)) + luaG_tointerror(L, p1, p2); + else + luaG_opinterror(L, p1, p2, "perform bitwise operation on"); + } + /* calls never return, but to avoid warnings: *//* FALLTHROUGH */ + default: + luaG_opinterror(L, p1, p2, "perform arithmetic on"); + } + } +} + + +int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2, + TMS event) { + if (!luaT_callbinTM(L, p1, p2, L->top, event)) + return -1; /* no metamethod */ + else + return !l_isfalse(L->top); } diff --git a/depends/lua/src/lua.c b/depends/lua/src/lua.c index 4345e554e..545d23d49 100644 --- a/depends/lua/src/lua.c +++ b/depends/lua/src/lua.c @@ -1,17 +1,19 @@ /* -** $Id: lua.c,v 1.206.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lua.c,v 1.226 2015/08/14 19:11:20 roberto Exp $ ** Lua stand-alone interpreter ** See Copyright Notice in lua.h */ +#define lua_c + +#include "lprefix.h" + #include #include #include #include -#define lua_c - #include "lua.h" #include "lauxlib.h" @@ -31,28 +33,38 @@ #define LUA_MAXINPUT 512 #endif -#if !defined(LUA_INIT) -#define LUA_INIT "LUA_INIT" +#if !defined(LUA_INIT_VAR) +#define LUA_INIT_VAR "LUA_INIT" #endif -#define LUA_INITVERSION \ - LUA_INIT "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR +#define LUA_INITVARVERSION \ + LUA_INIT_VAR "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR /* ** lua_stdin_is_tty detects whether the standard input is a 'tty' (that ** is, whether we're running lua interactively). */ -#if defined(LUA_USE_ISATTY) +#if !defined(lua_stdin_is_tty) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + #include #define lua_stdin_is_tty() isatty(0) -#elif defined(LUA_WIN) + +#elif defined(LUA_USE_WINDOWS) /* }{ */ + #include -#include #define lua_stdin_is_tty() _isatty(_fileno(stdin)) -#else + +#else /* }{ */ + +/* ISO C definition */ #define lua_stdin_is_tty() 1 /* assume stdin is a tty */ -#endif + +#endif /* } */ + +#endif /* } */ /* @@ -61,26 +73,27 @@ ** lua_saveline defines how to "save" a read line in a "history". ** lua_freeline defines how to free a line read by lua_readline. */ -#if defined(LUA_USE_READLINE) +#if !defined(lua_readline) /* { */ + +#if defined(LUA_USE_READLINE) /* { */ -#include #include #include #define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) -#define lua_saveline(L,idx) \ - if (lua_rawlen(L,idx) > 0) /* non-empty line? */ \ - add_history(lua_tostring(L, idx)); /* add it to history */ +#define lua_saveline(L,line) ((void)L, add_history(line)) #define lua_freeline(L,b) ((void)L, free(b)) -#elif !defined(lua_readline) +#else /* }{ */ #define lua_readline(L,b,p) \ ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ -#define lua_saveline(L,idx) { (void)L; (void)idx; } +#define lua_saveline(L,line) { (void)L; (void)line; } #define lua_freeline(L,b) { (void)L; (void)b; } -#endif +#endif /* } */ + +#endif /* } */ @@ -90,33 +103,40 @@ static lua_State *globalL = NULL; static const char *progname = LUA_PROGNAME; - +/* +** Hook set by signal function to stop the interpreter. +*/ static void lstop (lua_State *L, lua_Debug *ar) { (void)ar; /* unused arg. */ - lua_sethook(L, NULL, 0, 0); + lua_sethook(L, NULL, 0, 0); /* reset hook */ luaL_error(L, "interrupted!"); } +/* +** Function to be called at a C signal. Because a C signal cannot +** just change a Lua state (as there is no proper synchronization), +** this function only sets a hook that, when called, will stop the +** interpreter. +*/ static void laction (int i) { - signal(i, SIG_DFL); /* if another SIGINT happens before lstop, - terminate process (default action) */ + signal(i, SIG_DFL); /* if another SIGINT happens, terminate process */ lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } static void print_usage (const char *badoption) { - luai_writestringerror("%s: ", progname); + lua_writestringerror("%s: ", progname); if (badoption[1] == 'e' || badoption[1] == 'l') - luai_writestringerror("'%s' needs argument\n", badoption); + lua_writestringerror("'%s' needs argument\n", badoption); else - luai_writestringerror("unrecognized option '%s'\n", badoption); - luai_writestringerror( + lua_writestringerror("unrecognized option '%s'\n", badoption); + lua_writestringerror( "usage: %s [options] [script [args]]\n" "Available options are:\n" - " -e stat execute string " LUA_QL("stat") "\n" - " -i enter interactive mode after executing " LUA_QL("script") "\n" - " -l name require library " LUA_QL("name") "\n" + " -e stat execute string 'stat'\n" + " -i enter interactive mode after executing 'script'\n" + " -l name require library 'name'\n" " -v show version information\n" " -E ignore environment variables\n" " -- stop handling options\n" @@ -126,101 +146,114 @@ static void print_usage (const char *badoption) { } +/* +** Prints an error message, adding the program name in front of it +** (if present) +*/ static void l_message (const char *pname, const char *msg) { - if (pname) luai_writestringerror("%s: ", pname); - luai_writestringerror("%s\n", msg); + if (pname) lua_writestringerror("%s: ", pname); + lua_writestringerror("%s\n", msg); } +/* +** Check whether 'status' is not OK and, if so, prints the error +** message on the top of the stack. It assumes that the error object +** is a string, as it was either generated by Lua or by 'msghandler'. +*/ static int report (lua_State *L, int status) { - if (status != LUA_OK && !lua_isnil(L, -1)) { + if (status != LUA_OK) { const char *msg = lua_tostring(L, -1); - if (msg == NULL) msg = "(error object is not a string)"; l_message(progname, msg); - lua_pop(L, 1); - /* force a complete garbage collection in case of errors */ - lua_gc(L, LUA_GCCOLLECT, 0); + lua_pop(L, 1); /* remove message */ } return status; } -/* the next function is called unprotected, so it must avoid errors */ -static void finalreport (lua_State *L, int status) { - if (status != LUA_OK) { - const char *msg = (lua_type(L, -1) == LUA_TSTRING) ? lua_tostring(L, -1) - : NULL; - if (msg == NULL) msg = "(error object is not a string)"; - l_message(progname, msg); - lua_pop(L, 1); - } -} - - -static int traceback (lua_State *L) { +/* +** Message handler used to run all chunks +*/ +static int msghandler (lua_State *L) { const char *msg = lua_tostring(L, 1); - if (msg) - luaL_traceback(L, L, msg, 1); - else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ - if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ - lua_pushliteral(L, "(no error message)"); + if (msg == NULL) { /* is error object not a string? */ + if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */ + lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */ + return 1; /* that is the message */ + else + msg = lua_pushfstring(L, "(error object is a %s value)", + luaL_typename(L, 1)); } - return 1; + luaL_traceback(L, L, msg, 1); /* append a standard traceback */ + return 1; /* return the traceback */ } +/* +** Interface to 'lua_pcall', which sets appropriate message function +** and C-signal handler. Used to run all chunks. +*/ static int docall (lua_State *L, int narg, int nres) { int status; int base = lua_gettop(L) - narg; /* function index */ - lua_pushcfunction(L, traceback); /* push traceback function */ - lua_insert(L, base); /* put it under chunk and args */ + lua_pushcfunction(L, msghandler); /* push message handler */ + lua_insert(L, base); /* put it under function and args */ globalL = L; /* to be available to 'laction' */ - signal(SIGINT, laction); + signal(SIGINT, laction); /* set C-signal handler */ status = lua_pcall(L, narg, nres, base); - signal(SIGINT, SIG_DFL); - lua_remove(L, base); /* remove traceback function */ + signal(SIGINT, SIG_DFL); /* reset C-signal handler */ + lua_remove(L, base); /* remove message handler from the stack */ return status; } static void print_version (void) { - luai_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); - luai_writeline(); + lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); + lua_writeline(); } -static int getargs (lua_State *L, char **argv, int n) { - int narg; - int i; - int argc = 0; - while (argv[argc]) argc++; /* count total number of arguments */ - narg = argc - (n + 1); /* number of arguments to the script */ - luaL_checkstack(L, narg + 3, "too many arguments to script"); - for (i=n+1; i < argc; i++) - lua_pushstring(L, argv[i]); - lua_createtable(L, narg, n + 1); - for (i=0; i < argc; i++) { +/* +** Create the 'arg' table, which stores all arguments from the +** command line ('argv'). It should be aligned so that, at index 0, +** it has 'argv[script]', which is the script name. The arguments +** to the script (everything after 'script') go to positive indices; +** other arguments (before the script name) go to negative indices. +** If there is no script name, assume interpreter's name as base. +*/ +static void createargtable (lua_State *L, char **argv, int argc, int script) { + int i, narg; + if (script == argc) script = 0; /* no script name? */ + narg = argc - (script + 1); /* number of positive indices */ + lua_createtable(L, narg, script + 1); + for (i = 0; i < argc; i++) { lua_pushstring(L, argv[i]); - lua_rawseti(L, -2, i - n); + lua_rawseti(L, -2, i - script); } - return narg; + lua_setglobal(L, "arg"); } -static int dofile (lua_State *L, const char *name) { - int status = luaL_loadfile(L, name); +static int dochunk (lua_State *L, int status) { if (status == LUA_OK) status = docall(L, 0, 0); return report(L, status); } +static int dofile (lua_State *L, const char *name) { + return dochunk(L, luaL_loadfile(L, name)); +} + + static int dostring (lua_State *L, const char *s, const char *name) { - int status = luaL_loadbuffer(L, s, strlen(s), name); - if (status == LUA_OK) status = docall(L, 0, 0); - return report(L, status); + return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name)); } +/* +** Calls 'require(name)' and stores the result in a global variable +** with the given name. +*/ static int dolibrary (lua_State *L, const char *name) { int status; lua_getglobal(L, "require"); @@ -232,6 +265,9 @@ static int dolibrary (lua_State *L, const char *name) { } +/* +** Returns the string to be used as a prompt by the interpreter. +*/ static const char *get_prompt (lua_State *L, int firstline) { const char *p; lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2"); @@ -244,6 +280,12 @@ static const char *get_prompt (lua_State *L, int firstline) { #define EOFMARK "" #define marklen (sizeof(EOFMARK)/sizeof(char) - 1) + +/* +** Check whether 'status' signals a syntax error and the error +** message at the top of the stack ends with the above mark for +** incomplete statements. +*/ static int incomplete (lua_State *L, int status) { if (status == LUA_ERRSYNTAX) { size_t lmsg; @@ -257,163 +299,230 @@ static int incomplete (lua_State *L, int status) { } +/* +** Prompt the user, read a line, and push it into the Lua stack. +*/ static int pushline (lua_State *L, int firstline) { char buffer[LUA_MAXINPUT]; char *b = buffer; size_t l; const char *prmt = get_prompt(L, firstline); int readstatus = lua_readline(L, b, prmt); - lua_pop(L, 1); /* remove result from 'get_prompt' */ if (readstatus == 0) - return 0; /* no input */ + return 0; /* no input (prompt will be popped by caller) */ + lua_pop(L, 1); /* remove prompt */ l = strlen(b); if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ - b[l-1] = '\0'; /* remove it */ - if (firstline && b[0] == '=') /* first line starts with `=' ? */ - lua_pushfstring(L, "return %s", b+1); /* change it to `return' */ + b[--l] = '\0'; /* remove it */ + if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */ + lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */ else - lua_pushstring(L, b); + lua_pushlstring(L, b, l); lua_freeline(L, b); return 1; } +/* +** Try to compile line on the stack as 'return ;'; on return, stack +** has either compiled chunk or original line (if compilation failed). +*/ +static int addreturn (lua_State *L) { + const char *line = lua_tostring(L, -1); /* original line */ + const char *retline = lua_pushfstring(L, "return %s;", line); + int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin"); + if (status == LUA_OK) { + lua_remove(L, -2); /* remove modified line */ + if (line[0] != '\0') /* non empty? */ + lua_saveline(L, line); /* keep history */ + } + else + lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */ + return status; +} + + +/* +** Read multiple lines until a complete Lua statement +*/ +static int multiline (lua_State *L) { + for (;;) { /* repeat until gets a complete statement */ + size_t len; + const char *line = lua_tolstring(L, 1, &len); /* get what it has */ + int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */ + if (!incomplete(L, status) || !pushline(L, 0)) { + lua_saveline(L, line); /* keep history */ + return status; /* cannot or should not try to add continuation line */ + } + lua_pushliteral(L, "\n"); /* add newline... */ + lua_insert(L, -2); /* ...between the two lines */ + lua_concat(L, 3); /* join them */ + } +} + + +/* +** Read a line and try to load (compile) it first as an expression (by +** adding "return " in front of it) and second as a statement. Return +** the final status of load/call with the resulting function (if any) +** in the top of the stack. +*/ static int loadline (lua_State *L) { int status; lua_settop(L, 0); if (!pushline(L, 1)) return -1; /* no input */ - for (;;) { /* repeat until gets a complete line */ - size_t l; - const char *line = lua_tolstring(L, 1, &l); - status = luaL_loadbuffer(L, line, l, "=stdin"); - if (!incomplete(L, status)) break; /* cannot try to add lines? */ - if (!pushline(L, 0)) /* no more input? */ - return -1; - lua_pushliteral(L, "\n"); /* add a new line... */ - lua_insert(L, -2); /* ...between the two lines */ - lua_concat(L, 3); /* join them */ - } - lua_saveline(L, 1); - lua_remove(L, 1); /* remove line */ + if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */ + status = multiline(L); /* try as command, maybe with continuation lines */ + lua_remove(L, 1); /* remove line from the stack */ + lua_assert(lua_gettop(L) == 1); return status; } -static void dotty (lua_State *L) { +/* +** Prints (calling the Lua 'print' function) any values on the stack +*/ +static void l_print (lua_State *L) { + int n = lua_gettop(L); + if (n > 0) { /* any result to be printed? */ + luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, n, 0, 0) != LUA_OK) + l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)", + lua_tostring(L, -1))); + } +} + + +/* +** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and +** print any results. +*/ +static void doREPL (lua_State *L) { int status; const char *oldprogname = progname; - progname = NULL; + progname = NULL; /* no 'progname' on errors in interactive mode */ while ((status = loadline(L)) != -1) { - if (status == LUA_OK) status = docall(L, 0, LUA_MULTRET); - report(L, status); - if (status == LUA_OK && lua_gettop(L) > 0) { /* any result to print? */ - luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); - lua_getglobal(L, "print"); - lua_insert(L, 1); - if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != LUA_OK) - l_message(progname, lua_pushfstring(L, - "error calling " LUA_QL("print") " (%s)", - lua_tostring(L, -1))); - } + if (status == LUA_OK) + status = docall(L, 0, LUA_MULTRET); + if (status == LUA_OK) l_print(L); + else report(L, status); } lua_settop(L, 0); /* clear stack */ - luai_writeline(); + lua_writeline(); progname = oldprogname; } -static int handle_script (lua_State *L, char **argv, int n) { +/* +** Push on the stack the contents of table 'arg' from 1 to #arg +*/ +static int pushargs (lua_State *L) { + int i, n; + if (lua_getglobal(L, "arg") != LUA_TTABLE) + luaL_error(L, "'arg' is not a table"); + n = (int)luaL_len(L, -1); + luaL_checkstack(L, n + 3, "too many arguments to script"); + for (i = 1; i <= n; i++) + lua_rawgeti(L, -i, i); + lua_remove(L, -i); /* remove table from the stack */ + return n; +} + + +static int handle_script (lua_State *L, char **argv) { int status; - const char *fname; - int narg = getargs(L, argv, n); /* collect arguments */ - lua_setglobal(L, "arg"); - fname = argv[n]; - if (strcmp(fname, "-") == 0 && strcmp(argv[n-1], "--") != 0) + const char *fname = argv[0]; + if (strcmp(fname, "-") == 0 && strcmp(argv[-1], "--") != 0) fname = NULL; /* stdin */ status = luaL_loadfile(L, fname); - lua_insert(L, -(narg+1)); - if (status == LUA_OK) - status = docall(L, narg, LUA_MULTRET); - else - lua_pop(L, narg); + if (status == LUA_OK) { + int n = pushargs(L); /* push arguments to script */ + status = docall(L, n, LUA_MULTRET); + } return report(L, status); } -/* check that argument has no extra characters at the end */ -#define noextrachars(x) {if ((x)[2] != '\0') return -1;} - - -/* indices of various argument indicators in array args */ -#define has_i 0 /* -i */ -#define has_v 1 /* -v */ -#define has_e 2 /* -e */ -#define has_E 3 /* -E */ - -#define num_has 4 /* number of 'has_*' */ +/* bits of various argument indicators in 'args' */ +#define has_error 1 /* bad option */ +#define has_i 2 /* -i */ +#define has_v 4 /* -v */ +#define has_e 8 /* -e */ +#define has_E 16 /* -E */ -static int collectargs (char **argv, int *args) { +/* +** Traverses all arguments from 'argv', returning a mask with those +** needed before running any Lua code (or an error code if it finds +** any invalid argument). 'first' returns the first not-handled argument +** (either the script name or a bad argument in case of error). +*/ +static int collectargs (char **argv, int *first) { + int args = 0; int i; for (i = 1; argv[i] != NULL; i++) { + *first = i; if (argv[i][0] != '-') /* not an option? */ - return i; - switch (argv[i][1]) { /* option */ - case '-': - noextrachars(argv[i]); - return (argv[i+1] != NULL ? i+1 : 0); - case '\0': - return i; + return args; /* stop handling options */ + switch (argv[i][1]) { /* else check option */ + case '-': /* '--' */ + if (argv[i][2] != '\0') /* extra characters after '--'? */ + return has_error; /* invalid option */ + *first = i + 1; + return args; + case '\0': /* '-' */ + return args; /* script "name" is '-' */ case 'E': - args[has_E] = 1; + if (argv[i][2] != '\0') /* extra characters after 1st? */ + return has_error; /* invalid option */ + args |= has_E; break; case 'i': - noextrachars(argv[i]); - args[has_i] = 1; /* go through */ + args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */ case 'v': - noextrachars(argv[i]); - args[has_v] = 1; + if (argv[i][2] != '\0') /* extra characters after 1st? */ + return has_error; /* invalid option */ + args |= has_v; break; case 'e': - args[has_e] = 1; /* go through */ + args |= has_e; /* FALLTHROUGH */ case 'l': /* both options need an argument */ if (argv[i][2] == '\0') { /* no concatenated argument? */ i++; /* try next 'argv' */ if (argv[i] == NULL || argv[i][0] == '-') - return -(i - 1); /* no next argument or it is another option */ + return has_error; /* no next argument or it is another option */ } break; - default: /* invalid option; return its index... */ - return -i; /* ...as a negative value */ + default: /* invalid option */ + return has_error; } } - return 0; + *first = i; /* no script name */ + return args; } +/* +** Processes options 'e' and 'l', which involve running Lua code. +** Returns 0 if some code raises an error. +*/ static int runargs (lua_State *L, char **argv, int n) { int i; for (i = 1; i < n; i++) { - lua_assert(argv[i][0] == '-'); - switch (argv[i][1]) { /* option */ - case 'e': { - const char *chunk = argv[i] + 2; - if (*chunk == '\0') chunk = argv[++i]; - lua_assert(chunk != NULL); - if (dostring(L, chunk, "=(command line)") != LUA_OK) - return 0; - break; - } - case 'l': { - const char *filename = argv[i] + 2; - if (*filename == '\0') filename = argv[++i]; - lua_assert(filename != NULL); - if (dolibrary(L, filename) != LUA_OK) - return 0; /* stop if file fails */ - break; - } - default: break; + int option = argv[i][1]; + lua_assert(argv[i][0] == '-'); /* already checked */ + if (option == 'e' || option == 'l') { + int status; + const char *extra = argv[i] + 2; /* both options need an argument */ + if (*extra == '\0') extra = argv[++i]; + lua_assert(extra != NULL); + status = (option == 'e') + ? dostring(L, extra, "=(command line)") + : dolibrary(L, extra); + if (status != LUA_OK) return 0; } } return 1; @@ -421,10 +530,10 @@ static int runargs (lua_State *L, char **argv, int n) { static int handle_luainit (lua_State *L) { - const char *name = "=" LUA_INITVERSION; + const char *name = "=" LUA_INITVARVERSION; const char *init = getenv(name + 1); if (init == NULL) { - name = "=" LUA_INIT; + name = "=" LUA_INIT_VAR; init = getenv(name + 1); /* try alternative name */ } if (init == NULL) return LUA_OK; @@ -435,40 +544,44 @@ static int handle_luainit (lua_State *L) { } +/* +** Main body of stand-alone interpreter (to be called in protected mode). +** Reads the options and handles them all. +*/ static int pmain (lua_State *L) { int argc = (int)lua_tointeger(L, 1); char **argv = (char **)lua_touserdata(L, 2); int script; - int args[num_has]; - args[has_i] = args[has_v] = args[has_e] = args[has_E] = 0; + int args = collectargs(argv, &script); + luaL_checkversion(L); /* check that interpreter has correct version */ if (argv[0] && argv[0][0]) progname = argv[0]; - script = collectargs(argv, args); - if (script < 0) { /* invalid arg? */ - print_usage(argv[-script]); + if (args == has_error) { /* bad arg? */ + print_usage(argv[script]); /* 'script' has index of bad arg. */ return 0; } - if (args[has_v]) print_version(); - if (args[has_E]) { /* option '-E'? */ + if (args & has_v) /* option '-v'? */ + print_version(); + if (args & has_E) { /* option '-E'? */ lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); } - /* open standard libraries */ - luaL_checkversion(L); - lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ - luaL_openlibs(L); /* open libraries */ - lua_gc(L, LUA_GCRESTART, 0); - if (!args[has_E] && handle_luainit(L) != LUA_OK) - return 0; /* error running LUA_INIT */ - /* execute arguments -e and -l */ - if (!runargs(L, argv, (script > 0) ? script : argc)) return 0; - /* execute main script (if there is one) */ - if (script && handle_script(L, argv, script) != LUA_OK) return 0; - if (args[has_i]) /* -i option? */ - dotty(L); - else if (script == 0 && !args[has_e] && !args[has_v]) { /* no arguments? */ - if (lua_stdin_is_tty()) { + luaL_openlibs(L); /* open standard libraries */ + createargtable(L, argv, argc, script); /* create table 'arg' */ + if (!(args & has_E)) { /* no option '-E'? */ + if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */ + return 0; /* error running LUA_INIT */ + } + if (!runargs(L, argv, script)) /* execute arguments -e and -l */ + return 0; /* something failed */ + if (script < argc && /* execute main script (if there is one) */ + handle_script(L, argv + script) != LUA_OK) + return 0; + if (args & has_i) /* -i option? */ + doREPL(L); /* do read-eval-print loop */ + else if (script == argc && !(args & (has_e | has_v))) { /* no arguments? */ + if (lua_stdin_is_tty()) { /* running in interactive mode? */ print_version(); - dotty(L); + doREPL(L); /* do read-eval-print loop */ } else dofile(L, NULL); /* executes stdin as a file */ } @@ -484,13 +597,12 @@ int main (int argc, char **argv) { l_message(argv[0], "cannot create state: not enough memory"); return EXIT_FAILURE; } - /* call 'pmain' in protected mode */ - lua_pushcfunction(L, &pmain); + lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */ lua_pushinteger(L, argc); /* 1st argument */ lua_pushlightuserdata(L, argv); /* 2nd argument */ - status = lua_pcall(L, 2, 1, 0); + status = lua_pcall(L, 2, 1, 0); /* do the call */ result = lua_toboolean(L, -1); /* get result */ - finalreport(L, status); + report(L, status); lua_close(L); return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/depends/lua/src/luac.c b/depends/lua/src/luac.c index 7409706ec..c0c91d017 100644 --- a/depends/lua/src/luac.c +++ b/depends/lua/src/luac.c @@ -1,17 +1,20 @@ /* -** $Id: luac.c,v 1.69 2011/11/29 17:46:33 lhf Exp $ -** Lua compiler (saves bytecodes to files; also list bytecodes) +** $Id: luac.c,v 1.75 2015/03/12 01:58:27 lhf Exp $ +** Lua compiler (saves bytecodes to files; also lists bytecodes) ** See Copyright Notice in lua.h */ +#define luac_c +#define LUA_CORE + +#include "lprefix.h" + +#include #include #include #include #include -#define luac_c -#define LUA_CORE - #include "lua.h" #include "lauxlib.h" @@ -47,14 +50,14 @@ static void cannot(const char* what) static void usage(const char* message) { if (*message=='-') - fprintf(stderr,"%s: unrecognized option " LUA_QS "\n",progname,message); + fprintf(stderr,"%s: unrecognized option '%s'\n",progname,message); else fprintf(stderr,"%s: %s\n",progname,message); fprintf(stderr, "usage: %s [options] [filenames]\n" "Available options are:\n" " -l list (use -l -l for full listing)\n" - " -o name output to file " LUA_QL("name") " (default is \"%s\")\n" + " -o name output to file 'name' (default is \"%s\")\n" " -p parse only\n" " -s strip debug information\n" " -v show version information\n" @@ -89,7 +92,7 @@ static int doargs(int argc, char* argv[]) { output=argv[++i]; if (output==NULL || *output==0 || (*output=='-' && output[1]!=0)) - usage(LUA_QL("-o") " needs argument"); + usage("'-o' needs argument"); if (IS("-")) output=NULL; } else if (IS("-p")) /* parse only */ @@ -203,7 +206,7 @@ int main(int argc, char* argv[]) } /* -** $Id: print.c,v 1.69 2013/07/04 01:03:46 lhf Exp $ +** $Id: luac.c,v 1.75 2015/03/12 01:58:27 lhf Exp $ ** print bytecodes ** See Copyright Notice in lua.h */ @@ -223,7 +226,7 @@ int main(int argc, char* argv[]) static void PrintString(const TString* ts) { const char* s=getstr(ts); - size_t i,n=ts->tsv.len; + size_t i,n=tsslen(ts); printf("%c",'"'); for (i=0; ik[i]; - switch (ttypenv(o)) + switch (ttype(o)) { case LUA_TNIL: printf("nil"); @@ -259,11 +262,19 @@ static void PrintConstant(const Proto* f, int i) case LUA_TBOOLEAN: printf(bvalue(o) ? "true" : "false"); break; - case LUA_TNUMBER: - printf(LUA_NUMBER_FMT,nvalue(o)); + case LUA_TNUMFLT: + { + char buff[100]; + sprintf(buff,LUA_NUMBER_FMT,fltvalue(o)); + printf("%s",buff); + if (buff[strspn(buff,"-0123456789")]=='\0') printf(".0"); + break; + } + case LUA_TNUMINT: + printf(LUA_INTEGER_FMT,ivalue(o)); break; - case LUA_TSTRING: - PrintString(rawtsvalue(o)); + case LUA_TSHRSTR: case LUA_TLNGSTR: + PrintString(tsvalue(o)); break; default: /* cannot happen */ printf("? type=%d",ttype(o)); @@ -337,8 +348,14 @@ static void PrintCode(const Proto* f) case OP_ADD: case OP_SUB: case OP_MUL: - case OP_DIV: case OP_POW: + case OP_DIV: + case OP_IDIV: + case OP_BAND: + case OP_BOR: + case OP_BXOR: + case OP_SHL: + case OP_SHR: case OP_EQ: case OP_LT: case OP_LE: diff --git a/depends/lua/src/lundump.c b/depends/lua/src/lundump.c index 4163cb5d3..4080af9c0 100644 --- a/depends/lua/src/lundump.c +++ b/depends/lua/src/lundump.c @@ -1,14 +1,17 @@ /* -** $Id: lundump.c,v 2.22.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lundump.c,v 2.44 2015/11/02 16:09:30 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ -#include - #define lundump_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "ldebug.h" @@ -20,239 +23,257 @@ #include "lundump.h" #include "lzio.h" + +#if !defined(luai_verifycode) +#define luai_verifycode(L,b,f) /* empty */ +#endif + + typedef struct { - lua_State* L; - ZIO* Z; - Mbuffer* b; - const char* name; + lua_State *L; + ZIO *Z; + const char *name; } LoadState; -static l_noret error(LoadState* S, const char* why) -{ - luaO_pushfstring(S->L,"%s: %s precompiled chunk",S->name,why); - luaD_throw(S->L,LUA_ERRSYNTAX); + +static l_noret error(LoadState *S, const char *why) { + luaO_pushfstring(S->L, "%s: %s precompiled chunk", S->name, why); + luaD_throw(S->L, LUA_ERRSYNTAX); } -#define LoadMem(S,b,n,size) LoadBlock(S,b,(n)*(size)) -#define LoadByte(S) (lu_byte)LoadChar(S) -#define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x)) -#define LoadVector(S,b,n,size) LoadMem(S,b,n,size) -#if !defined(luai_verifycode) -#define luai_verifycode(L,b,f) /* empty */ -#endif +/* +** All high-level loads go through LoadVector; you can change it to +** adapt to the endianness of the input +*/ +#define LoadVector(S,b,n) LoadBlock(S,b,(n)*sizeof((b)[0])) -static void LoadBlock(LoadState* S, void* b, size_t size) -{ - if (luaZ_read(S->Z,b,size)!=0) error(S,"truncated"); +static void LoadBlock (LoadState *S, void *b, size_t size) { + if (luaZ_read(S->Z, b, size) != 0) + error(S, "truncated"); } -static int LoadChar(LoadState* S) -{ - char x; - LoadVar(S,x); - return x; + +#define LoadVar(S,x) LoadVector(S,&x,1) + + +static lu_byte LoadByte (LoadState *S) { + lu_byte x; + LoadVar(S, x); + return x; } -static int LoadInt(LoadState* S) -{ - int x; - LoadVar(S,x); - if (x<0) error(S,"corrupted"); - return x; + +static int LoadInt (LoadState *S) { + int x; + LoadVar(S, x); + return x; } -static lua_Number LoadNumber(LoadState* S) -{ - lua_Number x; - LoadVar(S,x); - return x; + +static lua_Number LoadNumber (LoadState *S) { + lua_Number x; + LoadVar(S, x); + return x; } -static TString* LoadString(LoadState* S) -{ - size_t size; - LoadVar(S,size); - if (size==0) - return NULL; - else - { - char* s=luaZ_openspace(S->L,S->b,size); - LoadBlock(S,s,size*sizeof(char)); - return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */ - } + +static lua_Integer LoadInteger (LoadState *S) { + lua_Integer x; + LoadVar(S, x); + return x; } -static void LoadCode(LoadState* S, Proto* f) -{ - int n=LoadInt(S); - f->code=luaM_newvector(S->L,n,Instruction); - f->sizecode=n; - LoadVector(S,f->code,n,sizeof(Instruction)); + +static TString *LoadString (LoadState *S) { + size_t size = LoadByte(S); + if (size == 0xFF) + LoadVar(S, size); + if (size == 0) + return NULL; + else if (--size <= LUAI_MAXSHORTLEN) { /* short string? */ + char buff[LUAI_MAXSHORTLEN]; + LoadVector(S, buff, size); + return luaS_newlstr(S->L, buff, size); + } + else { /* long string */ + TString *ts = luaS_createlngstrobj(S->L, size); + LoadVector(S, getstr(ts), size); /* load directly in final place */ + return ts; + } } -static void LoadFunction(LoadState* S, Proto* f); - -static void LoadConstants(LoadState* S, Proto* f) -{ - int i,n; - n=LoadInt(S); - f->k=luaM_newvector(S->L,n,TValue); - f->sizek=n; - for (i=0; ik[i]); - for (i=0; ik[i]; - int t=LoadChar(S); - switch (t) - { - case LUA_TNIL: - setnilvalue(o); - break; - case LUA_TBOOLEAN: - setbvalue(o,LoadChar(S)); - break; - case LUA_TNUMBER: - setnvalue(o,LoadNumber(S)); - break; - case LUA_TSTRING: - setsvalue2n(S->L,o,LoadString(S)); - break; - default: lua_assert(0); + +static void LoadCode (LoadState *S, Proto *f) { + int n = LoadInt(S); + f->code = luaM_newvector(S->L, n, Instruction); + f->sizecode = n; + LoadVector(S, f->code, n); +} + + +static void LoadFunction(LoadState *S, Proto *f, TString *psource); + + +static void LoadConstants (LoadState *S, Proto *f) { + int i; + int n = LoadInt(S); + f->k = luaM_newvector(S->L, n, TValue); + f->sizek = n; + for (i = 0; i < n; i++) + setnilvalue(&f->k[i]); + for (i = 0; i < n; i++) { + TValue *o = &f->k[i]; + int t = LoadByte(S); + switch (t) { + case LUA_TNIL: + setnilvalue(o); + break; + case LUA_TBOOLEAN: + setbvalue(o, LoadByte(S)); + break; + case LUA_TNUMFLT: + setfltvalue(o, LoadNumber(S)); + break; + case LUA_TNUMINT: + setivalue(o, LoadInteger(S)); + break; + case LUA_TSHRSTR: + case LUA_TLNGSTR: + setsvalue2n(S->L, o, LoadString(S)); + break; + default: + lua_assert(0); + } } - } - n=LoadInt(S); - f->p=luaM_newvector(S->L,n,Proto*); - f->sizep=n; - for (i=0; ip[i]=NULL; - for (i=0; ip[i]=luaF_newproto(S->L); - LoadFunction(S,f->p[i]); - } } -static void LoadUpvalues(LoadState* S, Proto* f) -{ - int i,n; - n=LoadInt(S); - f->upvalues=luaM_newvector(S->L,n,Upvaldesc); - f->sizeupvalues=n; - for (i=0; iupvalues[i].name=NULL; - for (i=0; iupvalues[i].instack=LoadByte(S); - f->upvalues[i].idx=LoadByte(S); - } + +static void LoadProtos (LoadState *S, Proto *f) { + int i; + int n = LoadInt(S); + f->p = luaM_newvector(S->L, n, Proto *); + f->sizep = n; + for (i = 0; i < n; i++) + f->p[i] = NULL; + for (i = 0; i < n; i++) { + f->p[i] = luaF_newproto(S->L); + LoadFunction(S, f->p[i], f->source); + } } -static void LoadDebug(LoadState* S, Proto* f) -{ - int i,n; - f->source=LoadString(S); - n=LoadInt(S); - f->lineinfo=luaM_newvector(S->L,n,int); - f->sizelineinfo=n; - LoadVector(S,f->lineinfo,n,sizeof(int)); - n=LoadInt(S); - f->locvars=luaM_newvector(S->L,n,LocVar); - f->sizelocvars=n; - for (i=0; ilocvars[i].varname=NULL; - for (i=0; ilocvars[i].varname=LoadString(S); - f->locvars[i].startpc=LoadInt(S); - f->locvars[i].endpc=LoadInt(S); - } - n=LoadInt(S); - for (i=0; iupvalues[i].name=LoadString(S); + +static void LoadUpvalues (LoadState *S, Proto *f) { + int i, n; + n = LoadInt(S); + f->upvalues = luaM_newvector(S->L, n, Upvaldesc); + f->sizeupvalues = n; + for (i = 0; i < n; i++) + f->upvalues[i].name = NULL; + for (i = 0; i < n; i++) { + f->upvalues[i].instack = LoadByte(S); + f->upvalues[i].idx = LoadByte(S); + } } -static void LoadFunction(LoadState* S, Proto* f) -{ - f->linedefined=LoadInt(S); - f->lastlinedefined=LoadInt(S); - f->numparams=LoadByte(S); - f->is_vararg=LoadByte(S); - f->maxstacksize=LoadByte(S); - LoadCode(S,f); - LoadConstants(S,f); - LoadUpvalues(S,f); - LoadDebug(S,f); + +static void LoadDebug (LoadState *S, Proto *f) { + int i, n; + n = LoadInt(S); + f->lineinfo = luaM_newvector(S->L, n, int); + f->sizelineinfo = n; + LoadVector(S, f->lineinfo, n); + n = LoadInt(S); + f->locvars = luaM_newvector(S->L, n, LocVar); + f->sizelocvars = n; + for (i = 0; i < n; i++) + f->locvars[i].varname = NULL; + for (i = 0; i < n; i++) { + f->locvars[i].varname = LoadString(S); + f->locvars[i].startpc = LoadInt(S); + f->locvars[i].endpc = LoadInt(S); + } + n = LoadInt(S); + for (i = 0; i < n; i++) + f->upvalues[i].name = LoadString(S); } -/* the code below must be consistent with the code in luaU_header */ -#define N0 LUAC_HEADERSIZE -#define N1 (sizeof(LUA_SIGNATURE)-sizeof(char)) -#define N2 N1+2 -#define N3 N2+6 - -static void LoadHeader(LoadState* S) -{ - lu_byte h[LUAC_HEADERSIZE]; - lu_byte s[LUAC_HEADERSIZE]; - luaU_header(h); - memcpy(s,h,sizeof(char)); /* first char already read */ - LoadBlock(S,s+sizeof(char),LUAC_HEADERSIZE-sizeof(char)); - if (memcmp(h,s,N0)==0) return; - if (memcmp(h,s,N1)!=0) error(S,"not a"); - if (memcmp(h,s,N2)!=0) error(S,"version mismatch in"); - if (memcmp(h,s,N3)!=0) error(S,"incompatible"); else error(S,"corrupted"); + +static void LoadFunction (LoadState *S, Proto *f, TString *psource) { + f->source = LoadString(S); + if (f->source == NULL) /* no source in dump? */ + f->source = psource; /* reuse parent's source */ + f->linedefined = LoadInt(S); + f->lastlinedefined = LoadInt(S); + f->numparams = LoadByte(S); + f->is_vararg = LoadByte(S); + f->maxstacksize = LoadByte(S); + LoadCode(S, f); + LoadConstants(S, f); + LoadUpvalues(S, f); + LoadProtos(S, f); + LoadDebug(S, f); } -/* -** load precompiled chunk -*/ -Closure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name) -{ - LoadState S; - Closure* cl; - if (*name=='@' || *name=='=') - S.name=name+1; - else if (*name==LUA_SIGNATURE[0]) - S.name="binary string"; - else - S.name=name; - S.L=L; - S.Z=Z; - S.b=buff; - LoadHeader(&S); - cl=luaF_newLclosure(L,1); - setclLvalue(L,L->top,cl); incr_top(L); - cl->l.p=luaF_newproto(L); - LoadFunction(&S,cl->l.p); - if (cl->l.p->sizeupvalues != 1) - { - Proto* p=cl->l.p; - cl=luaF_newLclosure(L,cl->l.p->sizeupvalues); - cl->l.p=p; - setclLvalue(L,L->top-1,cl); - } - luai_verifycode(L,buff,cl->l.p); - return cl; + +static void checkliteral (LoadState *S, const char *s, const char *msg) { + char buff[sizeof(LUA_SIGNATURE) + sizeof(LUAC_DATA)]; /* larger than both */ + size_t len = strlen(s); + LoadVector(S, buff, len); + if (memcmp(s, buff, len) != 0) + error(S, msg); +} + + +static void fchecksize (LoadState *S, size_t size, const char *tname) { + if (LoadByte(S) != size) + error(S, luaO_pushfstring(S->L, "%s size mismatch in", tname)); +} + + +#define checksize(S,t) fchecksize(S,sizeof(t),#t) + +static void checkHeader (LoadState *S) { + checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */ + if (LoadByte(S) != LUAC_VERSION) + error(S, "version mismatch in"); + if (LoadByte(S) != LUAC_FORMAT) + error(S, "format mismatch in"); + checkliteral(S, LUAC_DATA, "corrupted"); + checksize(S, int); + checksize(S, size_t); + checksize(S, Instruction); + checksize(S, lua_Integer); + checksize(S, lua_Number); + if (LoadInteger(S) != LUAC_INT) + error(S, "endianness mismatch in"); + if (LoadNumber(S) != LUAC_NUM) + error(S, "float format mismatch in"); } -#define MYINT(s) (s[0]-'0') -#define VERSION MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR) -#define FORMAT 0 /* this is the official format */ /* -* make header for precompiled chunks -* if you change the code below be sure to update LoadHeader and FORMAT above -* and LUAC_HEADERSIZE in lundump.h +** load precompiled chunk */ -void luaU_header (lu_byte* h) -{ - int x=1; - memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-sizeof(char)); - h+=sizeof(LUA_SIGNATURE)-sizeof(char); - *h++=cast_byte(VERSION); - *h++=cast_byte(FORMAT); - *h++=cast_byte(*(char*)&x); /* endianness */ - *h++=cast_byte(sizeof(int)); - *h++=cast_byte(sizeof(size_t)); - *h++=cast_byte(sizeof(Instruction)); - *h++=cast_byte(sizeof(lua_Number)); - *h++=cast_byte(((lua_Number)0.5)==0); /* is lua_Number integral? */ - memcpy(h,LUAC_TAIL,sizeof(LUAC_TAIL)-sizeof(char)); +LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) { + LoadState S; + LClosure *cl; + if (*name == '@' || *name == '=') + S.name = name + 1; + else if (*name == LUA_SIGNATURE[0]) + S.name = "binary string"; + else + S.name = name; + S.L = L; + S.Z = Z; + checkHeader(&S); + cl = luaF_newLclosure(L, LoadByte(&S)); + setclLvalue(L, L->top, cl); + luaD_inctop(L); + cl->p = luaF_newproto(L); + LoadFunction(&S, cl->p, NULL); + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luai_verifycode(L, buff, cl->p); + return cl; } + diff --git a/depends/lua/src/lutf8lib.c b/depends/lua/src/lutf8lib.c new file mode 100644 index 000000000..9042582d1 --- /dev/null +++ b/depends/lua/src/lutf8lib.c @@ -0,0 +1,256 @@ +/* +** $Id: lutf8lib.c,v 1.15 2015/03/28 19:16:55 roberto Exp $ +** Standard library for UTF-8 manipulation +** See Copyright Notice in lua.h +*/ + +#define lutf8lib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + +#define MAXUNICODE 0x10FFFF + +#define iscont(p) ((*(p) & 0xC0) == 0x80) + + +/* from strlib */ +/* translate a relative string position: negative means back from end */ +static lua_Integer u_posrelat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + + +/* +** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid. +*/ +static const char *utf8_decode (const char *o, int *val) { + static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF}; + const unsigned char *s = (const unsigned char *)o; + unsigned int c = s[0]; + unsigned int res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + while (c & 0x40) { /* still have continuation bytes? */ + int cc = s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + c <<= 1; /* to test next bit */ + } + res |= ((c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 3 || res > MAXUNICODE || res <= limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (val) *val = res; + return (const char *)s + 1; /* +1 to include first byte */ +} + + +/* +** utf8len(s [, i [, j]]) --> number of characters that start in the +** range [i,j], or nil + current position if 's' is not well formed in +** that interval +*/ +static int utflen (lua_State *L) { + int n = 0; + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2, + "initial position out of string"); + luaL_argcheck(L, --posj < (lua_Integer)len, 3, + "final position out of string"); + while (posi <= posj) { + const char *s1 = utf8_decode(s + posi, NULL); + if (s1 == NULL) { /* conversion error? */ + lua_pushnil(L); /* return nil ... */ + lua_pushinteger(L, posi + 1); /* ... and current position */ + return 2; + } + posi = s1 - s; + n++; + } + lua_pushinteger(L, n); + return 1; +} + + +/* +** codepoint(s, [i, [j]]) -> returns codepoints for all characters +** that start in the range [i,j] +*/ +static int codepoint (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len); + int n; + const char *se; + luaL_argcheck(L, posi >= 1, 2, "out of range"); + luaL_argcheck(L, pose <= (lua_Integer)len, 3, "out of range"); + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; + luaL_checkstack(L, n, "string slice too long"); + n = 0; + se = s + pose; + for (s += posi - 1; s < se;) { + int code; + s = utf8_decode(s, &code); + if (s == NULL) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, code); + n++; + } + return n; +} + + +static void pushutfchar (lua_State *L, int arg) { + lua_Integer code = luaL_checkinteger(L, arg); + luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, "value out of range"); + lua_pushfstring(L, "%U", (long)code); +} + + +/* +** utfchar(n1, n2, ...) -> char(n1)..char(n2)... +*/ +static int utfchar (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + if (n == 1) /* optimize common case of single char */ + pushutfchar(L, 1); + else { + int i; + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i = 1; i <= n; i++) { + pushutfchar(L, i); + luaL_addvalue(&b); + } + luaL_pushresult(&b); + } + return 1; +} + + +/* +** offset(s, n, [i]) -> index where n-th character counting from +** position 'i' starts; 0 means character at 'i'. +*/ +static int byteoffset (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = luaL_checkinteger(L, 2); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = u_posrelat(luaL_optinteger(L, 3, posi), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3, + "position out of range"); + if (n == 0) { + /* find beginning of current byte sequence */ + while (posi > 0 && iscont(s + posi)) posi--; + } + else { + if (iscont(s + posi)) + luaL_error(L, "initial position is a continuation byte"); + if (n < 0) { + while (n < 0 && posi > 0) { /* move back */ + do { /* find beginning of previous character */ + posi--; + } while (posi > 0 && iscont(s + posi)); + n++; + } + } + else { + n--; /* do not move for 1st character */ + while (n > 0 && posi < (lua_Integer)len) { + do { /* find beginning of next character */ + posi++; + } while (iscont(s + posi)); /* (cannot pass final '\0') */ + n--; + } + } + } + if (n == 0) /* did it find given character? */ + lua_pushinteger(L, posi + 1); + else /* no such character */ + lua_pushnil(L); + return 1; +} + + +static int iter_aux (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = lua_tointeger(L, 2) - 1; + if (n < 0) /* first iteration? */ + n = 0; /* start from here */ + else if (n < (lua_Integer)len) { + n++; /* skip current byte */ + while (iscont(s + n)) n++; /* and its continuations */ + } + if (n >= (lua_Integer)len) + return 0; /* no more codepoints */ + else { + int code; + const char *next = utf8_decode(s + n, &code); + if (next == NULL || iscont(next)) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, n + 1); + lua_pushinteger(L, code); + return 2; + } +} + + +static int iter_codes (lua_State *L) { + luaL_checkstring(L, 1); + lua_pushcfunction(L, iter_aux); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + + +/* pattern to match a single UTF-8 character */ +#define UTF8PATT "[\0-\x7F\xC2-\xF4][\x80-\xBF]*" + + +static const luaL_Reg funcs[] = { + {"offset", byteoffset}, + {"codepoint", codepoint}, + {"char", utfchar}, + {"len", utflen}, + {"codes", iter_codes}, + /* placeholders */ + {"charpattern", NULL}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_utf8 (lua_State *L) { + luaL_newlib(L, funcs); + lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)/sizeof(char) - 1); + lua_setfield(L, -2, "charpattern"); + return 1; +} + diff --git a/depends/lua/src/lvm.c b/depends/lua/src/lvm.c index 141b9fd19..84ade6b2f 100644 --- a/depends/lua/src/lvm.c +++ b/depends/lua/src/lvm.c @@ -1,17 +1,21 @@ /* -** $Id: lvm.c,v 2.155.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lvm.c,v 2.268 2016/02/05 19:59:14 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ +#define lvm_c +#define LUA_CORE + +#include "lprefix.h" +#include +#include +#include #include #include #include -#define lvm_c -#define LUA_CORE - #include "lua.h" #include "ldebug.h" @@ -27,304 +31,479 @@ #include "lvm.h" - /* limit for table tag-method chains (to avoid loops) */ -#define MAXTAGLOOP 100 +#define MAXTAGLOOP 2000 -const TValue *luaV_tonumber (const TValue *obj, TValue *n) { - lua_Number num; - if (ttisnumber(obj)) return obj; - if (ttisstring(obj) && luaO_str2d(svalue(obj), tsvalue(obj)->len, &num)) { - setnvalue(n, num); - return n; - } - else - return NULL; -} +/* +** 'l_intfitsf' checks whether a given integer can be converted to a +** float without rounding. Used in comparisons. Left undefined if +** all integers fit in a float precisely. +*/ +#if !defined(l_intfitsf) -int luaV_tostring (lua_State *L, StkId obj) { - if (!ttisnumber(obj)) - return 0; - else { - char s[LUAI_MAXNUMBER2STR]; - lua_Number n = nvalue(obj); - int l = lua_number2str(s, n); - setsvalue2s(L, obj, luaS_newlstr(L, s, l)); +/* number of bits in the mantissa of a float */ +#define NBM (l_mathlim(MANT_DIG)) + +/* +** Check whether some integers may not fit in a float, that is, whether +** (maxinteger >> NBM) > 0 (that implies (1 << NBM) <= maxinteger). +** (The shifts are done in parts to avoid shifting by more than the size +** of an integer. In a worst case, NBM == 113 for long double and +** sizeof(integer) == 32.) +*/ +#if ((((LUA_MAXINTEGER >> (NBM / 4)) >> (NBM / 4)) >> (NBM / 4)) \ + >> (NBM - (3 * (NBM / 4)))) > 0 + +#define l_intfitsf(i) \ + (-((lua_Integer)1 << NBM) <= (i) && (i) <= ((lua_Integer)1 << NBM)) + +#endif + +#endif + + + +/* +** Try to convert a value to a float. The float case is already handled +** by the macro 'tonumber'. +*/ +int luaV_tonumber_ (const TValue *obj, lua_Number *n) { + TValue v; + if (ttisinteger(obj)) { + *n = cast_num(ivalue(obj)); + return 1; + } + else if (cvt2num(obj) && /* string convertible to number? */ + luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { + *n = nvalue(&v); /* convert result of 'luaO_str2num' to a float */ return 1; } + else + return 0; /* conversion failed */ } -static void traceexec (lua_State *L) { - CallInfo *ci = L->ci; - lu_byte mask = L->hookmask; - int counthook = ((mask & LUA_MASKCOUNT) && L->hookcount == 0); - if (counthook) - resethookcount(L); /* reset count */ - if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ - ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ - return; /* do not call hook again (VM yielded, so it did not move) */ +/* +** try to convert a value to an integer, rounding according to 'mode': +** mode == 0: accepts only integral values +** mode == 1: takes the floor of the number +** mode == 2: takes the ceil of the number +*/ +int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) { + TValue v; + again: + if (ttisfloat(obj)) { + lua_Number n = fltvalue(obj); + lua_Number f = l_floor(n); + if (n != f) { /* not an integral value? */ + if (mode == 0) return 0; /* fails if mode demands integral value */ + else if (mode > 1) /* needs ceil? */ + f += 1; /* convert floor to ceil (remember: n != f) */ + } + return lua_numbertointeger(f, p); } - if (counthook) - luaD_hook(L, LUA_HOOKCOUNT, -1); /* call count hook */ - if (mask & LUA_MASKLINE) { - Proto *p = ci_func(ci)->p; - int npc = pcRel(ci->u.l.savedpc, p); - int newline = getfuncline(p, npc); - if (npc == 0 || /* call linehook when enter a new function, */ - ci->u.l.savedpc <= L->oldpc || /* when jump back (loop), or when */ - newline != getfuncline(p, pcRel(L->oldpc, p))) /* enter a new line */ - luaD_hook(L, LUA_HOOKLINE, newline); /* call line hook */ + else if (ttisinteger(obj)) { + *p = ivalue(obj); + return 1; } - L->oldpc = ci->u.l.savedpc; - if (L->status == LUA_YIELD) { /* did hook yield? */ - if (counthook) - L->hookcount = 1; /* undo decrement to zero */ - ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ - ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ - ci->func = L->top - 1; /* protect stack below results */ - luaD_throw(L, LUA_YIELD); + else if (cvt2num(obj) && + luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { + obj = &v; + goto again; /* convert result from 'luaO_str2num' to an integer */ } + return 0; /* conversion failed */ } -static void callTM (lua_State *L, const TValue *f, const TValue *p1, - const TValue *p2, TValue *p3, int hasres) { - ptrdiff_t result = savestack(L, p3); - setobj2s(L, L->top++, f); /* push function */ - setobj2s(L, L->top++, p1); /* 1st argument */ - setobj2s(L, L->top++, p2); /* 2nd argument */ - if (!hasres) /* no result? 'p3' is third argument */ - setobj2s(L, L->top++, p3); /* 3rd argument */ - /* metamethod may yield only when called from Lua code */ - luaD_call(L, L->top - (4 - hasres), hasres, isLua(L->ci)); - if (hasres) { /* if has result, move it to its place */ - p3 = restorestack(L, result); - setobjs2s(L, p3, --L->top); +/* +** Try to convert a 'for' limit to an integer, preserving the +** semantics of the loop. +** (The following explanation assumes a non-negative step; it is valid +** for negative steps mutatis mutandis.) +** If the limit can be converted to an integer, rounding down, that is +** it. +** Otherwise, check whether the limit can be converted to a number. If +** the number is too large, it is OK to set the limit as LUA_MAXINTEGER, +** which means no limit. If the number is too negative, the loop +** should not run, because any initial integer value is larger than the +** limit. So, it sets the limit to LUA_MININTEGER. 'stopnow' corrects +** the extreme case when the initial value is LUA_MININTEGER, in which +** case the LUA_MININTEGER limit would still run the loop once. +*/ +static int forlimit (const TValue *obj, lua_Integer *p, lua_Integer step, + int *stopnow) { + *stopnow = 0; /* usually, let loops run */ + if (!luaV_tointeger(obj, p, (step < 0 ? 2 : 1))) { /* not fit in integer? */ + lua_Number n; /* try to convert to float */ + if (!tonumber(obj, &n)) /* cannot convert to float? */ + return 0; /* not a number */ + if (luai_numlt(0, n)) { /* if true, float is larger than max integer */ + *p = LUA_MAXINTEGER; + if (step < 0) *stopnow = 1; + } + else { /* float is smaller than min integer */ + *p = LUA_MININTEGER; + if (step >= 0) *stopnow = 1; + } } + return 1; } -void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { - int loop; +/* +** Finish the table access 'val = t[key]'. +** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to +** t[k] entry (which must be nil). +*/ +void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, + const TValue *slot) { + int loop; /* counter to avoid infinite loops */ + const TValue *tm; /* metamethod */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* `t' is a table? */ - Table *h = hvalue(t); - const TValue *res = luaH_get(h, key); /* do a primitive get */ - if (!ttisnil(res) || /* result is not nil? */ - (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ - setobj2s(L, val, res); + if (slot == NULL) { /* 't' is not a table? */ + lua_assert(!ttistable(t)); + tm = luaT_gettmbyobj(L, t, TM_INDEX); + if (ttisnil(tm)) + luaG_typeerror(L, t, "index"); /* no metamethod */ + /* else will try the metamethod */ + } + else { /* 't' is a table */ + lua_assert(ttisnil(slot)); + tm = fasttm(L, hvalue(t)->metatable, TM_INDEX); /* table's metamethod */ + if (tm == NULL) { /* no metamethod? */ + setnilvalue(val); /* result is nil */ return; } - /* else will try the tag method */ + /* else will try the metamethod */ } - else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) - luaG_typeerror(L, t, "index"); - if (ttisfunction(tm)) { - callTM(L, tm, t, key, val, 1); + if (ttisfunction(tm)) { /* is metamethod a function? */ + luaT_callTM(L, tm, t, key, val, 1); /* call it */ + return; + } + t = tm; /* else try to access 'tm[key]' */ + if (luaV_fastget(L,t,key,slot,luaH_get)) { /* fast track? */ + setobj2s(L, val, slot); /* done */ return; } - t = tm; /* else repeat with 'tm' */ + /* else repeat (tail call 'luaV_finishget') */ } - luaG_runerror(L, "loop in gettable"); + luaG_runerror(L, "'__index' chain too long; possible loop"); } -void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { - int loop; +/* +** Finish a table assignment 't[key] = val'. +** If 'slot' is NULL, 't' is not a table. Otherwise, 'slot' points +** to the entry 't[key]', or to 'luaO_nilobject' if there is no such +** entry. (The value at 'slot' must be nil, otherwise 'luaV_fastset' +** would have done the job.) +*/ +void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot) { + int loop; /* counter to avoid infinite loops */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* `t' is a table? */ - Table *h = hvalue(t); - TValue *oldval = cast(TValue *, luaH_get(h, key)); - /* if previous value is not nil, there must be a previous entry - in the table; moreover, a metamethod has no relevance */ - if (!ttisnil(oldval) || - /* previous value is nil; must check the metamethod */ - ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL && - /* no metamethod; is there a previous entry in the table? */ - (oldval != luaO_nilobject || - /* no previous entry; must create one. (The next test is - always true; we only need the assignment.) */ - (oldval = luaH_newkey(L, h, key), 1)))) { + const TValue *tm; /* '__newindex' metamethod */ + if (slot != NULL) { /* is 't' a table? */ + Table *h = hvalue(t); /* save 't' table */ + lua_assert(ttisnil(slot)); /* old value must be nil */ + tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ + if (tm == NULL) { /* no metamethod? */ + if (slot == luaO_nilobject) /* no previous entry? */ + slot = luaH_newkey(L, h, key); /* create one */ /* no metamethod and (now) there is an entry with given key */ - setobj2t(L, oldval, val); /* assign new value to that entry */ + setobj2t(L, cast(TValue *, slot), val); /* set its new value */ invalidateTMcache(h); - luaC_barrierback(L, obj2gco(h), val); + luaC_barrierback(L, h, val); return; } /* else will try the metamethod */ } - else /* not a table; check metamethod */ + else { /* not a table; check metamethod */ if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_typeerror(L, t, "index"); - /* there is a metamethod */ + } + /* try the metamethod */ if (ttisfunction(tm)) { - callTM(L, tm, t, key, val, 0); + luaT_callTM(L, tm, t, key, val, 0); return; } - t = tm; /* else repeat with 'tm' */ + t = tm; /* else repeat assignment over 'tm' */ + if (luaV_fastset(L, t, key, slot, luaH_get, val)) + return; /* done */ + /* else loop */ } - luaG_runerror(L, "loop in settable"); + luaG_runerror(L, "'__newindex' chain too long; possible loop"); } -static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, - StkId res, TMS event) { - const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ - if (ttisnil(tm)) - tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ - if (ttisnil(tm)) return 0; - callTM(L, tm, p1, p2, res, 1); - return 1; +/* +** Compare two strings 'ls' x 'rs', returning an integer smaller-equal- +** -larger than zero if 'ls' is smaller-equal-larger than 'rs'. +** The code is a little tricky because it allows '\0' in the strings +** and it uses 'strcoll' (to respect locales) for each segments +** of the strings. +*/ +static int l_strcmp (const TString *ls, const TString *rs) { + const char *l = getstr(ls); + size_t ll = tsslen(ls); + const char *r = getstr(rs); + size_t lr = tsslen(rs); + for (;;) { /* for each segment */ + int temp = strcoll(l, r); + if (temp != 0) /* not equal? */ + return temp; /* done */ + else { /* strings are equal up to a '\0' */ + size_t len = strlen(l); /* index of first '\0' in both strings */ + if (len == lr) /* 'rs' is finished? */ + return (len == ll) ? 0 : 1; /* check 'ls' */ + else if (len == ll) /* 'ls' is finished? */ + return -1; /* 'ls' is smaller than 'rs' ('rs' is not finished) */ + /* both strings longer than 'len'; go on comparing after the '\0' */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } } -static const TValue *get_equalTM (lua_State *L, Table *mt1, Table *mt2, - TMS event) { - const TValue *tm1 = fasttm(L, mt1, event); - const TValue *tm2; - if (tm1 == NULL) return NULL; /* no metamethod */ - if (mt1 == mt2) return tm1; /* same metatables => same metamethods */ - tm2 = fasttm(L, mt2, event); - if (tm2 == NULL) return NULL; /* no metamethod */ - if (luaV_rawequalobj(tm1, tm2)) /* same metamethods? */ - return tm1; - return NULL; +/* +** Check whether integer 'i' is less than float 'f'. If 'i' has an +** exact representation as a float ('l_intfitsf'), compare numbers as +** floats. Otherwise, if 'f' is outside the range for integers, result +** is trivial. Otherwise, compare them as integers. (When 'i' has no +** float representation, either 'f' is "far away" from 'i' or 'f' has +** no precision left for a fractional part; either way, how 'f' is +** truncated is irrelevant.) When 'f' is NaN, comparisons must result +** in false. +*/ +static int LTintfloat (lua_Integer i, lua_Number f) { +#if defined(l_intfitsf) + if (!l_intfitsf(i)) { + if (f >= -cast_num(LUA_MININTEGER)) /* -minint == maxint + 1 */ + return 1; /* f >= maxint + 1 > i */ + else if (f > cast_num(LUA_MININTEGER)) /* minint < f <= maxint ? */ + return (i < cast(lua_Integer, f)); /* compare them as integers */ + else /* f <= minint <= i (or 'f' is NaN) --> not(i < f) */ + return 0; + } +#endif + return luai_numlt(cast_num(i), f); /* compare them as floats */ } -static int call_orderTM (lua_State *L, const TValue *p1, const TValue *p2, - TMS event) { - if (!call_binTM(L, p1, p2, L->top, event)) - return -1; /* no metamethod */ - else - return !l_isfalse(L->top); +/* +** Check whether integer 'i' is less than or equal to float 'f'. +** See comments on previous function. +*/ +static int LEintfloat (lua_Integer i, lua_Number f) { +#if defined(l_intfitsf) + if (!l_intfitsf(i)) { + if (f >= -cast_num(LUA_MININTEGER)) /* -minint == maxint + 1 */ + return 1; /* f >= maxint + 1 > i */ + else if (f >= cast_num(LUA_MININTEGER)) /* minint <= f <= maxint ? */ + return (i <= cast(lua_Integer, f)); /* compare them as integers */ + else /* f < minint <= i (or 'f' is NaN) --> not(i <= f) */ + return 0; + } +#endif + return luai_numle(cast_num(i), f); /* compare them as floats */ } -static int l_strcmp (const TString *ls, const TString *rs) { - const char *l = getstr(ls); - size_t ll = ls->tsv.len; - const char *r = getstr(rs); - size_t lr = rs->tsv.len; - for (;;) { - int temp = strcoll(l, r); - if (temp != 0) return temp; - else { /* strings are equal up to a `\0' */ - size_t len = strlen(l); /* index of first `\0' in both strings */ - if (len == lr) /* r is finished? */ - return (len == ll) ? 0 : 1; - else if (len == ll) /* l is finished? */ - return -1; /* l is smaller than r (because r is not finished) */ - /* both strings longer than `len'; go on comparing (after the `\0') */ - len++; - l += len; ll -= len; r += len; lr -= len; - } +/* +** Return 'l < r', for numbers. +*/ +static int LTnum (const TValue *l, const TValue *r) { + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li < ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LTintfloat(li, fltvalue(r)); /* l < r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numlt(lf, fltvalue(r)); /* both are float */ + else if (luai_numisnan(lf)) /* 'r' is int and 'l' is float */ + return 0; /* NaN < i is always false */ + else /* without NaN, (l < r) <--> not(r <= l) */ + return !LEintfloat(ivalue(r), lf); /* not (r <= l) ? */ } } +/* +** Return 'l <= r', for numbers. +*/ +static int LEnum (const TValue *l, const TValue *r) { + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li <= ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LEintfloat(li, fltvalue(r)); /* l <= r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numle(lf, fltvalue(r)); /* both are float */ + else if (luai_numisnan(lf)) /* 'r' is int and 'l' is float */ + return 0; /* NaN <= i is always false */ + else /* without NaN, (l <= r) <--> not(r < l) */ + return !LTintfloat(ivalue(r), lf); /* not (r < l) ? */ + } +} + + +/* +** Main operation less than; return 'l < r'. +*/ int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { int res; - if (ttisnumber(l) && ttisnumber(r)) - return luai_numlt(L, nvalue(l), nvalue(r)); - else if (ttisstring(l) && ttisstring(r)) - return l_strcmp(rawtsvalue(l), rawtsvalue(r)) < 0; - else if ((res = call_orderTM(L, l, r, TM_LT)) < 0) - luaG_ordererror(L, l, r); + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LTnum(l, r); + else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) < 0; + else if ((res = luaT_callorderTM(L, l, r, TM_LT)) < 0) /* no metamethod? */ + luaG_ordererror(L, l, r); /* error */ return res; } +/* +** Main operation less than or equal to; return 'l <= r'. If it needs +** a metamethod and there is no '__le', try '__lt', based on +** l <= r iff !(r < l) (assuming a total order). If the metamethod +** yields during this substitution, the continuation has to know +** about it (to negate the result of r= 0) /* first try `le' */ + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LEnum(l, r); + else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; + else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* try 'le' */ return res; - else if ((res = call_orderTM(L, r, l, TM_LT)) < 0) /* else try `lt' */ - luaG_ordererror(L, l, r); - return !res; + else { /* try 'lt': */ + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ + res = luaT_callorderTM(L, r, l, TM_LT); + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ + if (res < 0) + luaG_ordererror(L, l, r); + return !res; /* result is negated */ + } } /* -** equality of Lua values. L == NULL means raw equality (no metamethods) +** Main operation for equality of Lua values; return 't1 == t2'. +** L == NULL means raw equality (no metamethods) */ -int luaV_equalobj_ (lua_State *L, const TValue *t1, const TValue *t2) { +int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; - lua_assert(ttisequal(t1, t2)); + if (ttype(t1) != ttype(t2)) { /* not the same variant? */ + if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER) + return 0; /* only numbers can be equal with different variants */ + else { /* two numbers with different variants */ + lua_Integer i1, i2; /* compare them as integers */ + return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); + } + } + /* values have same type and same variant */ switch (ttype(t1)) { case LUA_TNIL: return 1; - case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); + case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2)); + case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); case LUA_TLCF: return fvalue(t1) == fvalue(t2); - case LUA_TSHRSTR: return eqshrstr(rawtsvalue(t1), rawtsvalue(t2)); - case LUA_TLNGSTR: return luaS_eqlngstr(rawtsvalue(t1), rawtsvalue(t2)); + case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); + case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); case LUA_TUSERDATA: { if (uvalue(t1) == uvalue(t2)) return 1; else if (L == NULL) return 0; - tm = get_equalTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ); + tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } case LUA_TTABLE: { if (hvalue(t1) == hvalue(t2)) return 1; else if (L == NULL) return 0; - tm = get_equalTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); + tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } default: - lua_assert(iscollectable(t1)); return gcvalue(t1) == gcvalue(t2); } - if (tm == NULL) return 0; /* no TM? */ - callTM(L, tm, t1, t2, L->top, 1); /* call TM */ + if (tm == NULL) /* no TM? */ + return 0; /* objects are different */ + luaT_callTM(L, tm, t1, t2, L->top, 1); /* call TM */ return !l_isfalse(L->top); } +/* macro used by 'luaV_concat' to ensure that element at 'o' is a string */ +#define tostring(L,o) \ + (ttisstring(o) || (cvt2str(o) && (luaO_tostring(L, o), 1))) + +#define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) + +/* copy strings in stack from top - n up to top - 1 to buffer */ +static void copy2buff (StkId top, int n, char *buff) { + size_t tl = 0; /* size already copied */ + do { + size_t l = vslen(top - n); /* length of string being copied */ + memcpy(buff + tl, svalue(top - n), l * sizeof(char)); + tl += l; + } while (--n > 0); +} + + +/* +** Main operation for concatenation: concat 'total' values in the stack, +** from 'L->top - total' up to 'L->top - 1'. +*/ void luaV_concat (lua_State *L, int total) { lua_assert(total >= 2); do { StkId top = L->top; int n = 2; /* number of elements handled in this pass (at least 2) */ - if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { - if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) - luaG_concaterror(L, top-2, top-1); - } - else if (tsvalue(top-1)->len == 0) /* second operand is empty? */ - (void)tostring(L, top - 2); /* result is first operand */ - else if (ttisstring(top-2) && tsvalue(top-2)->len == 0) { + if (!(ttisstring(top-2) || cvt2str(top-2)) || !tostring(L, top-1)) + luaT_trybinTM(L, top-2, top-1, top-2, TM_CONCAT); + else if (isemptystr(top - 1)) /* second operand is empty? */ + cast_void(tostring(L, top - 2)); /* result is first operand */ + else if (isemptystr(top - 2)) { /* first operand is an empty string? */ setobjs2s(L, top - 2, top - 1); /* result is second op. */ } else { /* at least two non-empty string values; get as many as possible */ - size_t tl = tsvalue(top-1)->len; - char *buffer; - int i; - /* collect total length */ - for (i = 1; i < total && tostring(L, top-i-1); i++) { - size_t l = tsvalue(top-i-1)->len; - if (l >= (MAX_SIZET/sizeof(char)) - tl) + size_t tl = vslen(top - 1); + TString *ts; + /* collect total length and number of strings */ + for (n = 1; n < total && tostring(L, top - n - 1); n++) { + size_t l = vslen(top - n - 1); + if (l >= (MAX_SIZE/sizeof(char)) - tl) luaG_runerror(L, "string length overflow"); tl += l; } - buffer = luaZ_openspace(L, &G(L)->buff, tl); - tl = 0; - n = i; - do { /* concat all strings */ - size_t l = tsvalue(top-i)->len; - memcpy(buffer+tl, svalue(top-i), l * sizeof(char)); - tl += l; - } while (--i > 0); - setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); + if (tl <= LUAI_MAXSHORTLEN) { /* is result a short string? */ + char buff[LUAI_MAXSHORTLEN]; + copy2buff(top, n, buff); /* copy strings to buffer */ + ts = luaS_newlstr(L, buff, tl); + } + else { /* long string; copy strings directly to final result */ + ts = luaS_createlngstrobj(L, tl); + copy2buff(top, n, getstr(ts)); + } + setsvalue2s(L, top - n, ts); /* create result */ } total -= n-1; /* got 'n' strings to create 1 new */ L->top -= n-1; /* popped 'n' strings and pushed one */ @@ -332,18 +511,25 @@ void luaV_concat (lua_State *L, int total) { } +/* +** Main operation 'ra' = #rb'. +*/ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { const TValue *tm; - switch (ttypenv(rb)) { + switch (ttype(rb)) { case LUA_TTABLE: { Table *h = hvalue(rb); tm = fasttm(L, h->metatable, TM_LEN); if (tm) break; /* metamethod? break switch to call it */ - setnvalue(ra, cast_num(luaH_getn(h))); /* else primitive len */ + setivalue(ra, luaH_getn(h)); /* else primitive len */ return; } - case LUA_TSTRING: { - setnvalue(ra, cast_num(tsvalue(rb)->len)); + case LUA_TSHRSTR: { + setivalue(ra, tsvalue(rb)->shrlen); + return; + } + case LUA_TLNGSTR: { + setivalue(ra, tsvalue(rb)->u.lnglen); return; } default: { /* try metamethod */ @@ -353,21 +539,66 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { break; } } - callTM(L, tm, rb, rb, ra, 1); + luaT_callTM(L, tm, rb, rb, ra, 1); +} + + +/* +** Integer division; return 'm // n', that is, floor(m/n). +** C division truncates its result (rounds towards zero). +** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer, +** otherwise 'floor(q) == trunc(q) - 1'. +*/ +lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to divide by zero"); + return intop(-, 0, m); /* n==-1; avoid overflow with 0x80000...//-1 */ + } + else { + lua_Integer q = m / n; /* perform C division */ + if ((m ^ n) < 0 && m % n != 0) /* 'm/n' would be negative non-integer? */ + q -= 1; /* correct result for different rounding */ + return q; + } +} + + +/* +** Integer modulus; return 'm % n'. (Assume that C '%' with +** negative operands follows C99 behavior. See previous comment +** about luaV_div.) +*/ +lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to perform 'n%%0'"); + return 0; /* m % -1 == 0; avoid overflow with 0x80000...%-1 */ + } + else { + lua_Integer r = m % n; + if (r != 0 && (m ^ n) < 0) /* 'm/n' would be non-integer negative? */ + r += n; /* correct result for different rounding */ + return r; + } } -void luaV_arith (lua_State *L, StkId ra, const TValue *rb, - const TValue *rc, TMS op) { - TValue tempb, tempc; - const TValue *b, *c; - if ((b = luaV_tonumber(rb, &tempb)) != NULL && - (c = luaV_tonumber(rc, &tempc)) != NULL) { - lua_Number res = luaO_arith(op - TM_ADD + LUA_OPADD, nvalue(b), nvalue(c)); - setnvalue(ra, res); +/* number of bits in an integer */ +#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT) + +/* +** Shift left operation. (Shift right just negates 'y'.) +*/ +lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) { + if (y < 0) { /* shift right? */ + if (y <= -NBITS) return 0; + else return intop(>>, x, -y); + } + else { /* shift left */ + if (y >= NBITS) return 0; + else return intop(<<, x, y); } - else if (!call_binTM(L, rb, rc, ra, op)) - luaG_aritherror(L, rb, rc); } @@ -376,15 +607,15 @@ void luaV_arith (lua_State *L, StkId ra, const TValue *rb, ** whether there is a cached closure with the same upvalues needed by ** new closure to be created. */ -static Closure *getcached (Proto *p, UpVal **encup, StkId base) { - Closure *c = p->cache; +static LClosure *getcached (Proto *p, UpVal **encup, StkId base) { + LClosure *c = p->cache; if (c != NULL) { /* is there a cached closure? */ int nup = p->sizeupvalues; Upvaldesc *uv = p->upvalues; int i; for (i = 0; i < nup; i++) { /* check whether it has right upvalues */ TValue *v = uv[i].instack ? base + uv[i].idx : encup[uv[i].idx]->v; - if (c->l.upvals[i]->v != v) + if (c->upvals[i]->v != v) return NULL; /* wrong upvalue; cannot reuse closure */ } } @@ -394,26 +625,28 @@ static Closure *getcached (Proto *p, UpVal **encup, StkId base) { /* ** create a new Lua closure, push it in the stack, and initialize -** its upvalues. Note that the call to 'luaC_barrierproto' must come -** before the assignment to 'p->cache', as the function needs the -** original value of that field. +** its upvalues. Note that the closure is not cached if prototype is +** already black (which means that 'cache' was already cleared by the +** GC). */ static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, StkId ra) { int nup = p->sizeupvalues; Upvaldesc *uv = p->upvalues; int i; - Closure *ncl = luaF_newLclosure(L, nup); - ncl->l.p = p; + LClosure *ncl = luaF_newLclosure(L, nup); + ncl->p = p; setclLvalue(L, ra, ncl); /* anchor new closure in stack */ for (i = 0; i < nup; i++) { /* fill in its upvalues */ if (uv[i].instack) /* upvalue refers to local variable? */ - ncl->l.upvals[i] = luaF_findupval(L, base + uv[i].idx); + ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx); else /* get upvalue from enclosing function */ - ncl->l.upvals[i] = encup[uv[i].idx]; + ncl->upvals[i] = encup[uv[i].idx]; + ncl->upvals[i]->refcount++; + /* new closure is white, so we do not need a barrier here */ } - luaC_barrierproto(L, p, ncl); - p->cache = ncl; /* save it on cache for reuse */ + if (!isblack(p)) /* cache will not break GC invariant? */ + p->cache = ncl; /* save it on cache for reuse */ } @@ -426,8 +659,10 @@ void luaV_finishOp (lua_State *L) { Instruction inst = *(ci->u.l.savedpc - 1); /* interrupted instruction */ OpCode op = GET_OPCODE(inst); switch (op) { /* finish its execution */ - case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: - case OP_MOD: case OP_POW: case OP_UNM: case OP_LEN: + case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_IDIV: + case OP_BAND: case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: + case OP_MOD: case OP_POW: + case OP_UNM: case OP_BNOT: case OP_LEN: case OP_GETTABUP: case OP_GETTABLE: case OP_SELF: { setobjs2s(L, base + GETARG_A(inst), --L->top); break; @@ -435,18 +670,18 @@ void luaV_finishOp (lua_State *L) { case OP_LE: case OP_LT: case OP_EQ: { int res = !l_isfalse(L->top - 1); L->top--; - /* metamethod should not be called when operand is K */ - lua_assert(!ISK(GETARG_B(inst))); - if (op == OP_LE && /* "<=" using "<" instead? */ - ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE))) - res = !res; /* invert result */ + if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ + lua_assert(op == OP_LE); + ci->callstatus ^= CIST_LEQ; /* clear mark */ + res = !res; /* negate result */ + } lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); if (res != GETARG_A(inst)) /* condition failed? */ ci->u.l.savedpc++; /* skip jump instruction */ break; } case OP_CONCAT: { - StkId top = L->top - 1; /* top when 'call_binTM' was called */ + StkId top = L->top - 1; /* top when 'luaT_trybinTM' was called */ int b = GETARG_B(inst); /* first element to concatenate */ int total = cast_int(top - 1 - (base + b)); /* yet to concatenate */ setobj2s(L, top - 2, top); /* put TM result in proper position */ @@ -477,31 +712,32 @@ void luaV_finishOp (lua_State *L) { + /* -** some macros for common tasks in `luaV_execute' +** {================================================================== +** Function 'luaV_execute': main interpreter loop +** =================================================================== */ -#if !defined luai_runtimecheck -#define luai_runtimecheck(L, c) /* void */ -#endif + +/* +** some macros for common tasks in 'luaV_execute' +*/ #define RA(i) (base+GETARG_A(i)) -/* to be used after possible stack reallocation */ #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) -#define KBx(i) \ - (k + (GETARG_Bx(i) != 0 ? GETARG_Bx(i) - 1 : GETARG_Ax(*ci->u.l.savedpc++))) /* execute a jump instruction */ #define dojump(ci,i,e) \ { int a = GETARG_A(i); \ - if (a > 0) luaF_close(L, ci->u.l.base + a - 1); \ + if (a != 0) luaF_close(L, ci->u.l.base + a - 1); \ ci->u.l.savedpc += GETARG_sBx(i) + e; } /* for test instructions, execute the jump instruction that follows it */ @@ -511,96 +747,124 @@ void luaV_finishOp (lua_State *L) { #define Protect(x) { {x;}; base = ci->u.l.base; } #define checkGC(L,c) \ - Protect( luaC_condGC(L,{L->top = (c); /* limit of live values */ \ - luaC_step(L); \ - L->top = ci->top;}) /* restore top */ \ - luai_threadyield(L); ) + { luaC_condGC(L, L->top = (c), /* limit of live values */ \ + Protect(L->top = ci->top)); /* restore top */ \ + luai_threadyield(L); } + + +/* fetch an instruction and prepare its execution */ +#define vmfetch() { \ + i = *(ci->u.l.savedpc++); \ + if (L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) \ + Protect(luaG_traceexec(L)); \ + ra = RA(i); /* WARNING: any stack reallocation invalidates 'ra' */ \ + lua_assert(base == ci->u.l.base); \ + lua_assert(base <= L->top && L->top < L->stack + L->stacksize); \ +} +#define vmdispatch(o) switch(o) +#define vmcase(l) case l: +#define vmbreak break -#define arith_op(op,tm) { \ - TValue *rb = RKB(i); \ - TValue *rc = RKC(i); \ - if (ttisnumber(rb) && ttisnumber(rc)) { \ - lua_Number nb = nvalue(rb), nc = nvalue(rc); \ - setnvalue(ra, op(L, nb, nc)); \ - } \ - else { Protect(luaV_arith(L, ra, rb, rc, tm)); } } + +/* +** copy of 'luaV_gettable', but protecting the call to potential +** metamethod (which can reallocate the stack) +*/ +#define gettableProtected(L,t,k,v) { const TValue *slot; \ + if (luaV_fastget(L,t,k,slot,luaH_get)) { setobj2s(L, v, slot); } \ + else Protect(luaV_finishget(L,t,k,v,slot)); } + + +/* same for 'luaV_settable' */ +#define settableProtected(L,t,k,v) { const TValue *slot; \ + if (!luaV_fastset(L,t,k,slot,luaH_get,v)) \ + Protect(luaV_finishset(L,t,k,v,slot)); } -#define vmdispatch(o) switch(o) -#define vmcase(l,b) case l: {b} break; -#define vmcasenb(l,b) case l: {b} /* nb = no break */ void luaV_execute (lua_State *L) { CallInfo *ci = L->ci; LClosure *cl; TValue *k; StkId base; + ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */ newframe: /* reentry point when frame changes (call/return) */ lua_assert(ci == L->ci); - cl = clLvalue(ci->func); - k = cl->p->k; - base = ci->u.l.base; + cl = clLvalue(ci->func); /* local reference to function's closure */ + k = cl->p->k; /* local reference to function's constant table */ + base = ci->u.l.base; /* local copy of function's base */ /* main loop of interpreter */ for (;;) { - Instruction i = *(ci->u.l.savedpc++); + Instruction i; StkId ra; - if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && - (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { - Protect(traceexec(L)); - } - /* WARNING: several calls may realloc the stack and invalidate `ra' */ - ra = RA(i); - lua_assert(base == ci->u.l.base); - lua_assert(base <= L->top && L->top < L->stack + L->stacksize); + vmfetch(); vmdispatch (GET_OPCODE(i)) { - vmcase(OP_MOVE, + vmcase(OP_MOVE) { setobjs2s(L, ra, RB(i)); - ) - vmcase(OP_LOADK, + vmbreak; + } + vmcase(OP_LOADK) { TValue *rb = k + GETARG_Bx(i); setobj2s(L, ra, rb); - ) - vmcase(OP_LOADKX, + vmbreak; + } + vmcase(OP_LOADKX) { TValue *rb; lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); rb = k + GETARG_Ax(*ci->u.l.savedpc++); setobj2s(L, ra, rb); - ) - vmcase(OP_LOADBOOL, + vmbreak; + } + vmcase(OP_LOADBOOL) { setbvalue(ra, GETARG_B(i)); if (GETARG_C(i)) ci->u.l.savedpc++; /* skip next instruction (if C) */ - ) - vmcase(OP_LOADNIL, + vmbreak; + } + vmcase(OP_LOADNIL) { int b = GETARG_B(i); do { setnilvalue(ra++); } while (b--); - ) - vmcase(OP_GETUPVAL, + vmbreak; + } + vmcase(OP_GETUPVAL) { int b = GETARG_B(i); setobj2s(L, ra, cl->upvals[b]->v); - ) - vmcase(OP_GETTABUP, - int b = GETARG_B(i); - Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra)); - ) - vmcase(OP_GETTABLE, - Protect(luaV_gettable(L, RB(i), RKC(i), ra)); - ) - vmcase(OP_SETTABUP, - int a = GETARG_A(i); - Protect(luaV_settable(L, cl->upvals[a]->v, RKB(i), RKC(i))); - ) - vmcase(OP_SETUPVAL, + vmbreak; + } + vmcase(OP_GETTABUP) { + TValue *upval = cl->upvals[GETARG_B(i)]->v; + TValue *rc = RKC(i); + gettableProtected(L, upval, rc, ra); + vmbreak; + } + vmcase(OP_GETTABLE) { + StkId rb = RB(i); + TValue *rc = RKC(i); + gettableProtected(L, rb, rc, ra); + vmbreak; + } + vmcase(OP_SETTABUP) { + TValue *upval = cl->upvals[GETARG_A(i)]->v; + TValue *rb = RKB(i); + TValue *rc = RKC(i); + settableProtected(L, upval, rb, rc); + vmbreak; + } + vmcase(OP_SETUPVAL) { UpVal *uv = cl->upvals[GETARG_B(i)]; setobj(L, uv->v, ra); - luaC_barrier(L, uv, ra); - ) - vmcase(OP_SETTABLE, - Protect(luaV_settable(L, ra, RKB(i), RKC(i))); - ) - vmcase(OP_NEWTABLE, + luaC_upvalbarrier(L, uv); + vmbreak; + } + vmcase(OP_SETTABLE) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + settableProtected(L, ra, rb, rc); + vmbreak; + } + vmcase(OP_NEWTABLE) { int b = GETARG_B(i); int c = GETARG_C(i); Table *t = luaH_new(L); @@ -608,96 +872,252 @@ void luaV_execute (lua_State *L) { if (b != 0 || c != 0) luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c)); checkGC(L, ra + 1); - ) - vmcase(OP_SELF, + vmbreak; + } + vmcase(OP_SELF) { + const TValue *aux; StkId rb = RB(i); - setobjs2s(L, ra+1, rb); - Protect(luaV_gettable(L, rb, RKC(i), ra)); - ) - vmcase(OP_ADD, - arith_op(luai_numadd, TM_ADD); - ) - vmcase(OP_SUB, - arith_op(luai_numsub, TM_SUB); - ) - vmcase(OP_MUL, - arith_op(luai_nummul, TM_MUL); - ) - vmcase(OP_DIV, - arith_op(luai_numdiv, TM_DIV); - ) - vmcase(OP_MOD, - arith_op(luai_nummod, TM_MOD); - ) - vmcase(OP_POW, - arith_op(luai_numpow, TM_POW); - ) - vmcase(OP_UNM, + TValue *rc = RKC(i); + TString *key = tsvalue(rc); /* key must be a string */ + setobjs2s(L, ra + 1, rb); + if (luaV_fastget(L, rb, key, aux, luaH_getstr)) { + setobj2s(L, ra, aux); + } + else Protect(luaV_finishget(L, rb, rc, ra, aux)); + vmbreak; + } + vmcase(OP_ADD) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, intop(+, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numadd(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD)); } + vmbreak; + } + vmcase(OP_SUB) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, intop(-, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numsub(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SUB)); } + vmbreak; + } + vmcase(OP_MUL) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, intop(*, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_nummul(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MUL)); } + vmbreak; + } + vmcase(OP_DIV) { /* float division (always with floats) */ + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numdiv(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_DIV)); } + vmbreak; + } + vmcase(OP_BAND) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, intop(&, ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BAND)); } + vmbreak; + } + vmcase(OP_BOR) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, intop(|, ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BOR)); } + vmbreak; + } + vmcase(OP_BXOR) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, intop(^, ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BXOR)); } + vmbreak; + } + vmcase(OP_SHL) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, luaV_shiftl(ib, ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHL)); } + vmbreak; + } + vmcase(OP_SHR) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Integer ib; lua_Integer ic; + if (tointeger(rb, &ib) && tointeger(rc, &ic)) { + setivalue(ra, luaV_shiftl(ib, -ic)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHR)); } + vmbreak; + } + vmcase(OP_MOD) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, luaV_mod(L, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + lua_Number m; + luai_nummod(L, nb, nc, m); + setfltvalue(ra, m); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MOD)); } + vmbreak; + } + vmcase(OP_IDIV) { /* floor division */ + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (ttisinteger(rb) && ttisinteger(rc)) { + lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); + setivalue(ra, luaV_div(L, ib, ic)); + } + else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numidiv(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_IDIV)); } + vmbreak; + } + vmcase(OP_POW) { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + lua_Number nb; lua_Number nc; + if (tonumber(rb, &nb) && tonumber(rc, &nc)) { + setfltvalue(ra, luai_numpow(L, nb, nc)); + } + else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_POW)); } + vmbreak; + } + vmcase(OP_UNM) { TValue *rb = RB(i); - if (ttisnumber(rb)) { - lua_Number nb = nvalue(rb); - setnvalue(ra, luai_numunm(L, nb)); + lua_Number nb; + if (ttisinteger(rb)) { + lua_Integer ib = ivalue(rb); + setivalue(ra, intop(-, 0, ib)); + } + else if (tonumber(rb, &nb)) { + setfltvalue(ra, luai_numunm(L, nb)); } else { - Protect(luaV_arith(L, ra, rb, rb, TM_UNM)); + Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM)); } - ) - vmcase(OP_NOT, + vmbreak; + } + vmcase(OP_BNOT) { + TValue *rb = RB(i); + lua_Integer ib; + if (tointeger(rb, &ib)) { + setivalue(ra, intop(^, ~l_castS2U(0), ib)); + } + else { + Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT)); + } + vmbreak; + } + vmcase(OP_NOT) { TValue *rb = RB(i); int res = l_isfalse(rb); /* next assignment may change this value */ setbvalue(ra, res); - ) - vmcase(OP_LEN, + vmbreak; + } + vmcase(OP_LEN) { Protect(luaV_objlen(L, ra, RB(i))); - ) - vmcase(OP_CONCAT, + vmbreak; + } + vmcase(OP_CONCAT) { int b = GETARG_B(i); int c = GETARG_C(i); StkId rb; L->top = base + c + 1; /* mark the end of concat operands */ Protect(luaV_concat(L, c - b + 1)); - ra = RA(i); /* 'luav_concat' may invoke TMs and move the stack */ - rb = b + base; + ra = RA(i); /* 'luaV_concat' may invoke TMs and move the stack */ + rb = base + b; setobjs2s(L, ra, rb); checkGC(L, (ra >= rb ? ra + 1 : rb)); L->top = ci->top; /* restore top */ - ) - vmcase(OP_JMP, + vmbreak; + } + vmcase(OP_JMP) { dojump(ci, i, 0); - ) - vmcase(OP_EQ, + vmbreak; + } + vmcase(OP_EQ) { TValue *rb = RKB(i); TValue *rc = RKC(i); Protect( - if (cast_int(equalobj(L, rb, rc)) != GETARG_A(i)) + if (luaV_equalobj(L, rb, rc) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); ) - ) - vmcase(OP_LT, + vmbreak; + } + vmcase(OP_LT) { Protect( if (luaV_lessthan(L, RKB(i), RKC(i)) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); ) - ) - vmcase(OP_LE, + vmbreak; + } + vmcase(OP_LE) { Protect( if (luaV_lessequal(L, RKB(i), RKC(i)) != GETARG_A(i)) ci->u.l.savedpc++; else donextjump(ci); ) - ) - vmcase(OP_TEST, + vmbreak; + } + vmcase(OP_TEST) { if (GETARG_C(i) ? l_isfalse(ra) : !l_isfalse(ra)) ci->u.l.savedpc++; else donextjump(ci); - ) - vmcase(OP_TESTSET, + vmbreak; + } + vmcase(OP_TESTSET) { TValue *rb = RB(i); if (GETARG_C(i) ? l_isfalse(rb) : !l_isfalse(rb)) ci->u.l.savedpc++; @@ -705,27 +1125,30 @@ void luaV_execute (lua_State *L) { setobjs2s(L, ra, rb); donextjump(ci); } - ) - vmcase(OP_CALL, + vmbreak; + } + vmcase(OP_CALL) { int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra+b; /* else previous instruction set top */ if (luaD_precall(L, ra, nresults)) { /* C function? */ - if (nresults >= 0) L->top = ci->top; /* adjust results */ - base = ci->u.l.base; + if (nresults >= 0) + L->top = ci->top; /* adjust results */ + Protect((void)0); /* update 'base' */ } else { /* Lua function */ ci = L->ci; - ci->callstatus |= CIST_REENTRY; goto newframe; /* restart luaV_execute over new Lua function */ } - ) - vmcase(OP_TAILCALL, + vmbreak; + } + vmcase(OP_TAILCALL) { int b = GETARG_B(i); if (b != 0) L->top = ra+b; /* else previous instruction set top */ lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); - if (luaD_precall(L, ra, LUA_MULTRET)) /* C function? */ - base = ci->u.l.base; + if (luaD_precall(L, ra, LUA_MULTRET)) { /* C function? */ + Protect((void)0); /* update 'base' */ + } else { /* tail call: put called frame (n) in place of caller one (o) */ CallInfo *nci = L->ci; /* called frame */ @@ -748,13 +1171,13 @@ void luaV_execute (lua_State *L) { lua_assert(L->top == oci->u.l.base + getproto(ofunc)->maxstacksize); goto newframe; /* restart luaV_execute over new Lua function */ } - ) - vmcasenb(OP_RETURN, + vmbreak; + } + vmcase(OP_RETURN) { int b = GETARG_B(i); - if (b != 0) L->top = ra+b-1; if (cl->p->sizep > 0) luaF_close(L, base); - b = luaD_poscall(L, ra); - if (!(ci->callstatus & CIST_REENTRY)) /* 'ci' still the called one */ + b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra))); + if (ci->callstatus & CIST_FRESH) /* local 'ci' still from callee */ return; /* external invocation: return */ else { /* invocation via reentry: continue execution */ ci = L->ci; @@ -763,105 +1186,137 @@ void luaV_execute (lua_State *L) { lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL); goto newframe; /* restart luaV_execute over new Lua function */ } - ) - vmcase(OP_FORLOOP, - lua_Number step = nvalue(ra+2); - lua_Number idx = luai_numadd(L, nvalue(ra), step); /* increment index */ - lua_Number limit = nvalue(ra+1); - if (luai_numlt(L, 0, step) ? luai_numle(L, idx, limit) - : luai_numle(L, limit, idx)) { - ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ - setnvalue(ra, idx); /* update internal index... */ - setnvalue(ra+3, idx); /* ...and external index */ - } - ) - vmcase(OP_FORPREP, - const TValue *init = ra; - const TValue *plimit = ra+1; - const TValue *pstep = ra+2; - if (!tonumber(init, ra)) - luaG_runerror(L, LUA_QL("for") " initial value must be a number"); - else if (!tonumber(plimit, ra+1)) - luaG_runerror(L, LUA_QL("for") " limit must be a number"); - else if (!tonumber(pstep, ra+2)) - luaG_runerror(L, LUA_QL("for") " step must be a number"); - setnvalue(ra, luai_numsub(L, nvalue(ra), nvalue(pstep))); + } + vmcase(OP_FORLOOP) { + if (ttisinteger(ra)) { /* integer loop? */ + lua_Integer step = ivalue(ra + 2); + lua_Integer idx = intop(+, ivalue(ra), step); /* increment index */ + lua_Integer limit = ivalue(ra + 1); + if ((0 < step) ? (idx <= limit) : (limit <= idx)) { + ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + chgivalue(ra, idx); /* update internal index... */ + setivalue(ra + 3, idx); /* ...and external index */ + } + } + else { /* floating loop */ + lua_Number step = fltvalue(ra + 2); + lua_Number idx = luai_numadd(L, fltvalue(ra), step); /* inc. index */ + lua_Number limit = fltvalue(ra + 1); + if (luai_numlt(0, step) ? luai_numle(idx, limit) + : luai_numle(limit, idx)) { + ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + chgfltvalue(ra, idx); /* update internal index... */ + setfltvalue(ra + 3, idx); /* ...and external index */ + } + } + vmbreak; + } + vmcase(OP_FORPREP) { + TValue *init = ra; + TValue *plimit = ra + 1; + TValue *pstep = ra + 2; + lua_Integer ilimit; + int stopnow; + if (ttisinteger(init) && ttisinteger(pstep) && + forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) { + /* all values are integer */ + lua_Integer initv = (stopnow ? 0 : ivalue(init)); + setivalue(plimit, ilimit); + setivalue(init, intop(-, initv, ivalue(pstep))); + } + else { /* try making all values floats */ + lua_Number ninit; lua_Number nlimit; lua_Number nstep; + if (!tonumber(plimit, &nlimit)) + luaG_runerror(L, "'for' limit must be a number"); + setfltvalue(plimit, nlimit); + if (!tonumber(pstep, &nstep)) + luaG_runerror(L, "'for' step must be a number"); + setfltvalue(pstep, nstep); + if (!tonumber(init, &ninit)) + luaG_runerror(L, "'for' initial value must be a number"); + setfltvalue(init, luai_numsub(L, ninit, nstep)); + } ci->u.l.savedpc += GETARG_sBx(i); - ) - vmcasenb(OP_TFORCALL, + vmbreak; + } + vmcase(OP_TFORCALL) { StkId cb = ra + 3; /* call base */ setobjs2s(L, cb+2, ra+2); setobjs2s(L, cb+1, ra+1); setobjs2s(L, cb, ra); L->top = cb + 3; /* func. + 2 args (state and index) */ - Protect(luaD_call(L, cb, GETARG_C(i), 1)); + Protect(luaD_call(L, cb, GETARG_C(i))); L->top = ci->top; i = *(ci->u.l.savedpc++); /* go to next instruction */ ra = RA(i); lua_assert(GET_OPCODE(i) == OP_TFORLOOP); goto l_tforloop; - ) - vmcase(OP_TFORLOOP, + } + vmcase(OP_TFORLOOP) { l_tforloop: if (!ttisnil(ra + 1)) { /* continue loop? */ setobjs2s(L, ra, ra + 1); /* save control variable */ ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ } - ) - vmcase(OP_SETLIST, + vmbreak; + } + vmcase(OP_SETLIST) { int n = GETARG_B(i); int c = GETARG_C(i); - int last; + unsigned int last; Table *h; if (n == 0) n = cast_int(L->top - ra) - 1; if (c == 0) { lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); c = GETARG_Ax(*ci->u.l.savedpc++); } - luai_runtimecheck(L, ttistable(ra)); h = hvalue(ra); last = ((c-1)*LFIELDS_PER_FLUSH) + n; if (last > h->sizearray) /* needs more space? */ - luaH_resizearray(L, h, last); /* pre-allocate it at once */ + luaH_resizearray(L, h, last); /* preallocate it at once */ for (; n > 0; n--) { TValue *val = ra+n; luaH_setint(L, h, last--, val); - luaC_barrierback(L, obj2gco(h), val); + luaC_barrierback(L, h, val); } L->top = ci->top; /* correct top (in case of previous open call) */ - ) - vmcase(OP_CLOSURE, + vmbreak; + } + vmcase(OP_CLOSURE) { Proto *p = cl->p->p[GETARG_Bx(i)]; - Closure *ncl = getcached(p, cl->upvals, base); /* cached closure */ + LClosure *ncl = getcached(p, cl->upvals, base); /* cached closure */ if (ncl == NULL) /* no match? */ pushclosure(L, p, cl->upvals, base, ra); /* create a new one */ else setclLvalue(L, ra, ncl); /* push cashed closure */ checkGC(L, ra + 1); - ) - vmcase(OP_VARARG, - int b = GETARG_B(i) - 1; + vmbreak; + } + vmcase(OP_VARARG) { + int b = GETARG_B(i) - 1; /* required results */ int j; int n = cast_int(base - ci->func) - cl->p->numparams - 1; + if (n < 0) /* less arguments than parameters? */ + n = 0; /* no vararg arguments */ if (b < 0) { /* B == 0? */ b = n; /* get all var. arguments */ Protect(luaD_checkstack(L, n)); ra = RA(i); /* previous call may change the stack */ L->top = ra + n; } - for (j = 0; j < b; j++) { - if (j < n) { - setobjs2s(L, ra + j, base - n + j); - } - else { - setnilvalue(ra + j); - } - } - ) - vmcase(OP_EXTRAARG, + for (j = 0; j < b && j < n; j++) + setobjs2s(L, ra + j, base - n + j); + for (; j < b; j++) /* complete required results with nil */ + setnilvalue(ra + j); + vmbreak; + } + vmcase(OP_EXTRAARG) { lua_assert(0); - ) + vmbreak; + } } } } +/* }================================================================== */ + diff --git a/depends/lua/src/lzio.c b/depends/lua/src/lzio.c index 20efea983..c9e1f491f 100644 --- a/depends/lua/src/lzio.c +++ b/depends/lua/src/lzio.c @@ -1,15 +1,17 @@ /* -** $Id: lzio.c,v 1.35.1.1 2013/04/12 18:48:47 roberto Exp $ +** $Id: lzio.c,v 1.37 2015/09/08 15:41:05 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ - -#include - #define lzio_c #define LUA_CORE +#include "lprefix.h" + + +#include + #include "lua.h" #include "llimits.h" @@ -64,13 +66,3 @@ size_t luaZ_read (ZIO *z, void *b, size_t n) { return 0; } -/* ------------------------------------------------------------------------ */ -char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { - if (n > buff->buffsize) { - if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; - luaZ_resizebuffer(L, buff, n); - } - return buff->buffer; -} - - diff --git a/depends/md5/CMakeLists.txt b/depends/md5/CMakeLists.txt index 69e0cf0b3..45cbe7e06 100644 --- a/depends/md5/CMakeLists.txt +++ b/depends/md5/CMakeLists.txt @@ -1,3 +1,6 @@ project(dfhack-md5) ADD_LIBRARY(dfhack-md5 STATIC EXCLUDE_FROM_ALL md5.cpp md5wrapper.cpp) -IDE_FOLDER(dfhack-md5 "Depends") \ No newline at end of file +IDE_FOLDER(dfhack-md5 "Depends") +IF(UNIX) + SET_TARGET_PROPERTIES(dfhack-md5 PROPERTIES COMPILE_FLAGS "-Wno-strict-aliasing") +ENDIF() diff --git a/depends/md5/md5.cpp b/depends/md5/md5.cpp index 5ee4fb45f..044df259e 100644 --- a/depends/md5/md5.cpp +++ b/depends/md5/md5.cpp @@ -135,7 +135,7 @@ void MD5Final(unsigned char digest[16], MD5Context *ctx) MD5Transform(ctx->buf, (uint32_t *) ctx->in); byteReverse((unsigned char *) ctx->buf, 4); 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 */ } diff --git a/depends/md5/md5wrapper.cpp b/depends/md5/md5wrapper.cpp index 7feb25035..7e64f7914 100644 --- a/depends/md5/md5wrapper.cpp +++ b/depends/md5/md5wrapper.cpp @@ -16,10 +16,6 @@ * Petr Mrázek */ -#if defined(_MSC_VER) && _MSC_VER >= 1400 -#define _CRT_SECURE_NO_WARNINGS -#endif - //---------------------------------------------------------------------- //basic includes #include diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 4fec34125..ce35d25f1 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -2,79 +2,23 @@ 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. IF(CMAKE_COMPILER_IS_GNUCC) - EXECUTE_PROCESS(COMMAND ${CMAKE_C_COMPILER} -dumpversion - OUTPUT_VARIABLE GCC_VERSION) - STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION}) - LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) - LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) - #IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2)) - #GCC is too old - # SET(STL_HASH_OLD_GCC 1) - #ENDIF() - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") - SET(HAVE_HASH_MAP 0) - SET(HASH_MAP_CLASS unordered_map) - - #Check for all of the possible combinations of unordered_map and hash_map - - 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) - MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.") - ENDIF() - - FIND_PACKAGE(Threads) - -ELSE() - SET(HASH_MAP_H ) - SET(HASH_NAMESPACE std) - SET(HASH_SET_H ) - SET(HAVE_HASH_MAP 1) - SET(HAVE_HASH_SET 1) - SET(HASH_MAP_CLASS hash_map) - SET(HASH_SET_CLASS hash_set) +ENDIF() + +SET(HASH_MAP_H ) +SET(HASH_SET_H ) +SET(HASH_NAMESPACE std) +SET(HASH_MAP_CLASS unordered_map) +SET(HASH_SET_CLASS unordered_set) +SET(HAVE_HASH_MAP 1) +SET(HAVE_HASH_SET 1) + +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.") +ENDIF() + +IF(UNIX) + FIND_PACKAGE(Threads REQUIRED) ENDIF() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h") @@ -207,7 +151,10 @@ LIST(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS}) IF(CMAKE_COMPILER_IS_GNUCC) 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) + # Disable warnings for integer conversion to smaller type + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") ENDIF() INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/depends/protobuf/google/protobuf/stubs/hash.h b/depends/protobuf/google/protobuf/stubs/hash.h index 822d60501..b4b2da574 100644 --- a/depends/protobuf/google/protobuf/stubs/hash.h +++ b/depends/protobuf/google/protobuf/stubs/hash.h @@ -1,6 +1,6 @@ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. -// http://code.google.com/p/protobuf/ +// https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -37,184 +37,402 @@ #include #include -#include "config.h" -#if defined(HAVE_HASH_MAP) && defined(HAVE_HASH_SET) -#include HASH_MAP_H -#include HASH_SET_H +#define GOOGLE_PROTOBUF_HAVE_HASH_MAP 1 +#define GOOGLE_PROTOBUF_HAVE_HASH_SET 1 + +// Android +#if defined(__ANDROID__) +# undef GOOGLE_PROTOBUF_HAVE_HASH_MAP +# undef GOOGLE_PROTOBUF_HAVE_HASH_MAP + +// Use C++11 unordered_{map|set} if available. +#elif ((_LIBCPP_STD_VER >= 11) || \ + (((__cplusplus >= 201103L) || defined(__GXX_EXPERIMENTAL_CXX0X)) && \ + (__GLIBCXX__ > 20090421))) +# define GOOGLE_PROTOBUF_HAS_CXX11_HASH + +// For XCode >= 4.6: the compiler is clang with libc++. +// For earlier XCode version: the compiler is gcc-4.2.1 with libstdc++. +// libc++ provides and friends even in non C++11 mode, +// and it does not provide the tr1 library. Therefore the following macro +// checks against this special case. +// Note that we should not test the __APPLE_CC__ version number or the +// __clang__ macro, since the new compiler can still use -stdlib=libstdc++, in +// which case is not compilable without -std=c++11 +#elif defined(__APPLE_CC__) +# if __GNUC__ >= 4 +# define GOOGLE_PROTOBUF_HAS_TR1 +# else +// Not tested for gcc < 4... These setting can compile under 4.2.1 though. +# define GOOGLE_PROTOBUF_HASH_NAMESPACE __gnu_cxx +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# endif + +// Version checks for gcc. +#elif defined(__GNUC__) +// For GCC 4.x+, use tr1::unordered_map/set; otherwise, follow the +// instructions from: +// https://gcc.gnu.org/onlinedocs/libstdc++/manual/backwards.html +# if __GNUC__ >= 4 +# define GOOGLE_PROTOBUF_HAS_TR1 +# elif __GNUC__ >= 3 +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# if __GNUC__ == 3 && __GNUC_MINOR__ == 0 +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std // GCC 3.0 +# else +# define GOOGLE_PROTOBUF_HASH_NAMESPACE __gnu_cxx // GCC 3.1 and later +# endif +# else +# define GOOGLE_PROTOBUF_HASH_NAMESPACE +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# endif + +// Version checks for MSC. +// Apparently Microsoft decided to move hash_map *back* to the std namespace in +// MSVC 2010: +// http://blogs.msdn.com/vcblog/archive/2009/05/25/stl-breaking-changes-in-visual-studio-2010-beta-1.aspx +// And.. they are moved back to stdext in MSVC 2013 (haven't checked 2012). That +// said, use unordered_map for MSVC 2010 and beyond is our safest bet. +#elif defined(_MSC_VER) +# if _MSC_VER >= 1600 // Since Visual Studio 2010 +# define GOOGLE_PROTOBUF_HAS_CXX11_HASH +# define GOOGLE_PROTOBUF_HASH_COMPARE std::hash_compare +# elif _MSC_VER >= 1500 // Since Visual Studio 2008 +# define GOOGLE_PROTOBUF_HASH_NAMESPACE stdext +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# define GOOGLE_PROTOBUF_HASH_COMPARE stdext::hash_compare +# define GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE +# elif _MSC_VER >= 1310 +# define GOOGLE_PROTOBUF_HASH_NAMESPACE stdext +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# define GOOGLE_PROTOBUF_HASH_COMPARE stdext::hash_compare +# else +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS hash_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS hash_set +# define GOOGLE_PROTOBUF_HASH_COMPARE stdext::hash_compare +# endif + +// **ADD NEW COMPILERS SUPPORT HERE.** +// For other compilers, undefine the macro and fallback to use std::map, in +// google/protobuf/stubs/hash.h #else -#define MISSING_HASH -#include -#include +# undef GOOGLE_PROTOBUF_HAVE_HASH_MAP +# undef GOOGLE_PROTOBUF_HAVE_HASH_SET #endif -namespace google { -namespace protobuf { - -#ifdef MISSING_HASH - -// This system doesn't have hash_map or hash_set. Emulate them using map and -// set. - -// Make hash be the same as less. Note that everywhere where custom -// hash functions are defined in the protobuf code, they are also defined such -// that they can be used as "less" functions, which is required by MSVC anyway. -template -struct hash { - // Dummy, just to make derivative hash functions compile. - int operator()(const Key& key) { - GOOGLE_LOG(FATAL) << "Should never be called."; - return 0; - } - - inline bool operator()(const Key& a, const Key& b) const { - return a < b; - } -}; - -// Make sure char* is compared by value. -template <> -struct hash { - // Dummy, just to make derivative hash functions compile. - int operator()(const char* key) { - GOOGLE_LOG(FATAL) << "Should never be called."; - return 0; - } - - inline bool operator()(const char* a, const char* b) const { - return strcmp(a, b) < 0; - } -}; - -template , - typename EqualKey = int > -class hash_map : public std::map { -}; - -template , - typename EqualKey = int > -class hash_set : public std::set { -}; +#if defined(GOOGLE_PROTOBUF_HAS_CXX11_HASH) +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS unordered_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS unordered_set +#elif defined(GOOGLE_PROTOBUF_HAS_TR1) +# define GOOGLE_PROTOBUF_HASH_NAMESPACE std::tr1 +# include +# define GOOGLE_PROTOBUF_HASH_MAP_CLASS unordered_map +# include +# define GOOGLE_PROTOBUF_HASH_SET_CLASS unordered_set +#endif -#elif defined(_MSC_VER) && !defined(_STLPORT_VERSION) +# define GOOGLE_PROTOBUF_HASH_NAMESPACE_DECLARATION_START \ + namespace google { \ + namespace protobuf { +# define GOOGLE_PROTOBUF_HASH_NAMESPACE_DECLARATION_END }} -template -struct hash : public HASH_NAMESPACE::hash_compare { -}; - -// MSVC's hash_compare hashes based on the string contents but -// compares based on the string pointer. WTF? -class CstringLess { - public: - inline bool operator()(const char* a, const char* b) const { - return strcmp(a, b) < 0; - } -}; - -template <> -struct hash - : public HASH_NAMESPACE::hash_compare { -}; - -template , - typename EqualKey = int > -class hash_map : public HASH_NAMESPACE::hash_map< - Key, Data, HashFcn> { -}; - -template , - typename EqualKey = int > -class hash_set : public HASH_NAMESPACE::hash_set< - Key, HashFcn> { -}; +#undef GOOGLE_PROTOBUF_HAS_CXX11_HASH +#undef GOOGLE_PROTOBUF_HAS_TR1 +#if defined(GOOGLE_PROTOBUF_HAVE_HASH_MAP) && \ + defined(GOOGLE_PROTOBUF_HAVE_HASH_SET) #else +#define GOOGLE_PROTOBUF_MISSING_HASH +#include +#include +#endif -template -struct hash : public HASH_NAMESPACE::hash { -}; - -template -struct hash { - inline size_t operator()(const Key* key) const { - return reinterpret_cast(key); - } -}; - -// Unlike the old SGI version, the TR1 "hash" does not special-case char*. So, -// we go ahead and provide our own implementation. -template <> -struct hash { - inline size_t operator()(const char* str) const { - size_t result = 0; - for (; *str != '\0'; str++) { - result = 5 * result + *str; - } - return result; - } -}; - -template , - typename EqualKey = std::equal_to > -class hash_map : public HASH_NAMESPACE::HASH_MAP_CLASS< - Key, Data, HashFcn, EqualKey> { -}; - -template , - typename EqualKey = std::equal_to > -class hash_set : public HASH_NAMESPACE::HASH_SET_CLASS< - Key, HashFcn, EqualKey> { -}; +namespace google { + namespace protobuf { + +#ifdef GOOGLE_PROTOBUF_MISSING_HASH +#undef GOOGLE_PROTOBUF_MISSING_HASH + + // This system doesn't have hash_map or hash_set. Emulate them using map and + // set. + + // Make hash be the same as less. Note that everywhere where custom + // hash functions are defined in the protobuf code, they are also defined such + // that they can be used as "less" functions, which is required by MSVC anyway. + template + struct hash { + // Dummy, just to make derivative hash functions compile. + int operator()(const Key& key) { + GOOGLE_LOG(FATAL) << "Should never be called."; + return 0; + } + + inline bool operator()(const Key& a, const Key& b) const { + return a < b; + } + }; + + // Make sure char* is compared by value. + template <> + struct hash { + // Dummy, just to make derivative hash functions compile. + int operator()(const char* key) { + GOOGLE_LOG(FATAL) << "Should never be called."; + return 0; + } + + inline bool operator()(const char* a, const char* b) const { + return strcmp(a, b) < 0; + } + }; + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map : public std::map { + typedef std::map BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), + const Alloc& d = Alloc()) : BaseClass(b, d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set : public std::set { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; -#endif +#elif defined(_MSC_VER) && !defined(_STLPORT_VERSION) -template <> -struct hash { - inline size_t operator()(const string& key) const { - return hash()(key.c_str()); - } - - static const size_t bucket_size = 4; - static const size_t min_buckets = 8; - inline size_t operator()(const string& a, const string& b) const { - return a < b; - } -}; - -template -struct hash > { - inline size_t operator()(const pair& key) const { - size_t first_hash = hash()(key.first); - size_t second_hash = hash()(key.second); - - // FIXME(kenton): What is the best way to compute this hash? I have - // no idea! This seems a bit better than an XOR. - return first_hash * ((1 << 16) - 1) + second_hash; - } - - static const size_t bucket_size = 4; - static const size_t min_buckets = 8; - inline size_t operator()(const pair& a, - const pair& b) const { - return a < b; - } -}; - -// Used by GCC/SGI STL only. (Why isn't this provided by the standard -// library? :( ) -struct streq { - inline bool operator()(const char* a, const char* b) const { - return strcmp(a, b) == 0; - } -}; - -} // namespace protobuf + template + struct hash : public GOOGLE_PROTOBUF_HASH_COMPARE { + }; + + // MSVC's hash_compare hashes based on the string contents but + // compares based on the string pointer. WTF? + class CstringLess { + public: + inline bool operator()(const char* a, const char* b) const { + return strcmp(a, b) < 0; + } + }; + + template <> + struct hash + : public GOOGLE_PROTOBUF_HASH_COMPARE {}; + +#ifdef GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE + + template + struct InternalHashCompare : public GOOGLE_PROTOBUF_HASH_COMPARE { + InternalHashCompare() {} + InternalHashCompare(HashFcn hashfcn, EqualKey equalkey) + : hashfcn_(hashfcn), equalkey_(equalkey) {} + size_t operator()(const Key& key) const { return hashfcn_(key); } + bool operator()(const Key& key1, const Key& key2) const { + return !equalkey_(key1, key2); + } + HashFcn hashfcn_; + EqualKey equalkey_; + }; + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, InternalHashCompare, Alloc> { + typedef GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, InternalHashCompare, Alloc> BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), const Alloc& d = Alloc()) + : BaseClass(InternalHashCompare(b, c), d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_SET_CLASS< + Key, InternalHashCompare > { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + +#else // GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> { + typedef GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), + const Alloc& d = Alloc()) : BaseClass(a, b, c, d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_SET_CLASS< + Key, HashFcn, EqualKey> { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; +#endif // GOOGLE_PROTOBUF_CONTAINERS_NEED_HASH_COMPARE + +#else // defined(_MSC_VER) && !defined(_STLPORT_VERSION) + + template + struct hash : public GOOGLE_PROTOBUF_HASH_NAMESPACE::hash { + }; + + template + struct hash { + inline size_t operator()(const Key* key) const { + return reinterpret_cast(key); + } + }; + + // Unlike the old SGI version, the TR1 "hash" does not special-case char*. So, + // we go ahead and provide our own implementation. + template <> + struct hash { + inline size_t operator()(const char* str) const { + size_t result = 0; + for (; *str != '\0'; str++) { + result = 5 * result + *str; + } + return result; + } + }; + + template<> + struct hash { + size_t operator()(bool x) const { + return static_cast(x); + } + }; + + template , + typename EqualKey = std::equal_to, + typename Alloc = std::allocator< std::pair > > + class hash_map + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> { + typedef GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_MAP_CLASS< + Key, Data, HashFcn, EqualKey, Alloc> BaseClass; + + public: + hash_map(int a = 0, const HashFcn& b = HashFcn(), + const EqualKey& c = EqualKey(), + const Alloc& d = Alloc()) : BaseClass(a, b, c, d) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + + template , + typename EqualKey = std::equal_to > + class hash_set + : public GOOGLE_PROTOBUF_HASH_NAMESPACE::GOOGLE_PROTOBUF_HASH_SET_CLASS< + Key, HashFcn, EqualKey> { + public: + hash_set(int = 0) {} + + HashFcn hash_function() const { return HashFcn(); } + }; + +#endif // !GOOGLE_PROTOBUF_MISSING_HASH + + template <> + struct hash { + inline size_t operator()(const string& key) const { + return hash()(key.c_str()); + } + + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + inline bool operator()(const string& a, const string& b) const { + return a < b; + } + }; + + template + struct hash > { + inline size_t operator()(const pair& key) const { + size_t first_hash = hash()(key.first); + size_t second_hash = hash()(key.second); + + // FIXME(kenton): What is the best way to compute this hash? I have + // no idea! This seems a bit better than an XOR. + return first_hash * ((1 << 16) - 1) + second_hash; + } + + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + inline bool operator()(const pair& a, + const pair& b) const { + return a < b; + } + }; + + // Used by GCC/SGI STL only. (Why isn't this provided by the standard + // library? :( ) + struct streq { + inline bool operator()(const char* a, const char* b) const { + return strcmp(a, b) == 0; + } + }; + + } // namespace protobuf } // namespace google -#endif // GOOGLE_PROTOBUF_STUBS_HASH_H__ +#endif // GOOGLE_PROTOBUF_STUBS_HASH_H__ \ No newline at end of file diff --git a/depends/tinyxml/CMakeLists.txt b/depends/tinyxml/CMakeLists.txt index 7d924924f..313837cac 100644 --- a/depends/tinyxml/CMakeLists.txt +++ b/depends/tinyxml/CMakeLists.txt @@ -1,3 +1,5 @@ -project(dfhack-tinyxml) -ADD_LIBRARY(dfhack-tinyxml STATIC EXCLUDE_FROM_ALL tinystr.cpp tinyxml.cpp tinyxmlerror.cpp tinyxmlparser.cpp) -IDE_FOLDER(dfhack-tinyxml "Depends") \ No newline at end of file +if(NOT TinyXML_FOUND) + project(dfhack-tinyxml) + ADD_LIBRARY(dfhack-tinyxml STATIC EXCLUDE_FROM_ALL tinystr.cpp tinyxml.cpp tinyxmlerror.cpp tinyxmlparser.cpp) + IDE_FOLDER(dfhack-tinyxml "Depends") +endif() diff --git a/depends/tinyxml/tinystr.cpp b/depends/tinyxml/tinystr.cpp index 681250714..066576820 100644 --- a/depends/tinyxml/tinystr.cpp +++ b/depends/tinyxml/tinystr.cpp @@ -1,6 +1,5 @@ /* www.sourceforge.net/projects/tinyxml -Original file by Yves Berquin. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -22,10 +21,6 @@ must not be misrepresented as being the original software. distribution. */ -/* - * THIS FILE WAS ALTERED BY Tyge Løvset, 7. April 2005. - */ - #ifndef TIXML_USE_STL diff --git a/depends/tinyxml/tinystr.h b/depends/tinyxml/tinystr.h index 3c2aa9d54..89cca3341 100644 --- a/depends/tinyxml/tinystr.h +++ b/depends/tinyxml/tinystr.h @@ -1,6 +1,5 @@ /* www.sourceforge.net/projects/tinyxml -Original file by Yves Berquin. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -22,17 +21,6 @@ must not be misrepresented as being the original software. distribution. */ -/* - * THIS FILE WAS ALTERED BY Tyge Lovset, 7. April 2005. - * - * - completely rewritten. compact, clean, and fast implementation. - * - sizeof(TiXmlString) = pointer size (4 bytes on 32-bit systems) - * - fixed reserve() to work as per specification. - * - fixed buggy compares operator==(), operator<(), and operator>() - * - fixed operator+=() to take a const ref argument, following spec. - * - added "copy" constructor with length, and most compare operators. - * - added swap(), clear(), size(), capacity(), operator+(). - */ #ifndef TIXML_USE_STL @@ -106,13 +94,11 @@ class TiXmlString quit(); } - // = operator TiXmlString& operator = (const char * copy) { return assign( copy, (size_type)strlen(copy)); } - // = operator TiXmlString& operator = (const TiXmlString & copy) { return assign(copy.start(), copy.length()); diff --git a/depends/tinyxml/tinyxml.cpp b/depends/tinyxml/tinyxml.cpp index 5de21f6de..9c161dfcb 100644 --- a/depends/tinyxml/tinyxml.cpp +++ b/depends/tinyxml/tinyxml.cpp @@ -1,6 +1,6 @@ /* www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) +Original code by Lee Thomason (www.grinninglizard.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -31,6 +31,7 @@ distribution. #include "tinyxml.h" +FILE* TiXmlFOpen( const char* filename, const char* mode ); bool TiXmlBase::condenseWhiteSpace = true; @@ -161,6 +162,7 @@ void TiXmlNode::CopyTo( TiXmlNode* target ) const { target->SetValue (value.c_str() ); target->userData = userData; + target->location = location; } @@ -186,10 +188,11 @@ TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) assert( node->parent == 0 || node->parent == this ); assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); - if ( node->Type() == TiXmlNode::DOCUMENT ) + if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) { delete node; - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } @@ -210,9 +213,10 @@ TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) { - if ( addThis.Type() == TiXmlNode::DOCUMENT ) + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) { - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } TiXmlNode* node = addThis.Clone(); @@ -228,9 +232,10 @@ TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& if ( !beforeThis || beforeThis->parent != this ) { return 0; } - if ( addThis.Type() == TiXmlNode::DOCUMENT ) + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) { - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } @@ -260,9 +265,10 @@ TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& a if ( !afterThis || afterThis->parent != this ) { return 0; } - if ( addThis.Type() == TiXmlNode::DOCUMENT ) + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) { - if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); return 0; } @@ -289,9 +295,20 @@ TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& a TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) { + if ( !replaceThis ) + return 0; + if ( replaceThis->parent != this ) return 0; + if ( withThis.ToDocument() ) { + // A document can never be a child. Thanks to Noam. + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = withThis.Clone(); if ( !node ) return 0; @@ -317,6 +334,10 @@ TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& wit bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) { + if ( !removeThis ) { + return false; + } + if ( removeThis->parent != this ) { assert( 0 ); @@ -502,7 +523,7 @@ const TiXmlDocument* TiXmlNode::GetDocument() const TiXmlElement::TiXmlElement (const char * _value) - : TiXmlNode( TiXmlNode::ELEMENT ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) { firstChild = lastChild = 0; value = _value; @@ -511,7 +532,7 @@ TiXmlElement::TiXmlElement (const char * _value) #ifdef TIXML_USE_STL TiXmlElement::TiXmlElement( const std::string& _value ) - : TiXmlNode( TiXmlNode::ELEMENT ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) { firstChild = lastChild = 0; value = _value; @@ -520,17 +541,18 @@ TiXmlElement::TiXmlElement( const std::string& _value ) TiXmlElement::TiXmlElement( const TiXmlElement& copy) - : TiXmlNode( TiXmlNode::ELEMENT ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) { firstChild = lastChild = 0; copy.CopyTo( this ); } -void TiXmlElement::operator=( const TiXmlElement& base ) +TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) { ClearThis(); base.CopyTo( this ); + return *this; } @@ -564,9 +586,9 @@ const char* TiXmlElement::Attribute( const char* name ) const #ifdef TIXML_USE_STL const std::string* TiXmlElement::Attribute( const std::string& name ) const { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( node ) - return &node->ValueStr(); + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( attrib ) + return &attrib->ValueStr(); return 0; } #endif @@ -574,195 +596,202 @@ const std::string* TiXmlElement::Attribute( const std::string& name ) const const char* TiXmlElement::Attribute( const char* name, int* i ) const { - const char* s = Attribute( name ); - if ( i ) - { - if ( s ) { - *i = atoi( s ); - } - else { - *i = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( i ) { + attrib->QueryIntValue( i ); } } - return s; + return result; } #ifdef TIXML_USE_STL const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const { - const std::string* s = Attribute( name ); - if ( i ) - { - if ( s ) { - *i = atoi( s->c_str() ); - } - else { - *i = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( i ) { + attrib->QueryIntValue( i ); } } - return s; + return result; } #endif const char* TiXmlElement::Attribute( const char* name, double* d ) const { - const char* s = Attribute( name ); - if ( d ) - { - if ( s ) { - *d = atof( s ); - } - else { - *d = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( d ) { + attrib->QueryDoubleValue( d ); } } - return s; + return result; } #ifdef TIXML_USE_STL const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const { - const std::string* s = Attribute( name ); - if ( d ) - { - if ( s ) { - *d = atof( s->c_str() ); - } - else { - *d = 0; + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( d ) { + attrib->QueryDoubleValue( d ); } } - return s; + return result; } #endif int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} + + +int TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const { const TiXmlAttribute* node = attributeSet.Find( name ); if ( !node ) return TIXML_NO_ATTRIBUTE; - return node->QueryIntValue( ival ); + + int ival = 0; + int result = node->QueryIntValue( &ival ); + *value = (unsigned)ival; + return result; } -#ifdef TIXML_USE_STL -int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +int TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const { const TiXmlAttribute* node = attributeSet.Find( name ); if ( !node ) return TIXML_NO_ATTRIBUTE; - return node->QueryIntValue( ival ); + + int result = TIXML_WRONG_TYPE; + if ( StringEqual( node->Value(), "true", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "yes", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "1", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = true; + result = TIXML_SUCCESS; + } + else if ( StringEqual( node->Value(), "false", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "no", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "0", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = false; + result = TIXML_SUCCESS; + } + return result; +} + + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); } #endif int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) return TIXML_NO_ATTRIBUTE; - return node->QueryDoubleValue( dval ); + return attrib->QueryDoubleValue( dval ); } #ifdef TIXML_USE_STL int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) return TIXML_NO_ATTRIBUTE; - return node->QueryDoubleValue( dval ); + return attrib->QueryDoubleValue( dval ); } #endif void TiXmlElement::SetAttribute( const char * name, int val ) { - char buf[64]; - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%d", val ); - #else - sprintf( buf, "%d", val ); - #endif - SetAttribute( name, buf ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } } #ifdef TIXML_USE_STL void TiXmlElement::SetAttribute( const std::string& name, int val ) { - std::ostringstream oss; - oss << val; - SetAttribute( name, oss.str() ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } } #endif void TiXmlElement::SetDoubleAttribute( const char * name, double val ) { - char buf[256]; - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%f", val ); - #else - sprintf( buf, "%f", val ); - #endif - SetAttribute( name, buf ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } } -void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) -{ - #ifdef TIXML_USE_STL - TIXML_STRING _name( cname ); - TIXML_STRING _value( cvalue ); - #else - const char* _name = cname; - const char* _value = cvalue; - #endif - - TiXmlAttribute* node = attributeSet.Find( _name ); - if ( node ) - { - node->SetValue( _value ); - return; +#ifdef TIXML_USE_STL +void TiXmlElement::SetDoubleAttribute( const std::string& name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); } +} +#endif - TiXmlAttribute* attrib = new TiXmlAttribute( cname, cvalue ); - if ( attrib ) - { - attributeSet.Add( attrib ); - } - else - { - TiXmlDocument* document = GetDocument(); - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + +void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( cname ); + if ( attrib ) { + attrib->SetValue( cvalue ); } } #ifdef TIXML_USE_STL -void TiXmlElement::SetAttribute( const std::string& name, const std::string& _value ) +void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) { - TiXmlAttribute* node = attributeSet.Find( name ); - if ( node ) - { - node->SetValue( _value ); - return; - } - - TiXmlAttribute* attrib = new TiXmlAttribute( name, _value ); - if ( attrib ) - { - attributeSet.Add( attrib ); - } - else - { - TiXmlDocument* document = GetDocument(); - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); + if ( attrib ) { + attrib->SetValue( _value ); } } #endif @@ -881,14 +910,14 @@ const char* TiXmlElement::GetText() const } -TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { tabsize = 4; useMicrosoftBOM = false; ClearError(); } -TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { tabsize = 4; useMicrosoftBOM = false; @@ -898,7 +927,7 @@ TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode #ifdef TIXML_USE_STL -TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { tabsize = 4; useMicrosoftBOM = false; @@ -908,49 +937,33 @@ TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiX #endif -TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::DOCUMENT ) +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) { copy.CopyTo( this ); } -void TiXmlDocument::operator=( const TiXmlDocument& copy ) +TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) { Clear(); copy.CopyTo( this ); + return *this; } bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) { - // See STL_STRING_BUG below. - //StringToBuffer buf( value ); - return LoadFile( Value(), encoding ); } bool TiXmlDocument::SaveFile() const { - // See STL_STRING_BUG below. -// StringToBuffer buf( value ); -// -// if ( buf.buffer && SaveFile( buf.buffer ) ) -// return true; -// -// return false; return SaveFile( Value() ); } bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) { - // There was a really terrifying little bug here. The code: - // value = filename - // in the STL case, cause the assignment method of the std::string to - // be called. What is strange, is that the std::string had the same - // address as it's c_str() method, and so bad things happen. Looks - // like a bug in the Microsoft STL implementation. - // Add an extra string to avoid the crash. TIXML_STRING filename( _filename ); value = filename; @@ -995,11 +1008,6 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) return false; } - // If we have a file, assume it is all one big XML file, and read it in. - // The document parser may decide the document ends sooner than the entire file, however. - TIXML_STRING data; - data.reserve( length ); - // Subtle bug here. TinyXml did use fgets. But from the XML spec: // 2.11 End-of-Line Handling // @@ -1030,58 +1038,46 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) return false; } - const char* lastPos = buf; - const char* p = buf; + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; buf[length] = 0; while( *p ) { assert( p < (buf+length) ); - if ( *p == 0xa ) { - // Newline character. No special rules for this. Append all the characters - // since the last string, and include the newline. - data.append( lastPos, (p-lastPos+1) ); // append, include the newline - ++p; // move past the newline - lastPos = p; // and point to the new buffer (may be 0) - assert( p <= (buf+length) ); - } - else if ( *p == 0xd ) { - // Carriage return. Append what we have so far, then - // handle moving forward in the buffer. - if ( (p-lastPos) > 0 ) { - data.append( lastPos, p-lastPos ); // do not add the CR - } - data += (char)0xa; // a proper newline - - if ( *(p+1) == 0xa ) { - // Carriage return - new line sequence - p += 2; - lastPos = p; - assert( p <= (buf+length) ); - } - else { - // it was followed by something else...that is presumably characters again. - ++p; - lastPos = p; - assert( p <= (buf+length) ); + assert( q <= (buf+length) ); + assert( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; } } else { - ++p; + *q++ = *p++; } } - // Handle any left over characters. - if ( p-lastPos ) { - data.append( lastPos, p-lastPos ); - } - delete [] buf; - buf = 0; + assert( q <= (buf+length) ); + *q = 0; - Parse( data.c_str(), 0, encoding ); + Parse( buf, 0, encoding ); - if ( Error() ) - return false; - else - return true; + delete [] buf; + return !Error(); } @@ -1220,7 +1216,7 @@ void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) cons if (value.find ('\"') == TIXML_STRING::npos) { if ( cfile ) { - fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); } if ( str ) { (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; @@ -1228,7 +1224,7 @@ void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) cons } else { if ( cfile ) { - fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); } if ( str ) { (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; @@ -1266,9 +1262,9 @@ void TiXmlAttribute::SetDoubleValue( double _value ) { char buf [256]; #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%lf", _value); + TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); #else - sprintf (buf, "%lf", _value); + sprintf (buf, "%g", _value); #endif SetValue (buf); } @@ -1284,16 +1280,17 @@ double TiXmlAttribute::DoubleValue() const } -TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::COMMENT ) +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { copy.CopyTo( this ); } -void TiXmlComment::operator=( const TiXmlComment& base ) +TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) { Clear(); base.CopyTo( this ); + return *this; } @@ -1382,7 +1379,7 @@ TiXmlNode* TiXmlText::Clone() const TiXmlDeclaration::TiXmlDeclaration( const char * _version, const char * _encoding, const char * _standalone ) - : TiXmlNode( TiXmlNode::DECLARATION ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) { version = _version; encoding = _encoding; @@ -1394,7 +1391,7 @@ TiXmlDeclaration::TiXmlDeclaration( const char * _version, TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, const std::string& _encoding, const std::string& _standalone ) - : TiXmlNode( TiXmlNode::DECLARATION ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) { version = _version; encoding = _encoding; @@ -1404,16 +1401,17 @@ TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) - : TiXmlNode( TiXmlNode::DECLARATION ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) { copy.CopyTo( this ); } -void TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) { Clear(); copy.CopyTo( this ); + return *this; } @@ -1548,9 +1546,9 @@ void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) #ifdef TIXML_USE_STL -const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const { - for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) { if ( node->name == name ) return node; @@ -1558,23 +1556,22 @@ const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const return 0; } -/* -TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) { - for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( node->name == name ) - return node; + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); } - return 0; + return attrib; } -*/ #endif -const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const { - for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) { if ( strcmp( node->name.c_str(), name ) == 0 ) return node; @@ -1582,17 +1579,18 @@ const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const return 0; } -/* -TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) { - for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( strcmp( node->name.c_str(), name ) == 0 ) - return node; + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); } - return 0; + return attrib; } -*/ + #ifdef TIXML_USE_STL std::istream& operator>> (std::istream & in, TiXmlNode & base) diff --git a/depends/tinyxml/tinyxml.h b/depends/tinyxml/tinyxml.h index c6f40cc27..a3589e5b2 100644 --- a/depends/tinyxml/tinyxml.h +++ b/depends/tinyxml/tinyxml.h @@ -1,6 +1,6 @@ /* www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) +Original code by Lee Thomason (www.grinninglizard.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -63,21 +63,19 @@ distribution. #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) // Microsoft visual studio, version 2005 and higher. #define TIXML_SNPRINTF _snprintf_s - #define TIXML_SNSCANF _snscanf_s #define TIXML_SSCANF sscanf_s #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) // Microsoft visual studio, version 6 and higher. //#pragma message( "Using _sn* functions." ) #define TIXML_SNPRINTF _snprintf - #define TIXML_SNSCANF _snscanf #define TIXML_SSCANF sscanf #elif defined(__GNUC__) && (__GNUC__ >= 3 ) // GCC version 3 and higher.s //#warning( "Using sn* functions." ) #define TIXML_SNPRINTF snprintf - #define TIXML_SNSCANF snscanf #define TIXML_SSCANF sscanf #else + #define TIXML_SNPRINTF snprintf #define TIXML_SSCANF sscanf #endif #endif @@ -92,8 +90,8 @@ class TiXmlDeclaration; class TiXmlParsingData; const int TIXML_MAJOR_VERSION = 2; -const int TIXML_MINOR_VERSION = 5; -const int TIXML_PATCH_VERSION = 3; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 2; /* Internal structure for tracking location of items in the XML file. @@ -109,10 +107,11 @@ struct TiXmlCursor /** + Implements the interface to the "Visitor pattern" (see the Accept() method.) If you call the Accept() method, it requires being passed a TiXmlVisitor class to handle callbacks. For nodes that contain other nodes (Document, Element) you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves - are simple called with Visit(). + are simply called with Visit(). If you return 'true' from a Visit method, recursive parsing will continue. If you return false, no children of this node or its sibilings will be Visited. @@ -147,7 +146,7 @@ public: virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } /// Visit a comment node virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } - /// Visit an unknow node + /// Visit an unknown node virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } }; @@ -267,7 +266,6 @@ public: TIXML_NO_ERROR = 0, TIXML_ERROR, TIXML_ERROR_OPENING_FILE, - TIXML_ERROR_OUT_OF_MEMORY, TIXML_ERROR_PARSING_ELEMENT, TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, TIXML_ERROR_READING_ELEMENT_VALUE, @@ -288,6 +286,7 @@ public: protected: static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + inline static bool IsWhiteSpace( char c ) { return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); @@ -462,13 +461,13 @@ public: */ enum NodeType { - DOCUMENT, - ELEMENT, - COMMENT, - UNKNOWN, - TEXT, - DECLARATION, - TYPECOUNT + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT }; virtual ~TiXmlNode(); @@ -679,8 +678,8 @@ public: #endif /** Query the type (as an enumerated value, above) of this node. - The possible types are: DOCUMENT, ELEMENT, COMMENT, - UNKNOWN, TEXT, and DECLARATION. + The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, + TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. */ int Type() const { return type; } @@ -915,17 +914,14 @@ public: const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } - const TiXmlAttribute* Find( const char* _name ) const; - TiXmlAttribute* Find( const char* _name ) { - return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) ); - } - #ifdef TIXML_USE_STL - const TiXmlAttribute* Find( const std::string& _name ) const; - TiXmlAttribute* Find( const std::string& _name ) { - return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) ); - } + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif - #endif private: //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), @@ -954,7 +950,7 @@ public: TiXmlElement( const TiXmlElement& ); - void operator=( const TiXmlElement& base ); + TiXmlElement& operator=( const TiXmlElement& base ); virtual ~TiXmlElement(); @@ -987,6 +983,13 @@ public: does not exist, then TIXML_NO_ATTRIBUTE is returned. */ int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute(). + int QueryUnsignedAttribute( const char* name, unsigned* _value ) const; + /** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). + Note that '1', 'true', or 'yes' are considered true, while '0', 'false' + and 'no' are considered false. + */ + int QueryBoolAttribute( const char* name, bool* _value ) const; /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). int QueryDoubleAttribute( const char* name, double* _value ) const; /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). @@ -1000,11 +1003,21 @@ public: } #ifdef TIXML_USE_STL + /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). + int QueryStringAttribute( const char* name, std::string* _value ) const { + const char* cstr = Attribute( name ); + if ( cstr ) { + *_value = std::string( cstr ); + return TIXML_SUCCESS; + } + return TIXML_NO_ATTRIBUTE; + } + /** Template form of the attribute query which will try to read the attribute into the specified type. Very easy, very powerful, but be careful to make sure to call this with the correct type. - NOTE: This method doesn't work correctly for 'string' types. + NOTE: This method doesn't work correctly for 'string' types that contain spaces. @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE */ @@ -1020,13 +1033,8 @@ public: return TIXML_SUCCESS; return TIXML_WRONG_TYPE; } - /* - This is - in theory - a bug fix for "QueryValueAtribute returns truncated std::string" - but template specialization is hard to get working cross-compiler. Leaving the bug for now. - - // The above will fail for std::string because the space character is used as a seperator. - // Specialize for strings. Bug [ 1695429 ] QueryValueAtribute returns truncated std::string - template<> int QueryValueAttribute( const std::string& name, std::string* outValue ) const + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const { const TiXmlAttribute* node = attributeSet.Find( name ); if ( !node ) @@ -1034,7 +1042,6 @@ public: *outValue = node->ValueStr(); return TIXML_SUCCESS; } - */ #endif /** Sets an attribute of name to a given value. The attribute @@ -1053,6 +1060,8 @@ public: void SetAttribute( const std::string& name, const std::string& _value ); ///< STL std::string form. void SetAttribute( const std::string& name, int _value ); + ///< STL std::string form. + void SetDoubleAttribute( const std::string& name, double value ); #endif /** Sets an attribute of name to a given value. The attribute @@ -1144,7 +1153,6 @@ protected: const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); private: - TiXmlAttributeSet attributeSet; }; @@ -1155,13 +1163,13 @@ class TiXmlComment : public TiXmlNode { public: /// Constructs an empty comment. - TiXmlComment() : TiXmlNode( TiXmlNode::COMMENT ) {} + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} /// Construct a comment from text. - TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::COMMENT ) { + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { SetValue( _value ); } TiXmlComment( const TiXmlComment& ); - void operator=( const TiXmlComment& base ); + TiXmlComment& operator=( const TiXmlComment& base ); virtual ~TiXmlComment() {} @@ -1175,8 +1183,8 @@ public: */ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. /** Walk the XML tree visiting this node and all of its children. */ @@ -1209,7 +1217,7 @@ public: normal, encoded text. If you want it be output as a CDATA text element, set the parameter _cdata to 'true' */ - TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TEXT) + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) { SetValue( initValue ); cdata = false; @@ -1218,15 +1226,15 @@ public: #ifdef TIXML_USE_STL /// Constructor. - TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TEXT) + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) { SetValue( initValue ); cdata = false; } #endif - TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TEXT ) { copy.CopyTo( this ); } - void operator=( const TiXmlText& base ) { base.CopyTo( this ); } + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } // Write this text object to a FILE stream. virtual void Print( FILE* cfile, int depth ) const; @@ -1278,7 +1286,7 @@ class TiXmlDeclaration : public TiXmlNode { public: /// Construct an empty declaration. - TiXmlDeclaration() : TiXmlNode( TiXmlNode::DECLARATION ) {} + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} #ifdef TIXML_USE_STL /// Constructor. @@ -1293,7 +1301,7 @@ public: const char* _standalone ); TiXmlDeclaration( const TiXmlDeclaration& copy ); - void operator=( const TiXmlDeclaration& copy ); + TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); virtual ~TiXmlDeclaration() {} @@ -1346,11 +1354,11 @@ private: class TiXmlUnknown : public TiXmlNode { public: - TiXmlUnknown() : TiXmlNode( TiXmlNode::UNKNOWN ) {} + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} virtual ~TiXmlUnknown() {} - TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::UNKNOWN ) { copy.CopyTo( this ); } - void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); } + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } /// Creates a copy of this Unknown and returns it. virtual TiXmlNode* Clone() const; @@ -1359,8 +1367,8 @@ public: virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. /** Walk the XML tree visiting this node and all of its children. */ @@ -1396,7 +1404,7 @@ public: #endif TiXmlDocument( const TiXmlDocument& copy ); - void operator=( const TiXmlDocument& copy ); + TiXmlDocument& operator=( const TiXmlDocument& copy ); virtual ~TiXmlDocument() {} @@ -1423,14 +1431,10 @@ public: #ifdef TIXML_USE_STL bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. { -// StringToBuffer f( filename ); -// return ( f.buffer && LoadFile( f.buffer, encoding )); return LoadFile( filename.c_str(), encoding ); } bool SaveFile( const std::string& filename ) const ///< STL std::string version. { -// StringToBuffer f( filename ); -// return ( f.buffer && SaveFile( f.buffer )); return SaveFile( filename.c_str() ); } #endif @@ -1638,7 +1642,7 @@ public: TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } /// Copy constructor TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } - TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } /// Return a handle to the first child node. TiXmlHandle FirstChild() const; @@ -1799,4 +1803,3 @@ private: #endif #endif - diff --git a/depends/tinyxml/tinyxmlerror.cpp b/depends/tinyxml/tinyxmlerror.cpp index d24f63b2e..538c21d0b 100644 --- a/depends/tinyxml/tinyxmlerror.cpp +++ b/depends/tinyxml/tinyxmlerror.cpp @@ -31,12 +31,11 @@ distribution. // It also cleans up the code a bit. // -const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] = +const char* TiXmlBase::errorString[ TiXmlBase::TIXML_ERROR_STRING_COUNT ] = { "No error", "Error", "Failed to open file", - "Memory allocation failed.", "Error parsing Element.", "Failed to read Element name", "Error reading Element value.", diff --git a/depends/tinyxml/tinyxmlparser.cpp b/depends/tinyxml/tinyxmlparser.cpp index 5793f0528..81b7eae96 100644 --- a/depends/tinyxml/tinyxmlparser.cpp +++ b/depends/tinyxml/tinyxmlparser.cpp @@ -1,6 +1,6 @@ /* www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) +Original code by Lee Thomason (www.grinninglizard.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -40,7 +40,7 @@ distribution. // Note tha "PutString" hardcodes the same list. This // is less flexible than it appears. Changing the entries // or order will break putstring. -TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = +TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = { { "&", 5, '&' }, { "<", 4, '<' }, @@ -174,7 +174,7 @@ class TiXmlParsingData public: void Stamp( const char* now, TiXmlEncoding encoding ); - const TiXmlCursor& Cursor() { return cursor; } + const TiXmlCursor& Cursor() const { return cursor; } private: // Only used by the document! @@ -346,7 +346,7 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) continue; } - if ( IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) // Still using old rules for white space. + if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. ++p; else break; @@ -354,7 +354,7 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) } else { - while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' ) + while ( *p && IsWhiteSpace( *p ) ) ++p; } @@ -631,9 +631,9 @@ const char* TiXmlBase::ReadText( const char* p, } } } - if ( p ) + if ( p && *p ) p += strlen( endTag ); - return p; + return ( p && *p ) ? p : 0; } #ifdef TIXML_USE_STL @@ -825,7 +825,6 @@ TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) return 0; } - TiXmlDocument* doc = GetDocument(); p = SkipWhiteSpace( p, encoding ); if ( !p || !*p ) @@ -896,11 +895,6 @@ TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) // Set the parent, so it can report errors returnNode->parent = this; } - else - { - if ( doc ) - doc->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); - } return returnNode; } @@ -1083,7 +1077,6 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc TIXML_STRING endTag (""; // Check for and read attributes. Also look for an empty // tag or an end tag. @@ -1122,10 +1115,20 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc } // We should find the end tag now + // note that: + // and + // + // are both valid end tags. if ( StringEqual( p, endTag.c_str(), false, encoding ) ) { p += endTag.length(); - return p; + p = SkipWhiteSpace( p, encoding ); + if ( p && *p && *p == '>' ) { + ++p; + return p; + } + if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding ); + return 0; } else { @@ -1139,7 +1142,6 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc TiXmlAttribute* attrib = new TiXmlAttribute(); if ( !attrib ) { - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, pErr, data, encoding ); return 0; } @@ -1162,7 +1164,7 @@ const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc #endif if ( node ) { - node->SetValue( attrib->Value() ); + if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding ); delete attrib; return 0; } @@ -1191,8 +1193,7 @@ const char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXm if ( !textNode ) { - if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, encoding ); - return 0; + return 0; } if ( TiXmlBase::IsWhiteSpaceCondensed() ) @@ -1297,9 +1298,10 @@ const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc if ( !p ) { - if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); + if ( document ) + document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); } - if ( *p == '>' ) + if ( p && *p == '>' ) return p+1; return p; } @@ -1349,7 +1351,8 @@ const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc if ( !StringEqual( p, startTag, false, encoding ) ) { - document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + if ( document ) + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); return 0; } p += strlen( startTag ); @@ -1379,7 +1382,7 @@ const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc value.append( p, 1 ); ++p; } - if ( p ) + if ( p && *p ) p += strlen( endTag ); return p; @@ -1391,10 +1394,6 @@ const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlE p = SkipWhiteSpace( p, encoding ); if ( !p || !*p ) return 0; -// int tabsize = 4; -// if ( document ) -// tabsize = document->TabSize(); - if ( data ) { data->Stamp( p, encoding ); @@ -1446,7 +1445,7 @@ const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlE // its best, even without them. value = ""; while ( p && *p // existence - && !IsWhiteSpace( *p ) && *p != '\n' && *p != '\r' // whitespace + && !IsWhiteSpace( *p ) // whitespace && *p != '/' && *p != '>' ) // tag end { if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { @@ -1515,7 +1514,8 @@ const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncodi if ( !StringEqual( p, startTag, false, encoding ) ) { - document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + if ( document ) + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); return 0; } p += strlen( startTag ); @@ -1539,7 +1539,7 @@ const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncodi const char* end = "<"; p = ReadText( p, &value, ignoreWhite, end, false, encoding ); - if ( p ) + if ( p && *p ) return p-1; // don't truncate the '<' return 0; } diff --git a/depends/tthread/fast_mutex.h b/depends/tthread/fast_mutex.h index b4e712f44..a381b2c75 100644 --- a/depends/tthread/fast_mutex.h +++ b/depends/tthread/fast_mutex.h @@ -1,5 +1,5 @@ -/* -Copyright (c) 2010 Marcus Geelnard +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -39,7 +39,7 @@ freely, subject to the following restrictions: // Check if we can support the assembly language level implementation (otherwise // revert to the system API) #if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \ - (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || \ + (defined(_MSC_VER) && (defined(_M_IX86) /*|| defined(_M_X64)*/)) || \ (defined(__GNUC__) && (defined(__ppc__))) #define _FAST_MUTEX_ASM_ #else @@ -47,7 +47,16 @@ freely, subject to the following restrictions: #endif #if defined(_TTHREAD_WIN32_) + #define NOMINMAX + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif #else #ifdef _FAST_MUTEX_ASM_ #include @@ -237,3 +246,4 @@ class fast_mutex { } #endif // _FAST_MUTEX_H_ + diff --git a/depends/tthread/tinythread.cpp b/depends/tthread/tinythread.cpp index eb2dce0e6..176bc2ac8 100644 --- a/depends/tthread/tinythread.cpp +++ b/depends/tthread/tinythread.cpp @@ -1,5 +1,5 @@ -/* -Copyright (c) 2010 Marcus Geelnard +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -171,13 +171,17 @@ void * thread::wrapper_function(void * aArg) catch(...) { // Uncaught exceptions will terminate the application (default behavior - // according to the C++0x draft) + // according to C++11) std::terminate(); } +#if 0 + // DFHack fix: this code prevents join from freeing thread resources. + // The thread is no longer executing lock_guard guard(ti->mThread->mDataMutex); ti->mThread->mNotAThread = true; +#endif // The thread is responsible for freeing the startup information delete ti; @@ -228,9 +232,16 @@ void thread::join() { #if defined(_TTHREAD_WIN32_) WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); #elif defined(_TTHREAD_POSIX_) pthread_join(mHandle, NULL); #endif + +#if 1 + // DFHack patch: moved here from the wrapper function + lock_guard guard(mDataMutex); + mNotAThread = true; +#endif } } @@ -242,6 +253,21 @@ bool thread::joinable() const return result; } +void thread::detach() +{ + mDataMutex.lock(); + if(!mNotAThread) + { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); +} + thread::id thread::get_id() const { if(!joinable()) diff --git a/depends/tthread/tinythread.h b/depends/tthread/tinythread.h index f4f8c5b22..d575bae33 100644 --- a/depends/tthread/tinythread.h +++ b/depends/tthread/tinythread.h @@ -1,5 +1,5 @@ -/* -Copyright (c) 2010 Marcus Geelnard +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -31,7 +31,7 @@ freely, subject to the following restrictions: /// TinyThread++ is a minimal, portable implementation of basic threading /// classes for C++. /// -/// They closely mimic the functionality and naming of the C++0x standard, and +/// They closely mimic the functionality and naming of the C++11 standard, and /// should be easily replaceable with the corresponding std:: variants. /// /// @section port_sec Portability @@ -39,7 +39,7 @@ freely, subject to the following restrictions: /// classes, while for other systems, the POSIX threads API (pthread) is used. /// /// @section class_sec Classes -/// In order to mimic the threading API of the C++0x standard, subsets of +/// In order to mimic the threading API of the C++11 standard, subsets of /// several classes are provided. The fundamental classes are: /// @li tthread::thread /// @li tthread::mutex @@ -68,7 +68,15 @@ freely, subject to the following restrictions: // Platform specific includes #if defined(_TTHREAD_WIN32_) #define NOMINMAX + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif #else #include #include @@ -82,22 +90,22 @@ freely, subject to the following restrictions: /// TinyThread++ version (major number). #define TINYTHREAD_VERSION_MAJOR 1 /// TinyThread++ version (minor number). -#define TINYTHREAD_VERSION_MINOR 0 +#define TINYTHREAD_VERSION_MINOR 1 /// TinyThread++ version (full version). #define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) -// Do we have a fully featured C++0x compiler? +// Do we have a fully featured C++11 compiler? #if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) - #define _TTHREAD_CPP0X_ + #define _TTHREAD_CPP11_ #endif -// ...at least partial C++0x? -#if defined(_TTHREAD_CPP0X_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) - #define _TTHREAD_CPP0X_PARTIAL_ +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP11_PARTIAL_ #endif // Macro for disabling assignments of objects. -#ifdef _TTHREAD_CPP0X_PARTIAL_ +#ifdef _TTHREAD_CPP11_PARTIAL_ #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ name(const name&) = delete; \ name& operator=(const name&) = delete; @@ -109,18 +117,18 @@ freely, subject to the following restrictions: /// @def thread_local /// Thread local storage keyword. -/// A variable that is declared with the \c thread_local keyword makes the +/// A variable that is declared with the @c thread_local keyword makes the /// value of the variable local to each thread (known as thread-local storage, /// or TLS). Example usage: /// @code /// // This variable is local to each thread. /// thread_local int variable; /// @endcode -/// @note The \c thread_local keyword is a macro that maps to the corresponding -/// compiler directive (e.g. \c __declspec(thread)). While the C++0x standard +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard /// allows for non-trivial types (e.g. classes with constructors and -/// destructors) to be declared with the \c thread_local keyword, most pre-C++0x -/// compilers only allow for trivial types (e.g. \c int). So, to guarantee +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee /// portable code, only use trivial types for thread local storage. /// @note This directive is currently not supported on Mac OS X (it will give /// a compiler error), since compile-time TLS is not supported in the Mac OS X @@ -128,7 +136,7 @@ freely, subject to the following restrictions: /// not support this directive. /// @hideinitializer -#if !defined(_TTHREAD_CPP0X_) && !defined(thread_local) +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) #define thread_local __thread #else @@ -138,8 +146,8 @@ freely, subject to the following restrictions: /// Main name space for TinyThread++. -/// This namespace is more or less equivalent to the \c std namespace for the -/// C++0x thread classes. For instance, the tthread::mutex class corresponds to +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to /// the std::mutex class. namespace tthread { @@ -176,7 +184,7 @@ class mutex { /// Lock the mutex. /// The method will block the calling thread until a lock on the mutex can - /// be obtained. The mutex remains locked until \c unlock() is called. + /// be obtained. The mutex remains locked until @c unlock() is called. /// @see lock_guard inline void lock() { @@ -192,7 +200,7 @@ class mutex { /// Try to lock the mutex. /// The method will try to lock the mutex. If it fails, the function will /// return immediately (non-blocking). - /// @return \c true if the lock was acquired, or \c false if the lock could + /// @return @c true if the lock was acquired, or @c false if the lock could /// not be acquired. inline bool try_lock() { @@ -268,7 +276,7 @@ class recursive_mutex { /// Lock the mutex. /// The method will block the calling thread until a lock on the mutex can - /// be obtained. The mutex remains locked until \c unlock() is called. + /// be obtained. The mutex remains locked until @c unlock() is called. /// @see lock_guard inline void lock() { @@ -282,7 +290,7 @@ class recursive_mutex { /// Try to lock the mutex. /// The method will try to lock the mutex. If it fails, the function will /// return immediately (non-blocking). - /// @return \c true if the lock was acquired, or \c false if the lock could + /// @return @c true if the lock was acquired, or @c false if the lock could /// not be acquired. inline bool try_lock() { @@ -406,7 +414,7 @@ class condition_variable { /// Wait for the condition. /// The function will block the calling thread until the condition variable - /// is woken by \c notify_one(), \c notify_all() or a spurious wake up. + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. /// @param[in] aMutex A mutex that will be unlocked when the wait operation /// starts, an locked again as soon as the wait operation is finished. template @@ -482,7 +490,7 @@ class thread { class id; /// Default constructor. - /// Construct a \c thread object without an associated thread of execution + /// Construct a @c thread object without an associated thread of execution /// (i.e. non-joinable). thread() : mHandle(0), mNotAThread(true) #if defined(_TTHREAD_WIN32_) @@ -491,7 +499,7 @@ class thread { {} /// Thread starting constructor. - /// Construct a \c thread object with a new thread of execution. + /// Construct a @c thread object with a new thread of execution. /// @param[in] aFunction A function pointer to a function of type: /// void fun(void * arg) /// @param[in] aArg Argument to the thread function. @@ -501,24 +509,34 @@ class thread { thread(void (*aFunction)(void *), void * aArg); /// Destructor. - /// @note If the thread is joinable upon destruction, \c std::terminate() + /// @note If the thread is joinable upon destruction, @c std::terminate() /// will be called, which terminates the process. It is always wise to do - /// \c join() before deleting a thread object. + /// @c join() before deleting a thread object. ~thread(); /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). void join(); /// Check if the thread is joinable. /// A thread object is joinable if it has an associated thread of execution. bool joinable() const; + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + /// Return the thread ID of a thread object. id get_id() const; /// Get the native handle for this thread. - /// @note Under Windows, this is a \c HANDLE, and under POSIX systems, this - /// is a \c pthread_t. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. inline native_handle_type native_handle() { return mHandle; @@ -613,18 +631,18 @@ class thread::id { // Related to - minimal to be able to support chrono. typedef long long __intmax_t; -/// Minimal implementation of the \c ratio class. This class provides enough -/// functionality to implement some basic \c chrono classes. +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. template <__intmax_t N, __intmax_t D = 1> class ratio { public: static double _as_double() { return double(N) / double(D); } }; -/// Minimal implementation of the \c chrono namespace. -/// The \c chrono namespace provides types for specifying time intervals. +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. namespace chrono { /// Duration template class. This class provides enough functionality to - /// implement \c this_thread::sleep_for(). + /// implement @c this_thread::sleep_for(). template > class duration { private: _Rep rep_; @@ -652,7 +670,7 @@ namespace chrono { typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. } -/// The namespace \c this_thread provides methods for dealing with the +/// The namespace @c this_thread provides methods for dealing with the /// calling thread. namespace this_thread { /// Return the thread ID of the calling thread. diff --git a/depends/zlib/lib/win32/.gitignore b/depends/zlib/lib/win32/.gitignore new file mode 100644 index 000000000..683bf139f --- /dev/null +++ b/depends/zlib/lib/win32/.gitignore @@ -0,0 +1 @@ +*.lib diff --git a/depends/zlib/lib/win64/.gitignore b/depends/zlib/lib/win64/.gitignore new file mode 100644 index 000000000..683bf139f --- /dev/null +++ b/depends/zlib/lib/win64/.gitignore @@ -0,0 +1 @@ +*.lib diff --git a/depends/zlib/lib/zlib.lib b/depends/zlib/lib/zlib.lib deleted file mode 100644 index 51f579dd7..000000000 Binary files a/depends/zlib/lib/zlib.lib and /dev/null differ diff --git a/dfhack.init-example b/dfhack.init-example index a367d3441..bdf035bd3 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -48,6 +48,19 @@ keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" # export a Dwarf's preferences screen in BBCode to post to a forum keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves +# an in-game init file editor +keybinding add Alt-S@title gui/settings-manager +keybinding add Alt-S@dwarfmode/Default gui/settings-manager + +# change quantity of manager orders +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 # ############################## @@ -55,6 +68,8 @@ keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves keybinding add Ctrl-B@dungeonmode adv-bodyswap keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force" 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 # @@ -131,6 +146,8 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow keybinding add Alt-W@overallstatus "gui/workflow status" +# equivalent to the one above when gui/extended-status is enabled +keybinding add Alt-W@dfhack/lua/status_overlay "gui/workflow status" # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" @@ -138,6 +155,9 @@ keybinding add Shift-B@pet/List/Unit "gui/autobutcher" # assign weapon racks to squads so that they can be used keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack +# view pathable tiles from active cursor +keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable + ############################ # UI and game logic tweaks # ############################ @@ -178,15 +198,28 @@ tweak farm-plot-select # Add Shift-Left/Right controls to import agreement screen tweak import-priority-category +# Fixes a crash in the work order contition material list (bug 9905). +tweak condition-material + +# Adds an option to clear currently-bound hotkeys +tweak hotkey-clear + +# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving +tweak embark-profile-name + # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled +tweak burrow-name-cancel +tweak cage-butcher tweak civ-view-agreement +tweak eggs-fertile tweak fps-min tweak hide-priority -tweak kitchen-keys +tweak kitchen-prefs-all tweak kitchen-prefs-empty tweak max-wheelbarrow tweak shift-8-scroll +tweak stone-status-all tweak title-start-rename tweak tradereq-pet-gender @@ -220,7 +253,6 @@ enable \ zone \ stocks \ autochop \ - stockflow \ stockpiles #end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. # Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. @@ -228,7 +260,7 @@ enable \ # You can comment out the extension of a line. # enable mouse controls and sand indicator in embark screen -embark-tools enable sand mouse +embark-tools enable sticky sand mouse ########### # Scripts # @@ -237,14 +269,18 @@ embark-tools enable sand mouse # write extra information to the gamelog modtools/extra-gamelog enable +# extended status screen (bedrooms page) +enable gui/extended-status + # add information to item viewscreens view-item-info enable +# a replacement for the "load game" screen gui/load-screen enable -#roses-init must be called on world load if you are using his persist-delay, class system, etc -#base/roses-init -all +############################## +# Extra DFHack command files # +############################## -####################################################### -# Apply binary patches at runtime # -####################################################### +# Run commands in this file when a world loads +sc-script add SC_WORLD_LOADED onLoad.init-example diff --git a/docs/Authors.rst b/docs/Authors.rst index 29521320f..a67266006 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -12,17 +12,28 @@ Name Github Other 8Z 8Z acwatkins acwatkins Alexander Gavrilov angavrilov ag +Amostubal Amostubal AndreasPK AndreasPK Angus Mezick amezick Antalia tamarakorr Anuradha Dissanayake falconne +Atkana Atkana AtomicChicken AtomicChicken belal jimhester Ben Lubar BenLubar +Ben Rosser TC01 +brndd brndd +Bumber Bumber64 Caldfir caldfir +Carter Bray Qartar Chris Dombroski cdombroski Clayton Hughes +Clément Vuchener cvuchener +Dan Amlund danamlund +Daniel Brooks db48x David Corbett dscorbett +David Seguin dseguin +David Timm dtimm Deon DoctorVanGogh DoctorVanGogh Donald Ruegsegger hashaash @@ -33,7 +44,11 @@ Erik Youngren Artanis Espen Wiborg expwnent expwnent Feng +figment figment +gchristopher gchristopher +grubsteak grubsteak Harlan Playford playfordh +Hayati Ayguen hayguen IndigoFenix James Logsdon jlogsdon Japa JapaMala @@ -44,28 +59,39 @@ John Beisley huin John Shade gsvslto Jonas Ask kane-t kane-t -Kelly Martin ab9rf +Kelly Kinkade ab9rf Kris Parker kaypy +Kromtec Kromtec Kurik Amudnil Lethosor lethosor Mason11987 Mason11987 Matthew Cline +Matthew Lindner mlindner Max maxthyme Max^TM melkor217 melkor217 Meneth32 Meph +Michael Casadevall NCommander +Michael Crouch creidieki Michon van Dooren MaienM miffedmap miffedmap Mike Stewart thewonderidiot Mikko Juola Noeda Adeon +Milo Christiansen milochristiansen MithrilTuxedo MithrilTuxedo mizipzor mizipzor moversti moversti Neil Little nmlittle Nick Rart nickrart comestible +Nikolay Amiantov abbradar +nocico nocico Omniclasm +OwnageIsMagic OwnageIsMagic +Patrik Lundell PatrikLundell +Paul Fenwick pjf PeridexisErrant PeridexisErrant Petr Mrázek peterix +Pfhreak Pfhreak potato Priit Laes plaes Putnam Putnam3145 @@ -76,9 +102,12 @@ rampaging-poet Raoul van Putten Raoul XQ raoulxq reverb +Rich Rauenzahn rrauenza Rinin Rinin +rndmvar rndmvar Robert Heinrich rh73 Robert Janetzko robertjanetzko +RocheLimit rofl0r rofl0r root Roses Pheosics @@ -93,8 +122,11 @@ Sebastian Wolfertz Enkrod Seth Woodworth sethwoodworth simon Simon Jackson sizeak +stolencatkarma sv-esk sv-esk Tacomagic +TheHologram TheHologram +ThiagoLira ThiagoLira Tim Walberg twalberg Timothy Collett danaris Tom Jobbins TheBloke @@ -103,10 +135,12 @@ Travis Hoppe thoppe orthographic-pedant txtsd txtsd U-glouglou\\simon Valentin Ochs Cat-Ion +ViTuRaS ViTuRaS Vjek Warmist warmist Wes Malone wesQ3 Will Rogers wjrogers +ZechyW ZechyW Zhentar Zhentar zilpin zilpin ======================= ======================= =========================== diff --git a/docs/Binpatches.rst b/docs/Binpatches.rst index 53e1455b4..a0fcc6630 100644 --- a/docs/Binpatches.rst +++ b/docs/Binpatches.rst @@ -13,15 +13,14 @@ the `binpatch` command. We recommend using a script or plugin instead of a raw patch if at all possible - that way your work will work for many versions - across multiple operating systems. There's a reason nobody has - written patches since ``0.34.11``! + across multiple operating systems. .. contents:: Getting a patch =============== -There are no binary patches available for Dwarf Fortress versions after 0.34.11 +There are no binary patches available for Dwarf Fortress versions after 0.34.11. This system is kept for the chance that someone will find it useful, so some hints on how to write your own follow. This will require disassembly and @@ -29,7 +28,7 @@ decent skill in `memory research `. * The patches are expected to be encoded in text format used by IDA. -* See :commit:`8a9e3d1a728` for examples. +* See `the patches folder in commit b0e1b51 `_ for examples. * :issue:`546` is about the future of the binpatches, and may be useful reading. diff --git a/docs/Compile.rst b/docs/Compile.rst index 006c1d263..7d7dbeccd 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -31,11 +31,20 @@ To get the latest development code (develop branch), clone as above and then:: git checkout develop git submodule update +Generally, you should only need to clone DFHack once. + **Important note regarding submodule update and changing branches**: -You must run ``git submodule update`` every time you change Git branch, -for example when switching between master and develop branches and back. +You must run ``git submodule update`` every time you change branches, +such as when switching between the master and develop branches or vice versa. +If a submodule only exists on the newer branch, you also need to run +``git submodule update --init``. Failure to do this may result in strange +build errors or "not a known DF version" errors. + +**More notes**: +* `note-offline-builds` - read this if your build machine may not have an internet connection! +* `note-old-git-and-dfhack` Contributing to DFHack ====================== @@ -47,12 +56,18 @@ and whenever you need help. .. _IRC: https://webchat.freenode.net/?channels=dfhack +(Note: for submodule issues, please see the above instructions first!) + For lots more details on contributing to DFHack, including pull requests, code format, and more, please see `contributing-code`. -Build types -=========== +Build settings +============== + +Build type +---------- + ``cmake`` allows you to pick a build type by changing the ``CMAKE_BUILD_TYPE`` variable:: cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE @@ -62,8 +77,33 @@ Without specifying a build type or 'None', cmake uses the Valid and useful build types include 'Release', 'Debug' and 'RelWithDebInfo'. -'Debug' is not available on Windows, use 'RelWithDebInfo' instead. +'Debug' is not available on Windows; use 'RelWithDebInfo' instead. + +Target architecture (32-bit vs. 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 +change, so specifying it explicitly is a good idea. +:: + + cmake .. -DDFHACK_BUILD_ARCH=32 + +*or* +:: + + cmake .. -DDFHACK_BUILD_ARCH=64 + +Note that the scripts in the "build" folder on Windows will set the architecture +automatically. + +Other settings +-------------- +There are a variety of other settings which you can find in CMakeCache.txt in +your build folder or by running ``ccmake`` (or another CMake GUI). Most +DFHack-specific settings begin with ``BUILD_`` and control which parts of DFHack +are built. Linux ===== @@ -74,34 +114,19 @@ Dependencies DFHack is meant to be installed into an existing DF folder, so get one ready. We assume that any Linux platform will have ``git`` available (though it may -require installing from your package manager.) - -To build DFHack you need GCC version 4.5 or later, capable of compiling for 32-bit -(i386) targets. GCC 4.5 is easiest to work with due to avoiding libstdc++ issues -(see below), but any version from 4.5 onwards (including 5.x) will work. - -On 64-bit distributions, you'll need the multilib development tools and libraries: - -* ``gcc-multilib`` and ``g++-multilib`` -* If you have installed a non-default version of GCC - for example, GCC 4.5 on a - distribution that defaults to 5.x - you may need to add the version number to - the multilib packages. - - * For example, ``gcc-4.5-multilib`` and ``g++-4.5-multilib`` if installing for GCC 4.5 - on a system that uses a later GCC version. - * This is definitely required on Ubuntu/Debian, check if using a different distribution. +need to be installed with your package manager.) -Note that installing a 32-bit GCC on 64-bit systems (e.g. ``gcc:i386`` on Debian) will -typically *not* work, as it depends on several other 32-bit libraries that -conflict with system libraries. Alternatively, you might be able to use ``lxc`` -to -:forums:`create a virtual 32-bit environment <139553.msg5435310#msg5435310>`. +To build DFHack you need GCC version 4.8 or later. GCC 4.8 is easiest to work +with due to avoiding libstdc++ issues (see below), but any version from 4.8 +onwards (including 5.x) will work. -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 parts. +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 +parts. -You also need perl and the XML::LibXML and XML::LibXSLT perl packages (for the code generation parts). -You should be able to find them in your distro repositories. +You also need zlib, libsdl (1.2, not sdl2, like DF), perl, and the XML::LibXML +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. @@ -111,16 +136,34 @@ Here are some package install commands for various platforms: * For the required Perl modules: ``perl-xml-libxml`` and ``perl-xml-libxslt`` (or through ``cpan``) -* On 64-bit Ubuntu:: +* On Ubuntu:: + + apt-get install gcc cmake git zlib1g-dev libsdl1.2-dev libxml-libxml-perl libxml-libxslt-perl - apt-get install gcc cmake git gcc-multilib g++-multilib zlib1g-dev:i386 libxml-libxml-perl libxml-libxslt-perl +* Debian and derived distros should have similar requirements to Ubuntu. -* On 32-bit Ubuntu:: - apt-get install gcc cmake git gcc-multilib g++-multilib zlib1g-dev libxml-libxml-perl libxml-libxslt-perl +Multilib dependencies +--------------------- +If you want to compile 32-bit DFHack on 64-bit distributions, you'll need the +multilib development tools and libraries: -* Debian and derived distros should have similar requirements to Ubuntu. +* ``gcc-multilib`` and ``g++-multilib`` +* If you have installed a non-default version of GCC - for example, GCC 4.8 on a + distribution that defaults to 5.x - you may need to add the version number to + the multilib packages. + + * For example, ``gcc-4.8-multilib`` and ``g++-4.8-multilib`` if installing for GCC 4.8 + on a system that uses a later GCC version. + * This is definitely required on Ubuntu/Debian, check if using a different distribution. + +* ``zlib1g-dev:i386`` (or a similar i386 zlib-dev package) +Note that installing a 32-bit GCC on 64-bit systems (e.g. ``gcc:i386`` on +Debian) will typically *not* work, as it depends on several other 32-bit +libraries that conflict with system libraries. Alternatively, you might be able +to use ``lxc`` to +:forums:`create a virtual 32-bit environment <139553.msg5435310#msg5435310>`. Build ----- @@ -147,15 +190,16 @@ or the cmake-gui program. Incompatible libstdc++ ~~~~~~~~~~~~~~~~~~~~~~ -When compiling dfhack yourself, it builds against your system libstdc++. -When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which -comes from GCC 4.5 and is incompatible with code compiled with newer GCC versions. -This manifests itself with an error message such as:: +When compiling dfhack yourself, it builds against your system libstdc++. When +Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which comes +from GCC 4.8 and is incompatible with code compiled with newer GCC versions. If +you compile DFHack with a GCC version newer than 4.8, you will see an error +message such as:: ./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version - `GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so) + `GLIBCXX_3.4.18' not found (required by ./hack/libdfhack.so) -To fix this you can compile with GCC 4.5 or remove the libstdc++ shipped with +To fix this you can compile with GCC 4.8 or remove the libstdc++ shipped with DF, which causes DF to use your system libstdc++ instead:: cd /path/to/DF/ @@ -164,9 +208,9 @@ DF, which causes DF to use your system libstdc++ instead:: Note that distributing binaries compiled with newer GCC versions requires end- users to delete libstdc++ themselves and have a libstdc++ on their system from the same GCC version or newer. For this reason, distributing anything compiled -with GCC versions newer than 4.5 is discouraged. In the future we may start +with GCC versions newer than 4.8 is discouraged. In the future we may start bundling a later libstdc++ as part of the DFHack package, so as to enable -compilation-for-distribution with a GCC newer than 4.5. +compilation-for-distribution with a GCC newer than 4.8. Mac OS X ======== @@ -174,38 +218,48 @@ DFHack functions similarly on OS X and Linux, and the majority of the information above regarding the build process (cmake and make) applies here as well. -If you have issues building on OS X 10.10 (Yosemite) or above, try definining the -following environment variable:: +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`), +and your build will likely not be redistributable. + +.. _osx-new-gcc-notes: + +Notes for GCC 8+ or OS X 10.10+ users +------------------------------------- + +If none of these situations apply to you, skip to `osx-setup`. + +If you have issues building on OS X 10.10 (Yosemite) or above, try definining +the following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 -Note for El Capitan (OSX 10.11) and XCode 7.x users ---------------------------------------------------- +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 +``hack/libstdc++.6.dylib`` with a symlink to the ``libstdc++.6.dylib`` included +in your version of GCC:: -* You will probably find when following the instructions below that GCC 4.5 will - fail to install on OSX 10.11, or any older OSX that is using XCode 7. -* There are two workarounds: + cd /hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig && + ln -s [PATH_TO_LIBSTDC++] . - * Install GCC 5.x instead (``brew install gcc5``), and then after compile - replace ``hack/libstdc++.6.dylib`` with a symlink to GCC 5's i386 - version of this file:: +For example, with GCC 6.3.0, ``PATH_TO_LIBSTDC++`` would be:: - cd /hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig && - ln -s /usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/i386/libstdc++.6.dylib . + /usr/local/Cellar/gcc@6/6.3.0/lib/gcc/6/libstdc++.6.dylib # for 64-bit DFHack + /usr/local/Cellar/gcc@6/6.3.0/lib/gcc/6/i386/libstdc++.6.dylib # for 32-bit DFHack - * Install XCode 6, which is available as a free download from the Apple - Developer Center. +**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`` +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 +recommended to use GCC 4.8 or 7. - * Either install this as your only XCode, or install it additionally - to XCode 7 and then switch between them using ``xcode-select`` - * Ensure XCode 6 is active before attempting to install GCC 4.5 and - whenever you are compiling DFHack with GCC 4.5. +.. _osx-setup: Dependencies and system set-up ------------------------------ #. Download and unpack a copy of the latest DF -#. Install Xcode from Mac App Store +#. Install Xcode from the Mac App Store #. Install the XCode Command Line Tools by running the following command:: @@ -213,26 +267,26 @@ Dependencies and system set-up #. Install dependencies + It is recommended to use Homebrew instead of MacPorts, as it is generally + cleaner, quicker, and smarter. For example, installing MacPort's GCC will + install more than twice as many dependencies as Homebrew's will, and all in + both 32-bit and 64-bit variants. Homebrew also doesn't require constant use + of sudo. + Using `Homebrew `_ (recommended):: brew tap homebrew/versions brew install git brew install cmake - brew install gcc45 + brew install gcc@7 Using `MacPorts `_:: - sudo port install gcc45 +universal cmake +universal git-core +universal + sudo port install gcc7 +universal cmake +universal git-core +universal Macports will take some time - maybe hours. At some point it may ask you to install a Java environment; let it do so. - It is recommended to use Homebrew instead of MacPorts, as it is generally - cleaner, quicker, and smarter. For example, installing - MacPort's GCC 4.5 will install more than twice as many dependencies - as Homebrew's will, and all in both 32bit and 64bit variants. - Homebrew also doesn't require constant use of sudo. - #. Install Perl dependencies * Using system Perl @@ -270,16 +324,23 @@ Building Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: - export CC=/usr/local/bin/gcc-4.5 - export CXX=/usr/local/bin/g++-4.5 + export CC=/usr/local/bin/gcc-7 + export CXX=/usr/local/bin/g++-7 Macports:: - export CC=/opt/local/bin/gcc-mp-4.5 - export CXX=/opt/local/bin/g++-mp-4.5 + export CC=/opt/local/bin/gcc-mp-7 + export CXX=/opt/local/bin/g++-mp-7 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:: mkdir build-osx @@ -300,47 +361,34 @@ Dependencies ------------ You will need the following: -* Microsoft Visual Studio 2010 SP1, with the C++ language +* Microsoft Visual Studio 2015, with the C++ language * Git * CMake * Perl with XML::LibXML and XML::LibXSLT * It is recommended to install StrawberryPerl, which includes both. -Microsoft Visual Studio 2010 SP1 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -DFHack has to be compiled with the Microsoft Visual C++ 2010 SP1 toolchain; later -versions won't work against Dwarf Fortress due to ABI and STL incompatibilities. - -At present, the only way to obtain the MSVC C++ 2010 toolchain is to install a -full copy of Microsoft Visual Studio 2010 SP1. The free Express version is sufficient. +* Python (for documentation; optional, except for release builds) -You can grab it from `Microsoft's site `_. - -You should also install the Visual Studio 2010 SP1 update. - -You can confirm whether you have SP1 by opening the Visual Studio 2010 IDE -and selecting About from the Help menu. If you have SP1 it will have *SP1Rel* -at the end of the version number, for example: *Version 10.0.40219.1 SP1Rel* - -Use of pre-SP1 releases has been reported to cause issues and is therefore not -supported by DFHack. Please ensure you are using SP1 before raising any Issues. - -If your Windows Update is configured to receive updates for all Microsoft -Products, not just Windows, you will receive the SP1 update automatically -through Windows Update (you will probably need to trigger a manual check.) +Microsoft Visual Studio 2015 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +DFHack has to be compiled with the Microsoft Visual C++ 2015 toolchain; other +versions won't work against Dwarf Fortress due to ABI and STL incompatibilities. -If not, you can download it directly `from this Microsoft Download link `_. +At present, the only way to obtain the MSVC C++ 2015 toolchain is to install a +full copy of Microsoft Visual Studio 2015. The free Community version is +sufficient. Additional dependencies: installing with the Chocolatey Package Manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The remainder of dependencies - Git, CMake and StrawberryPerl - can be most -easily installed using the Chocolatey Package Manger. Chocolatey is a + +The remainder of dependencies - Git, CMake, StrawberryPerl, and Python - can be +most easily installed using the Chocolatey Package Manger. Chocolatey is a \*nix-style package manager for Windows. It's fast, small (8-20MB on disk) and very capable. Think "``apt-get`` for Windows." -Chocolatey is a preferred way of installing the required dependencies -as it's quicker, less effort and will install known-good utilities +Chocolatey is a recommended way of installing the required dependencies +as it's quicker, requires less effort, and will install known-good utilities guaranteed to have the correct setup (especially PATH). To install Chocolatey and the required dependencies: @@ -427,8 +475,10 @@ install XML::LibXML and XML::LibXSLT for it using CPAN. Build ----- -There are several different batch files in the ``build`` folder along -with a script that's used for picking the DF path. +There are several different batch files in the ``win32`` and ``win64`` +subfolders in the ``build`` folder, along with a script that's used for picking +the DF path. Use the subfolder corresponding to the architecture that you want +to build for. First, run ``set_df_path.vbs`` and point the dialog that pops up at a suitable DF installation which is of the appropriate version for the DFHack @@ -446,6 +496,9 @@ solution file(s): in, then hit configure, then generate. More options can appear after the configure step. * ``minimal`` will create a minimal solution with just the bare necessities - the main library and standard plugins. +* ``release`` will create a solution with everything that should be included in + release builds of DFHack. Note that this includes documentation, which requires + Python. Then you can either open the solution with MSVC or use one of the msbuild scripts: @@ -493,9 +546,10 @@ files as detailed above. Building/installing from the Visual Studio IDE: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -After running the CMake generate script you will have a new folder called VC2010. -Open the file ``dfhack.sln`` inside that folder. If you have multiple versions of -Visual Studio installed, make sure you open with Visual Studio 2010. +After running the CMake generate script you will have a new folder called VC2015 +or VC2015_32, depending on the architecture you specified. Open the file +``dfhack.sln`` inside that folder. If you have multiple versions of Visual +Studio installed, make sure you open with Visual Studio 2015. The first thing you must then do is change the build type. It defaults to Debug, but this cannot be used on Windows. Debug is not binary-compatible with DF. @@ -619,3 +673,73 @@ Chocolatey as outlined in the `Windows section `:: Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run:: 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 +=========== + +.. _note-offline-builds: + +Note on building DFHack offline +------------------------------- + +As of 0.43.05, DFHack downloads several files during the build process, depending +on your target OS and architecture. If your build machine's internet connection +is unreliable, or nonexistent, you can download these files in advance. + +First, you must locate the files you will need. These can be found in the +`dfhack-bin repo `_. Look for the +most recent version number *before or equal to* the DF version which you are +building for. For example, suppose "0.43.05" and "0.43.07" are listed. You should +choose "0.43.05" if you are building for 0.43.05 or 0.43.06, and "0.43.07" if +you are building for 0.43.07 or 0.43.08. + +Then, download all of the files you need, and save them to ``/CMake/downloads/``. The destination filename you choose +does not matter, as long as the files end up in the ``CMake/downloads`` folder. +You need to download all of the files for the architecture(s) you are building +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 +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 +you have downloaded everything at this point, assuming your download machine has +CMake installed. This involves running a "generate" batch script on Windows, or +a command starting with ``cmake ..`` on Linux and OS X, following the +instructions in the sections above. CMake should automatically locate files that +you placed in ``CMake/downloads``, and use them instead of attempting to +download them. + +.. _note-old-git-and-dfhack: + +Note on using very old git versions with pre-0.43.03 DFHack versions +-------------------------------------------------------------------- + +If you are using git 1.8.0 or older, and cloned DFHack before commit 85a920d +(around DFHack v0.43.03-alpha1), you may run into fatal git errors when updating +submodules after switching branches. This is due to those versions of git being +unable to handle our change from "scripts/3rdparty/name" submodules to a single +"scripts" submodule. This may be fixable by renaming .git/modules/scripts to +something else and re-running ``git submodule update --init`` on the branch with +the single scripts submodule (and running it again when switching back to the +one with multiple submodules, if necessary), but it is usually much simpler to +upgrade your git version. diff --git a/docs/Core.rst b/docs/Core.rst index a65d16e12..b15305fa9 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -4,6 +4,12 @@ DFHack Core ########### +.. contents:: + :depth: 2 + + +Command Implementation +====================== DFHack commands can be implemented in three ways, all of which are used in the same way: @@ -21,7 +27,73 @@ are used in the same way: Most third-party DFHack addons are scripts. -.. contents:: +Using DFHack Commands +===================== +DFHack commands can be executed in a number of ways: + +#. Typing the command into the DFHack console (see below) +#. From the OS terminal (see below) +#. Pressing a key combination set up with `keybinding` +#. From one of several `init-files`, automatically +#. Using `script` to run a batch of commands from a file + +The DFHack Console +------------------ +The command line has some nice line editing capabilities, including history +that's preserved between different runs of DF - use :kbd:`↑` and :kbd:`↓` +to go through the history. + +To include whitespace in the argument/s to some command, quote it in +double quotes. To include a double quote character, use ``\"``. + +If the first non-whitespace character is ``:``, the command is parsed in +an alternative mode. The non-whitespace characters following the ``:`` are +the command name, and the remaining part of the line is used verbatim as +the first argument. This is very useful for the `lua` and `rb` commands. +As an example, the following two command lines are exactly equivalent:: + + :foo a b "c d" e f + foo "a b \"c d\" e f" + +Using an OS terminal +-------------------- +DFHack commands can be run from an OS terminal at startup, using '+ args', +or at any other time using the ``dfhack-run`` executable. + +If DF/DFHack is started with arguments beginning with ``+``, the remaining +text is treated as a command in the DFHack console. It is possible to use +multiple such commands, which are split on ``+``. For example:: + + ./dfhack +load-save region1 + "Dwarf Fortress.exe" +devel/print-args Hello! +enable workflow + +The first example (\*nix), `load-save`, skips the main menu and loads +``region1`` immediately. The second (Windows) example prints +:guilabel:`Hello!` in the DFHack console, and `enables ` `workflow`. +Note that the ``:foo`` syntax for whitespace in arguments is not compatible \ +with '+ args'. + + +If DF and DFHack are already running, calling ``dfhack-run my command`` +in an external terminal is equivalent to calling ``my command`` in the +DFHack console. Direct use of the DFhack console is generally easier, +but ``dfhack-run`` can be useful in a variety of circumstances: + +- if the console is unavailable + + - with the init setting ``PRINT_MODE:TEXT`` + - while running an interactive command (eg. `liquids` or `tiletypes`) + +- from external programs or scripts +- if DF or DFHack are not responding + +Examples:: + + ./dfhack-run cursecheck + dfhack-run multicmd kill-lua; die + +The first (\*nix) example `checks for vampires `; the +second (Windows) example uses `kill-lua` to cancel a script and exits. Built-in Commands @@ -29,6 +101,35 @@ Built-in Commands The following commands are provided by the 'core' components of DFhack, rather than plugins or scripts. +.. contents:: + :local: + + +.. _alias: + +alias +----- +The ``alias`` command allows configuring aliases to other DFHack commands. +Aliases are resolved immediately after built-in commands, which means that an +alias cannot override a built-in command, but can override a command implemented +by a plugin or script. + +Usage: + +:``alias list``: lists all configured aliases +:``alias add [arguments...]``: adds an alias +:``alias replace [arguments...]``: replaces an existing + alias with a new command, or adds the alias if it does not already exist +:``alias delete ``: removes the specified alias + +Aliases can be given additional arguments when created and invoked, which will +be passed to the underlying command in order. An example with `devel/print-args`:: + + [DFHack]# alias add pargs devel/print-args example + [DFHack]# pargs text + example + text + .. _cls: @@ -245,6 +346,10 @@ The following commands are *not* built-in, but offer similarly useful functions. Init Files ========== + +.. contents:: + :local: + DFHack allows users to automatically run commonly-used DFHack commands when DF is first loaded, when a game is loaded, and when a game is unloaded. @@ -325,38 +430,57 @@ Other init files 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 =================== This section is for odd but important notes that don't fit anywhere else. -* The ``dfhack-run`` executable is there for calling DFHack commands in - an already running DF+DFHack instance from external OS scripts and programs, - and is *not* the way how you use DFHack normally. - * If a DF :kbd:`H` hotkey is named with a DFHack command, pressing the corresponding :kbd:`Fx` button will run that command, instead of zooming to the set location. - -* The command line has some nice line editing capabilities, including history - that's preserved between different runs of DF (use up/down keys to go through - the history). + *This feature will be removed in a future version.* (see :issue:`731`) * The binaries for 0.40.15-r1 to 0.34.11-r4 are on DFFD_. Older versions are available here_. + *These files will eventually be migrated to GitHub.* (see :issue:`473`) .. _DFFD: http://dffd.bay12games.com/search.php?string=DFHack&id=15&limit=1000 .. _here: http://dethware.org/dfhack/download - -* To include whitespace in the argument/s to some command, quote it in - double quotes. To include a double quote character, use ``\"``. - -* If the first non-whitespace character is ``:``, the command is parsed in - an alternative mode which is very useful for the `lua` and `rb` commands. - The following two command lines are exactly equivalent:: - - :foo a b "c d" e f - foo "a b \"c d\" e f" - - * non-whitespace characters following the ``:`` are the command name - * the remaining part of the line is used verbatim as the first argument - diff --git a/docs/History.rst b/docs/History.rst index b399fbbdd..8386c4cab 100644 --- a/docs/History.rst +++ b/docs/History.rst @@ -12,11 +12,1005 @@ in ``NEWS.rst`` doesn't get too long. .. contents:: :depth: 2 +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 `: fixes a crash in the work order condition material list +- `tweak hotkey-clear `: 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.05-beta2 +==================== + +Fixes +----- +- 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 +---------- +- 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 `: fixes a crash in the work order condition material list +- Added `tweak hotkey-clear `: 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 +------------- +- 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 + +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 ` 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 ` 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 ` 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 `: Fixes the in-game minimum FPS setting +- `shift-8-scroll `: Gives Shift+8 (or ``*``) priority when scrolling menus, instead of scrolling the map +- `tradereq-pet-gender `: 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 `: 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 DFHack 0.40.23-r1 ================= Internals +--------- - plugins will not be loaded if globals they specify as required are not located (should prevent some crashes) Fixes @@ -221,7 +1215,8 @@ DFHack 0.40.11-r1 ================= Internals -- Plugins on OS X now use ``.plug.dylib` as an extension instead of ``.plug.so`` +--------- +- Plugins on OS X now use ``.plug.dylib`` as an extension instead of ``.plug.so`` Fixes ----- diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 64b6b36a5..3a36aa538 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -50,12 +50,12 @@ Installing DFhack involves copying files from a release archive into your DF folder, so that: * On Windows, ``SDL.dll`` is replaced -* On Linux or OSX, the ``dfhack`` script is placed in the same folder as the ``df`` script +* On Linux or OS X, the ``dfhack`` script is placed in the same folder as the ``df`` script Uninstalling is basically the same, in reverse: * On Windows, replace ``SDL.dll`` with ``SDLreal.dll``, then remove the DFHack files. -* On Linux or OSX, remove the DFHack files. +* On Linux or OS X, remove the DFHack files. New players may wish to :wiki:`get a pack ` with DFHack preinstalled. diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2db98abad..8190de4c1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -125,12 +125,18 @@ All typed objects have the following built-in features: * ``ref:delete()`` - Destroys the object with the C++ ``delete`` operator. - If destructor is not available, returns *false*. + Destroys the object with the C++ ``delete`` operator. If the destructor is not + available, returns *false*. (This typically only occurs when trying to delete + an instance of a DF class with virtual methods whose vtable address has not + been found; it is impossible for ``delete()`` to determine the validity of + ``ref``.) .. warning:: - the lua reference object remains as a dangling - pointer, like a raw C++ pointer would. + ``ref`` **must** be an object allocated with ``new``, like in C++. Calling + ``obj.field:delete()`` where ``obj`` was allocated with ``new`` will not + work. After ``delete()`` returns, ``ref`` remains as a dangling pointer, + like a raw C++ pointer would. Any accesses to ``ref`` after ``ref:delete()`` + has been called are undefined behavior. * ``ref:assign(object)`` @@ -798,6 +804,9 @@ Random number generation C++ function wrappers ===================== +.. contents:: + :local: + Thin wrappers around C++ functions, similar to the ones for virtual methods. One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments @@ -876,6 +885,9 @@ proper display on all platforms. Gui module ---------- +Screens +~~~~~~~ + * ``dfhack.gui.getCurViewscreen([skip_dismissed])`` Returns the topmost viewscreen. If ``skip_dismissed`` is *true*, @@ -896,6 +908,9 @@ Gui module 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. +General-purpose selections +~~~~~~~~~~~~~~~~~~~~~~~~~~ + * ``dfhack.gui.getSelectedWorkshopJob([silent])`` When a job is selected in :kbd:`q` mode, returns the job, else @@ -921,6 +936,57 @@ Gui module Returns the building selected via :kbd:`q`, :kbd:`t`, :kbd:`k` or :kbd:`i`. +* ``dfhack.gui.getSelectedPlant([silent])`` + + 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)`` Writes a string to :file:`gamelog.txt` without doing an announcement. @@ -965,6 +1031,13 @@ Gui module Uses the type to look up options from announcements.txt, and calls the above 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 ---------- @@ -1055,6 +1128,13 @@ Units module Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. +* ``dfhack.getUnitsInBox(x1,y1,z1,x2,y2,z2[,filter])`` + + Returns a table of all units within the specified coordinates. If the ``filter`` + argument is given, only units where ``filter(unit)`` returns true will be included. + Note that ``pos2xyz()`` cannot currently be used to convert coordinate objects to + the arguments required by this function. + * ``dfhack.units.getGeneralRef(unit, type)`` Searches for a general_ref with the given type. @@ -1124,6 +1204,10 @@ Units module The unit is an alive sane citizen of the fortress; wraps the same checks the game uses to decide game-over by extinction. +* ``dfhack.units.isVisible(unit)`` + + The unit is visible on the map. + * ``dfhack.units.getAge(unit[,true_age])`` Returns the age of the unit in years as a floating-point value. @@ -1269,6 +1353,29 @@ Items module 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 ----------- @@ -1289,6 +1396,10 @@ Maps module Checks if the given df::coord or x,y,z in local tile coordinates are valid. +* ``dfhack.maps.isTileVisible(coords)``, or ``isTileVisible(x,y,z)`` + + Checks if the given df::coord or x,y,z in local tile coordinates is visible. + * ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)`` Returns a map block object for given df::coord or x,y,z in local tile coordinates. @@ -1467,6 +1578,12 @@ General Returns a list of items stored on the given stockpile. Ignores empty bins, barrels, and wheelbarrows assigned as storage and transport for that stockpile. +* ``dfhack.buildings.getCageOccupants(cage)`` + + Returns a list of units in the given built cage. Note that this is different + from the list of units assigned to the cage, which can be accessed with + ``cage.assigned_units``. + Low-level ~~~~~~~~~ Low-level building creation functions: @@ -1512,6 +1629,18 @@ Low-level building creation functions: Destroys the building, or queues a deconstruction job. Returns *true* if the building was destroyed and deallocated immediately. +* ``dfhack.buildings.markedForRemoval(building)`` + + Returns *true* if the building is marked for removal (with :kbd:`x`), *false* + 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 ~~~~~~~~~~ More high-level functions are implemented in lua and can be loaded by @@ -1609,6 +1738,27 @@ Constructions module 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 ---------- @@ -1630,25 +1780,25 @@ Basic painting functions: Checks if [GRAPHICS:YES] was specified in init. -* ``dfhack.screen.paintTile(pen,x,y[,char,tile])`` +* ``dfhack.screen.paintTile(pen,x,y[,char,tile,map])`` Paints a tile using given parameters. See below for a description of pen. Returns *false* if coordinates out of bounds, or other error. -* ``dfhack.screen.readTile(x,y)`` +* ``dfhack.screen.readTile(x,y[,map])`` Retrieves the contents of the specified tile from the screen buffers. Returns a pen object, or *nil* if invalid or TrueType. -* ``dfhack.screen.paintString(pen,x,y,text)`` +* ``dfhack.screen.paintString(pen,x,y,text[,map])`` Paints the string starting at *x,y*. Uses the string characters in sequence to override the ``ch`` field of pen. Returns *true* if painting at least one character succeeded. -* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2)`` +* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2[,map])`` Fills the rectangle specified by the coordinates with the given pen. Returns *true* if painting at least one character succeeded. @@ -1936,6 +2086,18 @@ unless otherwise noted. ``listdir_recursive()`` returns the initial path and all components following it for each entry. +Console API +----------- + +* ``dfhack.console.clear()`` + + Clears the console; equivalent to the ``cls`` built-in command. + +* ``dfhack.console.flush()`` + + Flushes all output to the console. This can be useful when printing text that + does not end in a newline but should still be displayed. + Internal API ------------ @@ -2060,6 +2222,24 @@ and are only documented here for completeness: This requires an extension to be specified (``.lua`` or ``.rb``) - use ``dfhack.findScript()`` to include the ``.lua`` extension automatically. +* ``dfhack.internal.md5(string)`` + + Returns the MD5 hash of the given string. + +* ``dfhack.internal.md5File(filename[,first_kb])`` + + Computes the MD5 hash of the given file. Returns ``hash, length`` on success + (where ``length`` is the number of bytes read from the file), or ``nil, + error`` on failure. + + If the parameter ``first_kb`` is specified and evaluates to ``true``, and the + hash was computed successfully, a table containing the first 1024 bytes of the + file is returned as the third return value. + +* ``dfhack.internal.threadid()`` + + Returns a numeric identifier of the current thread. + Core interpreter context ======================== @@ -3041,6 +3221,7 @@ Attributes: If it returns false, the character is ignored. :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. +:key: If specified, the field is disabled until this key is pressed. Must be given as a string. Label class ----------- @@ -3228,11 +3409,13 @@ supports: :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_below: If true, the edit field is placed below the list instead of above. +:edit_key: If specified, the edit field is disabled until this key is pressed. :not_found_label: Specifies the text of the label shown when no items match the filter. The list choices may include the following attributes: :search_key: If specified, used instead of **text** to match against the filter. + This is required for any entries where **text** is not a string. The widget implements: @@ -3244,6 +3427,10 @@ The widget implements: Returns the list of *all* choices. +* ``list:getVisibleChoices()`` + + Returns the *filtered* list of choices. + * ``list:getFilter()`` Returns the current filter string, and the *filtered* list of choices. @@ -3276,6 +3463,19 @@ module file is still necessary for ``require`` to read. 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 ======= @@ -3566,6 +3766,8 @@ Or with auto_gears:: auto_gears=true } +.. _luasocket: + Luasocket ========= @@ -3702,6 +3904,12 @@ Note that this function lets errors propagate to the caller. This is intended to only allow scripts that take appropriate action when used 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 ============================== diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst new file mode 100644 index 000000000..7a9da7cb7 --- /dev/null +++ b/docs/NEWS-dev.rst @@ -0,0 +1,19 @@ +.. comment + This is the changelog for development releases. Entries are included from + changelog.txt. + +.. _dev-changelog: + +##################### +Development Changelog +##################### + +This file contains changes grouped by the release (stable or development) in +which they first appeared. See `build-changelog` for more information. + +See `changelog` for a list of changes grouped by stable releases. + +.. contents:: + :depth: 2 + +.. include:: /docs/_auto/news-dev.rst diff --git a/docs/NEWS.rst b/docs/NEWS.rst new file mode 100644 index 000000000..22f9881b9 --- /dev/null +++ b/docs/NEWS.rst @@ -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. diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 75d2d988d..03ccd809f 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -10,7 +10,7 @@ Most commands offered by plugins are listed here, hopefully organised in a way you will find useful. .. contents:: - :depth: 2 + :depth: 3 =============================== Data inspection and visualizers @@ -57,6 +57,8 @@ An in-development plugin for realtime fortress visualisation. See :forums:`Armok Vision <146473>`. +.. _cursecheck: + cursecheck ========== Checks a single map tile or the whole map/world for cursed creatures (ghosts, @@ -99,17 +101,35 @@ Examples: Please report any living/active creatures with cursetype "unknown" - this is most likely with mods which introduce new types of curses. +.. _flows: + flows ===== A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map. +.. _pathable: + +pathable +======== + +This plugin implements the back end of the `gui/pathable` script. It exports a +single Lua function, in ``hack/lua/plugins/pathable.lua``: + +* ``paintScreen(cursor[,skip_unrevealed])``: Paint each visible of the screen + green or red, depending on whether it can be pathed to from the tile at + ``cursor``. If ``skip_unrevealed`` is specified and true, do not draw + unrevealed tiles. + +.. _probe: + probe ===== Can be used to determine tile properties like temperature. .. _prospect: +.. _prospector: prospect ======== @@ -269,7 +289,11 @@ Subcommands that persist until disabled or DF quits: the contents separately from the container. This forcefully skips child reagents. :block-labors: Prevents labors that can't be used from being toggled +:burrow-name-cancel: Implements the "back" option when renaming a burrow, + which currently does nothing (:bug:`1518`) +:cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` :civ-view-agreement: Fixes overlapping text on the "view agreement" screen +:condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). :craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). With this tweak, items made from cloth and leather will gain a level of wear every 20 years. @@ -284,10 +308,11 @@ Subcommands that persist until disabled or DF quits: the current item (fully, in case of a stack), and scroll down one line. :fps-min: Fixes the in-game minimum FPS setting :hide-priority: Adds an option to hide designation priority indicators +:hotkey-clear: Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu) :import-priority-category: Allows changing the priority of all goods in a 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-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) :max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile @@ -304,6 +329,7 @@ Subcommands that persist until disabled or DF quits: :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 :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 :tradereq-pet-gender: Displays pet genders on the trade request screen @@ -351,7 +377,8 @@ This plugin adds an option to the :kbd:`q` menu when `enabled `. command-prompt ============== An in-game DFHack terminal, where you can enter other commands. -Best used from a keybinding; by default :kbd:`Ctrl`:kbd:`Shift`:kbd:`P`. + +:dfhack-keybind:`command-prompt` Usage: ``command-prompt [entry]`` @@ -370,18 +397,17 @@ Otherwise somewhat similar to `gui/quickcmd`. hotkeys ======= Opens an in-game screen showing which DFHack keybindings are -active in the current context. +active in the current context. See also `hotkey-notes`. .. image:: images/hotkeys.png -Type ``hotkeys`` into the DFHack console to open the screen, -or bind the command to a globally active hotkey. The default -keybinding is :kbd:`Ctrl`:kbd:`F1`. See also `hotkey-notes`. +:dfhack-keybind:`hotkeys` .. _rb: +.. _ruby: -rb -== +ruby +==== Ruby language plugin, which evaluates the following arguments as a ruby string. Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. @@ -405,7 +431,7 @@ military and social skills. .. image:: images/manipulator2.png -Press :kbd:`t` to toggle between Profession and Squad view. +Press :kbd:`t` to toggle between Profession, Squad, and Job views. .. image:: images/manipulator3.png @@ -440,6 +466,20 @@ The following mouse shortcuts are also available: Pressing :kbd:`Esc` normally returns to the unit screen, but :kbd:`Shift`:kbd:`Esc` would exit directly to the main dwarf mode screen. +Professions +----------- + +The manipulator plugin supports saving Professions: a named set of Labors labors that can be +quickly applied to one or multiple Dwarves. + +To save a Profession highlight a Dwarf and press :kbd:`P`. The Profession will be saved using +the Custom Profession Name of the Dwarf, or the default for that Dwarf if no Custom Profession +Name has been set. + +To apply a Profession either highlight a single Dwarf, or select multiple with :kbd:`x`, and press +:kbd:`p` to select the Profession to apply. All labors for the selected Dwarves will be reset to +the labors of the chosen Profession. + .. comment - the link target "search" is reserved for the Sphinx search page .. _search-plugin: @@ -489,6 +529,18 @@ nopause Disables pausing (both manual and automatic) with the exception of pause forced 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. Requires 46 lines to display properly. + .. _embark-tools: embark-tools @@ -569,6 +621,8 @@ Usage: :confirm enable option1 [option2...]: Enable (or disable) specific confirmation dialogues. +.. _follow: + follow ====== Makes the game view follow the currently highlighted unit after you exit from the @@ -601,6 +655,12 @@ resume Allows automatic resumption of suspended constructions, along with colored UI hints for construction status. +.. _title-folder: + +title-folder +============= +Displays the DF folder name in the window title bar when enabled. + .. _title-version: title-version @@ -651,23 +711,28 @@ Unit order examples:: The orderings are defined in ``hack/lua/plugins/sort/*.lua`` +:dfhack-keybind:`sort-units` + .. _stocks: stocks ====== Replaces the DF stocks screen with an improved version. +:dfhack-keybind:`stocks` + .. _stocksettings: .. _stockpiles: -stocksettings -============= +stockpiles +========== Offers the following commands to save and load stockpile settings. See `gui/stockpiles` for an in-game interface. :copystock: Copies the parameters of the currently highlighted stockpile to the custom stockpile settings and switches to custom stockpile placement mode, effectively allowing you to copy/paste stockpiles easily. + :dfhack-keybind:`copystock` :savestock: Saves the currently highlighted stockpile's settings to a file in your Dwarf Fortress folder. This file can be used to copy settings between game saves or @@ -810,6 +875,115 @@ Examples: ``autolabor CUTWOOD disable`` Turn off autolabor for wood cutting. +.. _labormanager: + +labormanager +============ +Automatically manage dwarf labors to efficiently complete jobs. +Labormanager is derived from autolabor (above) but uses a completely +different approach to assigning jobs to dwarves. While autolabor tries +to keep as many dwarves busy as possible, labormanager instead strives +to get jobs done as quickly as possible. + +Labormanager frequently scans the current job list, current list of +dwarfs, and the map to determine how many dwarves need to be assigned to +what labors in order to meet all current labor needs without starving +any particular type of job. + +.. warning:: + + *As with autolabor, labormanager will override any manual changes you + make to labors while it is enabled, including through other tools such + as Dwarf Therapist* + +Simple usage: + +:enable labormanager: Enables the plugin with default settings. + (Persistent per fortress) + +:disable labormanager: Disables the plugin. + +Anything beyond this is optional - labormanager works fairly well on the +default settings. + +The default priorities for each labor vary (some labors are higher +priority by default than others). The way the plugin works is that, once +it determines how many of each labor is needed, it then sorts them by +adjusted priority. (Labors other than hauling have a bias added to them +based on how long it's been since they were last used, to prevent job +starvation.) The labor with the highest priority is selected, the "best +fit" dwarf for that labor is assigned to that labor, and then its +priority is *halved*. This process is repeated until either dwarfs or +labors run out. + +Because there is no easy way to detect how many haulers are actually +needed at any moment, the plugin always ensures that at least one dwarf +is assigned to each of the hauling labors, even if no hauling jobs are +detected. At least one dwarf is always assigned to construction removing +and cleaning because these jobs also cannot be easily detected. Lever +pulling is always assigned to everyone. Any dwarfs for which there are +no jobs will be assigned hauling, lever pulling, and cleaning labors. If +you use animal trainers, note that labormanager will misbehave if you +assign specific trainers to specific animals; results are only guaranteed +if you use "any trainer", and animal trainers will probably be +overallocated in any case. + +Labormanager also sometimes assigns extra labors to currently busy +dwarfs so that when they finish their current job, they will go off and +do something useful instead of standing around waiting for a job. + +There is special handling to ensure that at least one dwarf is assigned +to haul food whenever food is detected left in a place where it will rot +if not stored. This will cause a dwarf to go idle if you have no +storepiles to haul food to. + +Dwarfs who are unable to work (child, in the military, wounded, +handless, asleep, in a meeting) are entirely excluded from labor +assignment. Any dwarf explicitly assigned to a burrow will also be +completely ignored by labormanager. + +The fitness algorithm for assigning jobs to dwarfs generally attempts to +favor dwarfs who are more skilled over those who are less skilled. It +also tries to avoid assigning female dwarfs with children to jobs that +are "outside", favors assigning "outside" jobs to dwarfs who are +carrying a tool that could be used as a weapon, and tries to minimize +how often dwarfs have to reequip. + +Labormanager automatically determines medical needs and reserves health +care providers as needed. Note that this may cause idling if you have +injured dwarfs but no or inadequate hospital facilities. + +Hunting is never assigned without a butchery, and fishing is never +assigned without a fishery, and neither of these labors is assigned +unless specifically enabled. + +The method by which labormanager determines what labor is needed for a +particular job is complicated and, in places, incomplete. In some +situations, labormanager will detect that it cannot determine what labor +is required. It will, by default, pause and print an error message on +the dfhack console, followed by the message "LABORMANAGER: Game paused +so you can investigate the above message.". If this happens, please open +an issue on github, reporting the lines that immediately preceded this +message. You can tell labormanager to ignore this error and carry on by +typing ``labormanager pause-on-error no``, but be warned that some job may go +undone in this situation. + +Advanced usage: + +:labormanager enable: Turn plugin on. +:labormanager disable: Turn plugin off. +:labormanager priority : Set the priority value (see above) for labor to . +:labormanager reset : Reset the priority value of labor to its default. +:labormanager reset-all: Reset all priority values to their defaults. +:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. +:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. +:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. +:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. +:labormanager list: Show current priorities and current allocation stats. +:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. +:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. + + .. _autohauler: autohauler @@ -852,6 +1026,8 @@ Options: **item-type ** Replace the exact item type id in the job item. +.. _job-material: + job-material ============ Alter the material of the selected job. Similar to ``job item-material ...`` @@ -860,7 +1036,7 @@ Invoked as:: job-material -Intended to be used as a keybinding: +:dfhack-keybind:`job-material` * In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, changes the material of the job. Only inorganic materials can be used @@ -868,11 +1044,15 @@ Intended to be used as a keybinding: * In :kbd:`b` mode, during selection of building components positions the cursor over the first available choice with the matching material. +.. _job-duplicate: + job-duplicate ============= In :kbd:`q` mode, when a job is highlighted within a workshop or furnace building, calling ``job-duplicate`` instantly duplicates the job. +:dfhack-keybind:`job-duplicate` + .. _autogems: autogems @@ -880,6 +1060,9 @@ autogems Creates a new Workshop Order setting, automatically cutting rough gems when `enabled `. +See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload`` +command reloads the configuration file produced by that script. + .. _stockflow: stockflow @@ -1057,11 +1240,15 @@ Extra options for ``map``: :mud: Remove mud in addition to the normal stuff. :snow: Also remove snow coverings. +.. _spotclean: + spotclean ========= Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal if you want to keep that bloody entrance ``clean map`` would clean up. +:dfhack-keybind:`spotclean` + .. _autodump: autodump @@ -1084,10 +1271,17 @@ Options: :destroy-here: As ``destroy``, but only the selected item in the :kbd:`k` list, or inside a container. Alias ``autodump-destroy-here``, for keybindings. + :dfhack-keybind:`autodump-destroy-here` :visible: Only process items that are not hidden. :hidden: Only process hidden items. :forbidden: Only process forbidden items (default: only unforbidden). +``autodump-destroy-item`` destroys the selected item, which may be selected +in the :kbd:`k` list, or inside a container. If called again before the game +is resumed, cancels destruction of the item. +:dfhack-keybind:`autodump-destroy-item` + +.. _cleanowned: cleanowned ========== @@ -1109,7 +1303,6 @@ Example: This will confiscate rotten and dropped food, garbage on the floors and any worn items with 'X' damage and above. - .. _dwarfmonitor: dwarfmonitor @@ -1126,6 +1319,8 @@ Options: :prefs: Show dwarf preferences summary :reload: Reload configuration file (``dfhack-config/dwarfmonitor.json``) +:dfhack-keybind:`dwarfmonitor` + Widget configuration: The following types of widgets (defined in :file:`hack/lua/plugins/dwarfmonitor.lua`) @@ -1187,11 +1382,42 @@ Some widgets support additional options: displayed as ``-1`` when the cursor is outside of the DF window; otherwise, nothing will be displayed. +.. _dwarfvet: + +dwarfvet +============ +Enables Animal Caretaker functionality + +Always annoyed your dragons become useless after a minor injury? Well, with +dwarfvet, your animals become first rate members of your fort. It can also +be used to train medical skills. + +Animals need to be treated in an animal hospital, which is simply a hospital +that is also an animal training zone. The console will print out a list on game +load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker +labor to treat animals. Normal medical skills are used (and no experience is given +to the Animal Caretaker skill). + +Options: + +:enable: Enables Animal Caretakers to treat and manage animals +:disable: Turns off the plguin +:report: Reports all zones that the game considers animal hospitals + .. _workNow: workNow ======= -Force all dwarves to look for a job immediately, or as soon as the game is unpaused. +Don't allow dwarves to idle if any jobs are available. + +When workNow is active, every time the game pauses, DF will make dwarves +perform any appropriate available jobs. This includes when you one step +through the game using the pause menu. Usage: + +:workNow: print workNow status +:workNow 0: deactivate workNow +:workNow 1: activate workNow (look for jobs on pause, and only then) +:workNow 2: make dwarves look for jobs whenever a job completes .. _seedwatch: @@ -1229,6 +1455,8 @@ zone ==== Helps a bit with managing activity zones (pens, pastures and pits) and cages. +:dfhack-keybind:`zone` + Options: :set: Set zone or cage under cursor as default for future assigns. @@ -1352,6 +1580,8 @@ Examples Stuff up to 50 owned tame male animals who are not grazers into cages built on the current default zone. +.. _autonestbox: + autonestbox =========== Assigns unpastured female egg-layers to nestbox zones. Requires that you create @@ -1475,15 +1705,28 @@ quotas. Open the dashboard by running:: - getplants autochop + enable autochop -The plugin must be activated (with ``c``) before it can be used. You can then set logging quotas -and restrict designations to specific burrows (with 'Enter') if desired. The plugin's activity -cycle runs once every in game day. +The plugin must be activated (with :kbd:`d`-:kbd:`t`-:kbd:`c`-:kbd:`a`) before +it can be used. You can then set logging quotas and restrict designations to +specific burrows (with 'Enter') if desired. The plugin's activity cycle runs +once every in game day. -If you add ``enable getplants`` to your dfhack.init there will be a hotkey to +If you add ``enable autochop`` to your dfhack.init there will be a hotkey to open the dashboard from the chop designation menu. +.. _orders: + +orders +====== + +A plugin for manipulating manager orders. + +Subcommands: + +:export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. +:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. +:clear: Deletes all manager orders in the current embark. ================ Map modification @@ -1703,6 +1946,15 @@ Basic commands: to remove designations, for if you accidentally set 50 levels at once. :diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. +: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 @@ -2174,12 +2426,7 @@ Usage: * When viewing unit details, body-swaps into that unit. * In the main adventure mode screen, reverts transient swap. -.. _catsplosion: - -catsplosion -=========== -Makes cats just *multiply*. It is not a good idea to run this more than once or -twice. +:dfhack-keybind:`adv-bodyswap` .. _createitem: @@ -2188,7 +2435,8 @@ createitem Allows creating new items of arbitrary types and made of arbitrary materials. By default, items created are spawned at the feet of the selected unit. -Specify the item and material information as you would indicate them in custom reaction raws, with the following differences: +Specify the item and material information as you would indicate them in +custom reaction raws, with the following differences: * Separate the item and material with a space rather than a colon * If the item has no subtype, omit the :NONE @@ -2204,7 +2452,10 @@ Examples:: createitem WOOD PLANT_MAT:TOWER_CAP:WOOD Create tower-cap logs. -To change where new items are placed, first run the command with a destination type while an appropriate destination is selected. +For more examples, :wiki:`see this wiki page `. + +To change where new items are placed, first run the command with a +destination type while an appropriate destination is selected. Options: @@ -2272,6 +2523,24 @@ armor onto a war animal or to add unusual items (such as crowns) to any unit. For more information run ``forceequip help``. See also `modtools/equip-item`. +.. _generated-creature-renamer: + +generated-creature-renamer +========================== +Automatically renames generated creatures, such as forgotten beasts, titans, +etc, to have raw token names that match the description given in-game. + +The ``list-generated`` command can be used to list the token names of all +generated creatures in a given save, with an optional ``detailed`` argument +to show the accompanying description. + +The ``save-generated-raws`` command will save a sample creature graphics file in +the Dwarf Fortress root directory, to use as a start for making a graphics set +for generated creatures using the new names that they get with this plugin. + +The new names are saved with the save, and the plugin, when enabled, only runs once +per save, unless there's an update. + .. _lair: lair @@ -2287,6 +2556,23 @@ Options: :lair: Mark the map as monster lair :lair reset: Mark the map as ordinary (not lair) +.. _misery: + +misery +====== +When enabled, fake bad thoughts will be added to all dwarves. + +Usage: + +:misery enable n: enable misery with optional magnitude n. If specified, n must + be positive. +:misery n: same as "misery enable n" +:misery enable: same as "misery enable 1" +:misery disable: stop adding new negative thoughts. This will not remove + existing negative thoughts. Equivalent to "misery 0". +:misery clear: remove fake thoughts, even after saving and reloading. Does + not change factor. + .. _mode: mode @@ -2324,7 +2610,7 @@ Options: :-unit: Make the strange mood strike the selected unit instead of picking one randomly. Unit eligibility is still enforced. :-type : Force the mood to be of a particular type instead of choosing randomly based on happiness. - Valid values for Tare "fey", "secretive", "possessed", "fell", and "macabre". + Valid values for T are "fey", "secretive", "possessed", "fell", and "macabre". :-skill S: Force the mood to use a specific skill instead of choosing the highest moodable skill. Valid values are "miner", "carpenter", "engraver", "mason", "tanner", "weaver", "clothier", "weaponsmith", "armorsmith", "metalsmith", "gemcutter", "gemsetter", diff --git a/docs/Scripts-removed.rst b/docs/Scripts-removed.rst new file mode 100644 index 000000000..4490110ba --- /dev/null +++ b/docs/Scripts-removed.rst @@ -0,0 +1,14 @@ +############### +Removed scripts +############### + +The following scripts were removed for various reasons. + +.. contents:: + :depth: 2 + +.. _warn-stuck-trees: + +warn-stuck-trees +================ +The corresponding DF bug, :bug:`9252` was fixed in DF 0.44.01. diff --git a/docs/_auto/.gitignore b/docs/_auto/.gitignore new file mode 100644 index 000000000..30d85567b --- /dev/null +++ b/docs/_auto/.gitignore @@ -0,0 +1 @@ +*.rst diff --git a/docs/_changelogs/.gitignore b/docs/_changelogs/.gitignore new file mode 100644 index 000000000..2211df63d --- /dev/null +++ b/docs/_changelogs/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/docs/build.sh b/docs/build.sh new file mode 100755 index 000000000..6745db4d5 --- /dev/null +++ b/docs/build.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# usage: +# ./build.sh +# ./build.sh sphinx-executable +# JOBS=3 ./build.sh ... + +cd $(dirname "$0") +cd .. + +sphinx=sphinx-build +if [ -n "$SPHINX" ]; then + sphinx=$SPHINX +fi + +if [ -z "$JOBS" ]; then + JOBS=2 +fi + +"$sphinx" -a -E -q -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" diff --git a/docs/changelog.txt b/docs/changelog.txt new file mode 100644 index 000000000..f8335c6ca --- /dev/null +++ b/docs/changelog.txt @@ -0,0 +1,420 @@ +=== 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 + +# 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 +- `gui/autogems`: a configuration UI for the `autogems` plugin + +## Fixes +- `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 `_ 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`` + diff --git a/docs/gen_changelog.py b/docs/gen_changelog.py new file mode 100644 index 000000000..d042ca2e7 --- /dev/null +++ b/docs/gen_changelog.py @@ -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', + 'Structures', + 'Lua', + 'Ruby', +] + +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) diff --git a/docs/styles/dfhack-logo.png b/docs/styles/dfhack-logo.png new file mode 100644 index 000000000..b9a617816 Binary files /dev/null and b/docs/styles/dfhack-logo.png differ diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 67f75b3b1..d12348c31 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -1,10 +1,10 @@ /* make sure to sync this with the base theme's css filename */ @import url("alabaster.css"); -.kbd { /* Keybinding CSS from the DF wiki; applies to :kbd:`` directives. * Use this directive for all keypresses, to make them look like keys. */ +.kbd { border: 1px solid #aaa; border-radius: 0.2em; -webkit-border-radius: 0.2em; @@ -28,13 +28,21 @@ src: url("cp437.ttf"); } -.guilabel { /* In-game text CSS from the DF wiki; applies to :guilabel:`` directives. * Use this for any text from an in-game announcement or menu. */ +.guilabel { color: #CBC7C0; font-family: cp437, 'fixedsys', monospace; background: #000000; font-size: 0.95em; padding: 0.05em 0.4em; } + +/* Override hyphenation from Sphinx's basic.css (excessive) */ +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} diff --git a/index.rst b/index.rst index de587e95a..d4bd223c5 100644 --- a/index.rst +++ b/index.rst @@ -49,7 +49,8 @@ Other Contents /docs/Authors /LICENSE - /NEWS + /docs/NEWS + /docs/Scripts-removed For Developers ============== @@ -57,8 +58,9 @@ For Developers .. toctree:: :maxdepth: 1 - /docs/Contributing + /Contributing /docs/Compile + /docs/NEWS-dev /docs/Lua API /library/xml/SYNTAX /library/xml/how-to-update diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 54300ec8d..8541f37cd 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -1,14 +1,11 @@ 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 -IF(CMAKE_VERSION VERSION_GREATER "2.8.12") - CMAKE_POLICY(SET CMP0022 OLD) -ENDIF() +CMAKE_POLICY(SET CMP0022 NEW) ## build options OPTION(BUILD_DEVEL "Install/package files required for development (For SDK)." OFF) -OPTION(BUILD_DOXYGEN "Create/install/package doxygen documentation for DFHack (For SDK)." OFF) IF(UNIX) OPTION(CONSOLE_NO_CATCH "Make the console not catch 'CTRL+C' events for easier debugging." OFF) ENDIF() @@ -16,8 +13,6 @@ ENDIF() include_directories (proto) include_directories (include) -SET(PERL_EXECUTABLE "perl" CACHE FILEPATH "This is the perl executable to run in the codegen step. Tweak it if you need to run a specific one.") - execute_process(COMMAND ${PERL_EXECUTABLE} xml/list.pl xml ${dfapi_SOURCE_DIR}/include/df ";" WORKING_DIRECTORY ${dfapi_SOURCE_DIR} OUTPUT_VARIABLE GENERATED_HDRS) @@ -60,6 +55,7 @@ SET(MAIN_SOURCES Core.cpp ColorText.cpp DataDefs.cpp +Error.cpp VTableInterpose.cpp LuaWrapper.cpp LuaTypes.cpp @@ -115,54 +111,56 @@ SET(MODULE_HEADERS include/modules/Buildings.h include/modules/Burrows.h include/modules/Constructions.h -include/modules/Units.h +include/modules/Designations.h include/modules/Engravings.h include/modules/EventManager.h +include/modules/Filesystem.h +include/modules/Graphic.h include/modules/Gui.h include/modules/GuiHooks.h include/modules/Items.h include/modules/Job.h -include/modules/kitchen.h -include/modules/Maps.h +include/modules/Kitchen.h include/modules/MapCache.h +include/modules/Maps.h include/modules/Materials.h include/modules/Notes.h +include/modules/Once.h include/modules/Random.h include/modules/Renderer.h include/modules/Screen.h include/modules/Translation.h +include/modules/Units.h include/modules/Vermin.h include/modules/World.h -include/modules/Graphic.h -include/modules/Once.h -include/modules/Filesystem.h ) SET( MODULE_SOURCES modules/Buildings.cpp modules/Burrows.cpp modules/Constructions.cpp -modules/Units.cpp +modules/Designations.cpp modules/Engravings.cpp modules/EventManager.cpp +modules/Filesystem.cpp +modules/Graphic.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp -modules/kitchen.cpp +modules/Kitchen.cpp modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp modules/Notes.cpp +modules/Once.cpp modules/Random.cpp modules/Renderer.cpp modules/Screen.cpp modules/Translation.cpp +modules/Units.cpp modules/Vermin.cpp -modules/World.cpp -modules/Graphic.cpp modules/Windows.cpp -modules/Once.cpp -modules/Filesystem.cpp +modules/World.cpp ) SET(STATIC_FIELDS_FILES) @@ -217,18 +215,41 @@ FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}") STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}") +STRING(REPLACE "/proto/" "/proto/tmp/" PROJECT_PROTO_TMP_FILES "${PROJECT_PROTO_SRCS};${PROJECT_PROTO_HDRS}") +SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + PROPERTIES GENERATED TRUE) + +# Force a re-gen if any *.pb.* files are missing +# (only runs when cmake is run, but better than nothing) +FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) + IF(NOT EXISTS ${file}) + # MESSAGE("Resetting generate_proto_core because '${file}' is missing") + FILE(REMOVE ${PROJECT_PROTO_TMP_FILES}) + BREAK() + ENDIF() +ENDFOREACH() LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_HDRS}) LIST(APPEND PROJECT_SOURCES ${PROJECT_PROTO_SRCS}) ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + OUTPUT ${PROJECT_PROTO_TMP_FILES} COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ - --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/ + --cpp_out=dllexport_decl=DFHACK_EXPORT:${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl + ${PROJECT_PROTO_TMP_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ + COMMENT "Generating core protobufs" 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}) + # Merge headers into sources SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE ) LIST(APPEND PROJECT_SOURCES ${PROJECT_HEADERS}) @@ -237,7 +258,7 @@ LIST(APPEND PROJECT_SOURCES ${PROJECT_HEADERS}) 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_XMLS ${dfapi_SOURCE_DIR}/xml/*.xml) +FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml) ADD_CUSTOM_COMMAND( OUTPUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml @@ -269,12 +290,12 @@ IF(UNIX) ENDIF() IF(APPLE) - SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS dl dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) ELSEIF(UNIX) - SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS rt dl dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) ELSE(WIN32) #FIXME: do we really need psapi? - SET(PROJECT_LIBS psapi dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS psapi dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) ENDIF() ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp) @@ -282,6 +303,7 @@ SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS DFHACK_VERSION="${DFHACK_VERSION}" DF_VERSION="${DF_VERSION}" DFHACK_RELEASE="${DFHACK_RELEASE}" + DFHACK_ABI_VERSION=${DFHACK_ABI_VERSION} ) IF(DFHACK_PRERELEASE) SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS @@ -299,9 +321,9 @@ ADD_CUSTOM_TARGET(git-describe ADD_DEPENDENCIES(dfhack-version git-describe) ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) -ADD_DEPENDENCIES(dfhack generate_headers) +ADD_DEPENDENCIES(dfhack generate_headers generate_proto_core) -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_EXECUTABLE(dfhack-run dfhack-run.cpp) @@ -340,12 +362,13 @@ IF(APPLE) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) + TARGET_LINK_LIBRARIES(dfhack ncurses) SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) ENDIF() TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp 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-run dfhack-client) @@ -360,12 +383,6 @@ IF(UNIX) DESTINATION .) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run DESTINATION .) - OPTION(INSTALL_NEW_LIBSTDCXX "Install a version of libstdc++ from GCC 4.5.4 to fix various crashes" ON) - IF(INSTALL_NEW_LIBSTDCXX) - execute_process(COMMAND bunzip2 --keep --force ${dfhack_SOURCE_DIR}/package/darwin/libstdc++.6.dylib.bz2) - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/libstdc++.6.dylib - DESTINATION ./hack/) - ENDIF(INSTALL_NEW_LIBSTDCXX) else() # On linux, copy our version of the df launch script which sets LD_PRELOAD install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack @@ -376,7 +393,7 @@ IF(UNIX) ELSE() if(NOT BUILD_EGGY) # On windows, copy the renamed SDL so DF can still run. - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/SDLreal.dll + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll DESTINATION ${DFHACK_LIBRARY_DESTINATION}) endif() ENDIF() @@ -396,7 +413,7 @@ endif() install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) #linux: share/dfhack #install the example autoexec file -install(FILES ../dfhack.init-example +install(FILES ../dfhack.init-example ../onLoad.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) install(TARGETS dfhack-run dfhack-client binpatch @@ -407,13 +424,6 @@ install(DIRECTORY lua/ DESTINATION ${DFHACK_LUA_DESTINATION} FILES_MATCHING PATTERN "*.lua") -#install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts -# DESTINATION ${DFHACK_DATA_DESTINATION} -# FILES_MATCHING PATTERN "*.lua" -# PATTERN "*.rb" -# PATTERN "3rdparty" EXCLUDE -# ) - install(DIRECTORY ${dfhack_SOURCE_DIR}/patches DESTINATION ${DFHACK_DATA_DESTINATION} FILES_MATCHING PATTERN "*.dif") @@ -429,8 +439,4 @@ if(BUILD_DEVEL) install(DIRECTORY include/ DESTINATION ${DFHACK_INCLUDES_DESTINATION} FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" ) #linux: include - # Building the docs - IF(BUILD_DOXYGEN) - add_subdirectory (doc) - ENDIF() endif() diff --git a/library/ColorText.cpp b/library/ColorText.cpp index 359b717e6..7f417e0a8 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -126,10 +126,18 @@ void color_ostream::vprinterr(const char *format, va_list args) color_value save = cur_color; if (log_errors_to_stderr) - vfprintf(stderr, format, args); + { + va_list args1; + va_copy(args1, args); + vfprintf(stderr, format, args1); + va_end(args1); + } color(COLOR_LIGHTRED); - vprint(format, args); + va_list args2; + va_copy(args2, args); + vprint(format, args2); + va_end(args2); color(save); } diff --git a/library/Console-posix.cpp b/library/Console-posix.cpp index 8ef879d48..ef74e67d2 100644 --- a/library/Console-posix.cpp +++ b/library/Console-posix.cpp @@ -532,7 +532,7 @@ namespace DFHack } if (seq[1] == 'D') { - left_arrow: + /* left arrow */ if (raw_cursor > 0) { raw_cursor--; @@ -541,7 +541,6 @@ namespace DFHack } else if ( seq[1] == 'C') { - right_arrow: /* right arrow */ if (size_t(raw_cursor) != raw_buffer.size()) { @@ -636,7 +635,7 @@ namespace DFHack prompt_refresh(); break; 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); raw_buffer.erase(raw_cursor); prompt_refresh(); @@ -652,7 +651,7 @@ namespace DFHack case 20: // Ctrl+t, transpose current and previous characters if (raw_buffer.size() >= 2 && raw_cursor > 0) { - if (raw_cursor == raw_buffer.size()) + if (size_t(raw_cursor) == raw_buffer.size()) raw_cursor--; std::swap(raw_buffer[raw_cursor - 1], raw_buffer[raw_cursor]); raw_cursor++; diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index c11661a30..3caacd924 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -62,7 +62,7 @@ using namespace DFHack; using namespace tthread; // FIXME: maybe make configurable with an ini option? -#define MAX_CONSOLE_LINES 999; +#define MAX_CONSOLE_LINES 999 namespace DFHack { @@ -165,14 +165,14 @@ namespace DFHack // Blank to EOL char* tmp = (char*)malloc(inf.dwSize.X); memset(tmp, ' ', inf.dwSize.X); - output(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y); + blankout(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y); free(tmp); COORD coord = {0, inf.dwCursorPosition.Y}; // Windows uses 0-based coordinates SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); } void gotoxy(int x, int y) { - COORD coord = {x-1, y-1}; // Windows uses 0-based coordinates + COORD coord = {(SHORT)(x-1), (SHORT)(y-1)}; // Windows uses 0-based coordinates SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); } @@ -210,13 +210,23 @@ namespace DFHack } } - void output(const char* str, size_t len, int x, int y) + void blankout(const char* str, size_t len, int x, int y) { COORD pos = { (SHORT)x, (SHORT)y }; DWORD count = 0; WriteConsoleOutputCharacterA(console_out, str, len, pos, &count); } + void output(const char* str, size_t len, int x, int y) + { + COORD pos = { (SHORT)x, (SHORT)y }; + DWORD count = 0; + CONSOLE_SCREEN_BUFFER_INFO inf = { 0 }; + GetConsoleScreenBufferInfo(console_out, &inf); + SetConsoleCursorPosition(console_out, pos); + WriteConsoleA(console_out, str, len, &count, NULL); + } + void prompt_refresh() { size_t cols = get_columns(); @@ -245,7 +255,7 @@ namespace DFHack // Blank to EOL char* tmp = (char*)malloc(inf.dwSize.X - (plen + len)); memset(tmp, ' ', inf.dwSize.X - (plen + len)); - output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y); + blankout(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y); free(tmp); } inf.dwCursorPosition.X = (SHORT)(cooked_cursor + plen); @@ -434,7 +444,7 @@ bool Console::init(bool) { d = new Private(); int hConHandle; - long lStdHandle; + intptr_t lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; DWORD oldMode, newMode; @@ -469,14 +479,14 @@ bool Console::init(bool) // redirect unbuffered STDOUT to the console d->console_out = GetStdHandle(STD_OUTPUT_HANDLE); - lStdHandle = (long)d->console_out; + lStdHandle = (intptr_t)d->console_out; hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); d->dfout_C = _fdopen( hConHandle, "w" ); setvbuf( d->dfout_C, NULL, _IONBF, 0 ); // redirect unbuffered STDIN to the console d->console_in = GetStdHandle(STD_INPUT_HANDLE); - lStdHandle = (long)d->console_in; + lStdHandle = (intptr_t)d->console_in; hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "r" ); *stdin = *fp; diff --git a/library/Core.cpp b/library/Core.cpp index f7f057e19..49aa61ecd 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -69,6 +69,7 @@ using namespace DFHack; #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_game_cleanerst.h" #include "df/viewscreen_loadgamest.h" +#include "df/viewscreen_new_regionst.h" #include "df/viewscreen_savegamest.h" #include @@ -81,6 +82,10 @@ using namespace DFHack; #include "SDL_events.h" +#ifdef LINUX_BUILD +#include +#endif + using namespace tthread; using namespace df::enums; using df::global::init; @@ -135,6 +140,16 @@ struct Core::Private } }; +struct CommandDepthCounter +{ + static const int MAX_DEPTH = 20; + static thread_local int depth; + CommandDepthCounter() { depth++; } + ~CommandDepthCounter() { depth--; } + bool ok() { return depth < MAX_DEPTH; } +}; +thread_local int CommandDepthCounter::depth = 0; + void Core::cheap_tokenise(string const& input, vector &output) { string *cur = NULL; @@ -250,6 +265,7 @@ static string dfhack_version_desc() s << "(release)"; else s << "(development build " << Version::git_description() << ")"; + s << " on " << (sizeof(void*) == 8 ? "x86_64" : "x86"); return s.str(); } @@ -278,19 +294,24 @@ static void listScripts(PluginManager *plug_mgr, std::map &pset, std::vector files; Filesystem::listdir(path, files); + path += '/'; for (size_t i = 0; i < files.size(); i++) { if (hasEnding(files[i], ".lua")) { - std::string help = getScriptHelp(path + files[i], "--"); - - pset[prefix + files[i].substr(0, files[i].size()-4)] = help; + string help = getScriptHelp(path + files[i], "--"); + string key = prefix + files[i].substr(0, files[i].size()-4); + if (pset.find(key) == pset.end()) { + pset[key] = help; + } } else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && hasEnding(files[i], ".rb")) { - std::string help = getScriptHelp(path + files[i], "#"); - - pset[prefix + files[i].substr(0, files[i].size()-3)] = help; + string help = getScriptHelp(path + files[i], "#"); + string key = prefix + files[i].substr(0, files[i].size()-3); + if (pset.find(key) == pset.end()) { + pset[key] = help; + } } else if (all && !files[i].empty() && files[i][0] != '.') { @@ -299,10 +320,12 @@ static void listScripts(PluginManager *plug_mgr, std::map &pset, } } -static bool fileExists(std::string path) +static void listAllScripts(map &pset, bool all) { - ifstream script(path.c_str()); - return script.good(); + vector paths; + Core::getInstance().getScriptPaths(&paths); + for (string path : paths) + listScripts(Core::getInstance().getPluginManager(), pset, path, all); } namespace { @@ -365,17 +388,26 @@ static command_result enableLuaScript(color_ostream &out, std::string name, bool return ok ? CR_OK : CR_FAILURE; } -static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string name, vector &args) +static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string filename, vector &args) { if (!plug_mgr->ruby || !plug_mgr->ruby->is_enabled()) return CR_FAILURE; + // ugly temporary patch for https://github.com/DFHack/dfhack/issues/1146 + string cwd = Filesystem::getcwd(); + if (filename.find(cwd) == 0) + { + filename = filename.substr(cwd.size()); + while (!filename.empty() && (filename[0] == '/' || filename[0] == '\\')) + filename = filename.substr(1); + } + std::string rbcmd = "$script_args = ["; for (size_t i = 0; i < args.size(); i++) rbcmd += "'" + args[i] + "', "; rbcmd += "]\n"; - rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }"; + rbcmd += "catch(:script_finished) { load '" + filename + "' }"; return plug_mgr->ruby->eval_ruby(out, rbcmd.c_str()); } @@ -402,10 +434,38 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } +// List of built in commands +static const std::set built_in_commands = { + "ls" , + "help" , + "type" , + "load" , + "unload" , + "reload" , + "enable" , + "disable" , + "plug" , + "keybinding" , + "alias" , + "fpause" , + "cls" , + "die" , + "kill-lua" , + "script" , + "hide" , + "show" , + "sc-script" +}; + static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) { std::vector possible; + // Check for possible built in commands to autocomplete first + for (auto const &command : built_in_commands) + if (command.substr(0, first.size()) == first) + possible.push_back(command); + auto plug_mgr = Core::getInstance().getPluginManager(); for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { @@ -423,7 +483,7 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: bool all = (first.find('/') != std::string::npos); std::map scripts; - listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all); + listAllScripts(scripts, all); for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) if (iter->first.substr(0, first.size()) == first) possible.push_back(iter->first); @@ -585,27 +645,12 @@ static std::string sc_event_name (state_change_event id) { string getBuiltinCommand(std::string cmd) { std::string builtin = ""; - if (cmd == "ls" || - cmd == "help" || - cmd == "type" || - cmd == "load" || - cmd == "unload" || - cmd == "reload" || - cmd == "enable" || - cmd == "disable" || - cmd == "plug" || - cmd == "keybinding" || - cmd == "fpause" || - cmd == "cls" || - cmd == "die" || - cmd == "kill-lua" || - cmd == "script" || - cmd == "hide" || - cmd == "show" || - cmd == "sc-script" - ) + + // Check our list of builtin commands from the header + if (built_in_commands.count(cmd)) builtin = cmd; + // Check for some common aliases for built in commands else if (cmd == "?" || cmd == "man") builtin = "help"; @@ -618,9 +663,39 @@ string getBuiltinCommand(std::string cmd) return builtin; } +void ls_helper(color_ostream &con, const string &name, const string &desc) +{ + const size_t help_line_length = 80 - 22 - 5; + const string padding = string(80 - help_line_length, ' '); + vector lines; + con.print(" %-22s - ", name.c_str()); + word_wrap(&lines, desc, help_line_length); + + // print first line, then any additional lines preceded by padding + for (size_t i = 0; i < lines.size(); i++) + con.print("%s%s\n", i ? padding.c_str() : "", lines[i].c_str()); +} + +void ls_helper(color_ostream &con, const PluginCommand &pcmd) +{ + if (pcmd.isHotkeyCommand()) + con.color(COLOR_CYAN); + ls_helper(con, pcmd.name, pcmd.description); + con.reset_color(); +} + command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) { std::string first = first_; + CommandDepthCounter counter; + if (!counter.ok()) + { + con.printerr("Cannot invoke \"%s\": maximum command depth exceeded (%i)\n", + first.c_str(), CommandDepthCounter::MAX_DEPTH); + return CR_FAILURE; + } + + command_result res; if (!first.empty()) { if(first.find('\\') != std::string::npos) @@ -758,8 +833,6 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(parts.size()) { - command_result res = CR_OK; - for (size_t i = 0; i < parts.size(); i++) { std::string part = parts[i]; @@ -846,11 +919,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else for (size_t j = 0; j < plug->size();j++) { - const PluginCommand & pcmd = (plug->operator[](j)); - if (pcmd.isHotkeyCommand()) - con.color(COLOR_CYAN); - con.print(" %-22s - %s\n",pcmd.name.c_str(), pcmd.description.c_str()); - con.reset_color(); + ls_helper(con, plug->operator[](j)); } } else @@ -891,23 +960,23 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { if ((*iter).recolor) con.color(COLOR_CYAN); - con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str()); + ls_helper(con, iter->name, iter->description); con.reset_color(); } std::map scripts; - listScripts(plug_mgr, scripts, getHackPath() + "scripts/", all); + listAllScripts(scripts, all); if (!scripts.empty()) { con.print("\nscripts:\n"); for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) - con.print(" %-22s- %s\n", iter->first.c_str(), iter->second.c_str()); + ls_helper(con, iter->first, iter->second); } } } else if (builtin == "plug") { - const char *header_format = "%25s %10s %4s %8s\n"; - const char *row_format = "%25s %10s %4i %8s\n"; + const char *header_format = "%30s %10s %4s %8s\n"; + const char *row_format = "%30s %10s %4i %8s\n"; con.print(header_format, "Name", "State", "Cmds", "Enabled"); plug_mgr->refresh(); @@ -969,6 +1038,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v con << " (aliased to " << builtin_cmd << ")"; con << std::endl; } + else if (IsAlias(parts[0])) + { + con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; + } else if (plug) { con << " is a command implemented by the plugin " << plug->getName() << std::endl; @@ -1031,16 +1104,56 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << endl << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << endl << "Later adds, and earlier items within one command have priority." << endl - << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, or F1-F9, or Enter)." << endl + << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, or Enter)." << endl << "Context may be used to limit the scope of the binding, by" << endl << "requiring the current context to have a certain prefix." << endl << "Current UI context is: " << Gui::getFocusString(Core::getTopViewscreen()) << endl; } } + else if (builtin == "alias") + { + if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) + { + const string &name = parts[1]; + vector cmd(parts.begin() + 2, parts.end()); + if (!AddAlias(name, cmd, parts[0] == "replace")) + { + con.printerr("Could not add alias %s - already exists\n", name.c_str()); + return CR_FAILURE; + } + } + else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) + { + if (!RemoveAlias(parts[1])) + { + con.printerr("Could not remove alias %s\n", parts[1].c_str()); + return CR_FAILURE; + } + } + else if (parts.size() >= 1 && (parts[0] == "list")) + { + auto aliases = ListAliases(); + for (auto p : aliases) + { + con << p.first << ": " << join_strings(" ", p.second) << endl; + } + } + else + { + con << "Usage: " << endl + << " alias add|replace " << endl + << " alias delete|clear " << endl + << " alias list" << endl; + } + } else if (builtin == "fpause") { World::SetPauseState(true); + if (auto scr = Gui::getViewscreenByType()) + { + scr->worldgen_paused = true; + } con.print("The game was forced to pause!\n"); } else if (builtin == "cls") @@ -1101,13 +1214,9 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else if (builtin == "sc-script") { - if (parts.size() < 1) + if (parts.empty() || parts[0] == "help" || parts[0] == "?") { con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; - return CR_WRONG_USAGE; - } - if (parts[0] == "help" || parts[0] == "?") - { con << "Valid event names (SC_ prefix is optional):" << endl; for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) { @@ -1197,9 +1306,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_WRONG_USAGE; } } + else if (RunAlias(con, first, parts, res)) + { + return res; + } else { - command_result res = plug_mgr->InvokeCommand(con, first, parts); + res = plug_mgr->InvokeCommand(con, first, parts); if(res == CR_NOT_IMPLEMENTED) { string completed; @@ -1211,7 +1324,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if ( lua ) res = runLuaScript(con, first, parts); else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) - res = runRubyScript(con, plug_mgr, first, parts); + res = runRubyScript(con, plug_mgr, filename, parts); else if ( try_autocomplete(con, first, completed) ) res = CR_NOT_IMPLEMENTED; else @@ -1384,7 +1497,8 @@ Core::Core() hotkey_set = false; HotkeyMutex = 0; HotkeyCond = 0; - misc_data_mutex=0; + alias_mutex = 0; + misc_data_mutex = 0; last_world_data_ptr = NULL; last_local_map_ptr = NULL; last_pause_state = false; @@ -1438,6 +1552,17 @@ bool Core::Init() if(errorstate) return false; + // 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 + // make it obvious what's going on if someone checks the *.txt files. + #ifndef LINUX_BUILD + // Don't do this on Linux because it will break PRINT_MODE:TEXT + fprintf(stdout, "dfhack: redirecting stdout to stdout.log (again)\n"); + fprintf(stderr, "dfhack: redirecting stderr to stderr.log (again)\n"); + freopen("stdout.log", "w", stdout); + freopen("stderr.log", "w", stderr); + #endif + fprintf(stderr, "DFHack build: %s\n", Version::git_description()); // find out what we are... @@ -1468,7 +1593,34 @@ bool Core::Init() if(!vinfo || !p->isIdentified()) { - fatal("Not a known DF version.\n"); + if (!Version::git_xml_match()) + { + const char *msg = ( + "*******************************************************\n" + "* BIG, UGLY ERROR MESSAGE *\n" + "*******************************************************\n" + "\n" + "This DF version is missing from hack/symbols.xml, and\n" + "you have compiled DFHack with a df-structures (xml)\n" + "version that does *not* match the version tracked in git.\n" + "\n" + "If you are not actively working on df-structures and you\n" + "expected DFHack to work, you probably forgot to run\n" + "\n" + " git submodule update\n" + "\n" + "If this does not sound familiar, read Compile.rst and \n" + "recompile.\n" + "More details can be found in stderr.log in this folder.\n" + ); + cout << msg << endl; + cerr << msg << endl; + fatal("Not a known DF version - XML version mismatch (see console or stderr.log)"); + } + else + { + fatal("Not a known DF version.\n"); + } errorstate = true; delete p; p = NULL; @@ -1476,13 +1628,43 @@ bool Core::Init() } 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 df::global::InitGlobals(); + alias_mutex = new recursive_mutex(); cerr << "Initializing Console.\n"; // init the console. 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) || getenv("DFHACK_DISABLE_CONSOLE")) { con.init(true); cerr << "Console is not available. Use dfhack-run to send commands.\n"; @@ -1562,21 +1744,24 @@ bool Core::Init() HotkeyMutex = new mutex(); HotkeyCond = new condition_variable(); - if (!is_text_mode) + if (!is_text_mode || is_headless) { cerr << "Starting IO thread.\n"; // create IO thread thread * IO = new thread(fIOthread, (void *) temp); + (void)IO; } else { cerr << "Starting dfhack.init thread.\n"; thread * init = new thread(fInitthread, (void *) temp); + (void)init; } cerr << "Starting DF input capture thread.\n"; // set up hotkey capture thread * HK = new thread(fHKthread, (void *) temp); + (void)HK; screen_window = new Windows::top_level_window(); screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; @@ -1774,6 +1959,7 @@ void Core::Resume() lock_guard 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(); @@ -1813,6 +1999,7 @@ void Core::DisclaimSuspend(int level) lock_guard lock(d->AccessMutex); assert(d->df_suspend_depth == level && d->df_suspend_thread == tid); + (void)tid; if (level == 1000000) d->df_suspend_depth = 0; @@ -2523,6 +2710,64 @@ std::vector Core::ListKeyBindings(std::string keyspec) return rv; } +bool Core::AddAlias(const std::string &name, const std::vector &command, bool replace) +{ + tthread::lock_guard lock(*alias_mutex); + if (!IsAlias(name) || replace) + { + aliases[name] = command; + return true; + } + return false; +} + +bool Core::RemoveAlias(const std::string &name) +{ + tthread::lock_guard lock(*alias_mutex); + if (IsAlias(name)) + { + aliases.erase(name); + return true; + } + return false; +} + +bool Core::IsAlias(const std::string &name) +{ + tthread::lock_guard lock(*alias_mutex); + return aliases.find(name) != aliases.end(); +} + +bool Core::RunAlias(color_ostream &out, const std::string &name, + const std::vector ¶meters, command_result &result) +{ + tthread::lock_guard lock(*alias_mutex); + if (!IsAlias(name)) + { + return false; + } + + const string &first = aliases[name][0]; + vector parts(aliases[name].begin() + 1, aliases[name].end()); + parts.insert(parts.end(), parameters.begin(), parameters.end()); + result = runCommand(out, first, parts); + return true; +} + +std::map> Core::ListAliases() +{ + tthread::lock_guard lock(*alias_mutex); + return aliases; +} + +std::string Core::GetAliasCommand(const std::string &name, const std::string &default_) +{ + tthread::lock_guard lock(*alias_mutex); + if (IsAlias(name)) + return join_strings(" ", aliases[name]); + else + return default_; +} ///////////////// // ClassNameCheck diff --git a/library/DFHackVersion.cpp b/library/DFHackVersion.cpp index 9d367ae64..f4f0cf242 100644 --- a/library/DFHackVersion.cpp +++ b/library/DFHackVersion.cpp @@ -3,6 +3,10 @@ #include "git-describe.h" namespace DFHack { namespace Version { + int dfhack_abi_version() + { + return DFHACK_ABI_VERSION; + } const char *dfhack_version() { return DFHACK_VERSION; diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index f9187ff09..3d802638f 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -36,6 +36,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "VTableInterpose.h" +#include "Error.h" #include "MiscUtils.h" @@ -142,11 +143,30 @@ enum_identity::enum_identity(size_t size, type_identity *base_type, int64_t first_item_value, int64_t last_item_value, const char *const *keys, + const ComplexData *complex, const void *attrs, struct_identity *attr_type) : 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) { + 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 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, @@ -255,6 +275,9 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) virtual_identity *virtual_identity::find(void *vtable) { + if (!vtable) + return NULL; + // Actually, a reader/writer lock would be sufficient, // since the table is only written once per class. tthread::lock_guard lock(*known_mutex); @@ -268,19 +291,17 @@ virtual_identity *virtual_identity::find(void *vtable) Core &core = Core::getInstance(); std::string name = core.p->doReadClassName(vtable); - virtual_identity *actual = NULL; - auto name_it = name_lookup.find(name); if (name_it != name_lookup.end()) { virtual_identity *p = name_it->second; if (p->vtable_ptr && p->vtable_ptr != vtable) { std::cerr << "Conflicting vtable ptr for class '" << p->getName() - << "': found 0x" << std::hex << unsigned(vtable) - << ", previous 0x" << unsigned(p->vtable_ptr) << std::dec << std::endl; + << "': found 0x" << std::hex << uintptr_t(vtable) + << ", previous 0x" << uintptr_t(p->vtable_ptr) << std::dec << std::endl; abort(); } else if (!p->vtable_ptr) { - uint32_t pv = unsigned(vtable); + uintptr_t pv = uintptr_t(vtable); pv -= Core::getInstance().vinfo->getRebaseDelta(); std::cerr << "" << std::endl; @@ -292,7 +313,7 @@ virtual_identity *virtual_identity::find(void *vtable) } std::cerr << "UNKNOWN CLASS '" << name << "': vtable = 0x" - << std::hex << unsigned(vtable) << std::dec << std::endl; + << std::hex << uintptr_t(vtable) << std::dec << std::endl; known[vtable] = NULL; return NULL; @@ -309,7 +330,7 @@ void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) return; std::cerr << "Attempt to create class '" << getName() << "' without known vtable." << std::endl; - abort(); + throw DFHack::Error::VTableMissing(getName()); } virtual_ptr virtual_identity::clone(virtual_ptr obj) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index 583e4d6a4..d6f0414bb 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -1,37 +1,35 @@ -#include "Internal.h" -#include "DataDefs.h" -#include "MiscUtils.h" -#include "VersionInfo.h" +#include #ifndef STATIC_FIELDS_GROUP -#include "df/world.h" -#include "df/world_data.h" -#include "df/ui.h" +#include "DataDefs.h" #endif -#include "DataIdentity.h" #include "DataFuncs.h" -#include - +#ifdef __GNUC__ #pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif namespace df { -#define NUMBER_IDENTITY_TRAITS(type) \ - number_identity identity_traits::identity(#type); +#define NUMBER_IDENTITY_TRAITS(category, type, name) \ + category##_identity identity_traits::identity(name); +#define INTEGER_IDENTITY_TRAITS(type, name) NUMBER_IDENTITY_TRAITS(integer, type, name) +#define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type, #type) #ifndef STATIC_FIELDS_GROUP - NUMBER_IDENTITY_TRAITS(char); - NUMBER_IDENTITY_TRAITS(int8_t); - NUMBER_IDENTITY_TRAITS(uint8_t); - NUMBER_IDENTITY_TRAITS(int16_t); - NUMBER_IDENTITY_TRAITS(uint16_t); - NUMBER_IDENTITY_TRAITS(int32_t); - NUMBER_IDENTITY_TRAITS(uint32_t); - NUMBER_IDENTITY_TRAITS(int64_t); - NUMBER_IDENTITY_TRAITS(uint64_t); - NUMBER_IDENTITY_TRAITS(float); - NUMBER_IDENTITY_TRAITS(double); + INTEGER_IDENTITY_TRAITS(char, "char"); + INTEGER_IDENTITY_TRAITS(signed char, "int8_t"); + INTEGER_IDENTITY_TRAITS(unsigned char, "uint8_t"); + INTEGER_IDENTITY_TRAITS(short, "int16_t"); + INTEGER_IDENTITY_TRAITS(unsigned short, "uint16_t"); + INTEGER_IDENTITY_TRAITS(int, "int32_t"); + INTEGER_IDENTITY_TRAITS(unsigned int, "uint32_t"); + INTEGER_IDENTITY_TRAITS(long, "long"); + INTEGER_IDENTITY_TRAITS(unsigned long, "unsigned long"); + INTEGER_IDENTITY_TRAITS(long long, "int64_t"); + INTEGER_IDENTITY_TRAITS(unsigned long long, "uint64_t"); + FLOAT_IDENTITY_TRAITS(float); + FLOAT_IDENTITY_TRAITS(double); bool_identity identity_traits::identity; stl_string_identity identity_traits::identity; @@ -53,6 +51,8 @@ namespace df { buffer_container_identity buffer_container_identity::base_instance; #endif #undef NUMBER_IDENTITY_TRAITS +#undef INTEGER_IDENTITY_TRAITS +#undef FLOAT_IDENTITY_TRAITS } #define TID(type) (&identity_traits< type >::identity) @@ -60,4 +60,5 @@ namespace df { #define FLD(mode, name) struct_field_info::mode, #name, offsetof(CUR_STRUCT, name) #define GFLD(mode, name) struct_field_info::mode, #name, (size_t)&df::global::name #define METHOD(mode, name) struct_field_info::mode, #name, 0, wrap_function(&CUR_STRUCT::name) +#define METHOD_N(mode, func, name) struct_field_info::mode, #name, 0, wrap_function(&CUR_STRUCT::func) #define FLD_END struct_field_info::END diff --git a/library/Error.cpp b/library/Error.cpp new file mode 100644 index 000000000..3cd0eb31d --- /dev/null +++ b/library/Error.cpp @@ -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)) +{} diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index 81ed828bc..22e7cc2e9 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -59,15 +59,16 @@ typedef struct interpose_s };*/ -#define DYLD_INTERPOSE(_replacment,_replacee) \ +#define DYLD_INTERPOSE(_replacement,_replacee) \ __attribute__((used)) static struct{ const void* replacment; const void* replacee; } \ _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = \ - { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee }; + { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; DYLD_INTERPOSE(DFH_SDL_Init,SDL_Init); DYLD_INTERPOSE(DFH_SDL_PollEvent,SDL_PollEvent); DYLD_INTERPOSE(DFH_SDL_Quit,SDL_Quit); DYLD_INTERPOSE(DFH_SDL_NumJoysticks,SDL_NumJoysticks); +DYLD_INTERPOSE(DFH_wgetch,wgetch); /******************************************************************************* * SDL part starts here * @@ -127,16 +128,11 @@ DFhackCExport int SDL_PushEvent(SDL::Event* event) } struct WINDOW; -DFhackCExport int wgetch(WINDOW *win) +DFhackCExport int DFH_wgetch(WINDOW *win) { - static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); - if(!_wgetch) - { - exit(EXIT_FAILURE); - } DFHack::Core & c = DFHack::Core::getInstance(); wgetch_again: - int in = _wgetch(win); + int in = wgetch(win); int out; if(c.ncurses_wgetch(in, out)) { diff --git a/library/Hooks-linux.cpp b/library/Hooks-linux.cpp index b0bf5a781..8291bf899 100644 --- a/library/Hooks-linux.cpp +++ b/library/Hooks-linux.cpp @@ -88,6 +88,10 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event) struct WINDOW; DFhackCExport int wgetch(WINDOW *win) { + if (getenv("DFHACK_HEADLESS")) + { + return 0; + } static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); if(!_wgetch) { diff --git a/library/Hooks-windows.cpp b/library/Hooks-windows.cpp index 7ea1385a7..3d925bc27 100644 --- a/library/Hooks-windows.cpp +++ b/library/Hooks-windows.cpp @@ -726,6 +726,22 @@ DFhackCExport uint32_t SDL_ThreadID(void) return _SDL_ThreadID(); } +static char* (*_SDL_getenv)(const char *name) = 0; +DFhackCExport char* SDL_getenv(const char *name) +{ + if(!inited) + FirstCall(); + return _SDL_getenv(name); +} + +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) +{ + if(!inited) + FirstCall(); + return _SDL_strlcat(dst, src, maxlen); +} + // FIXME: this has to be thread-safe. bool FirstCall() { @@ -813,6 +829,10 @@ bool FirstCall() _SDL_SemWait = (int (*)(void *))GetProcAddress(realSDLlib,"SDL_SemWait"); _SDL_ThreadID = (uint32_t (*)(void))GetProcAddress(realSDLlib,"SDL_ThreadID"); + // new in DF 0.43.05 + _SDL_getenv = (char* (*)(const char*))GetProcAddress(realSDLlib,"SDL_getenv"); + _SDL_strlcat = (size_t (*)(char*, const char*, size_t))GetProcAddress(realSDLlib,"SDL_strlcat"); + _SDL_EnableUNICODE(1); fprintf(stderr,"Initized HOOKS!\n"); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 712ccb541..292db698c 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -40,28 +40,34 @@ distribution. #include "DataFuncs.h" #include "DFHackVersion.h" #include "PluginManager.h" +#include "tinythread.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/Screen.h" -#include "modules/Job.h" -#include "modules/Translation.h" -#include "modules/Units.h" #include "modules/Items.h" -#include "modules/Materials.h" -#include "modules/Maps.h" +#include "modules/Job.h" +#include "modules/Kitchen.h" #include "modules/MapCache.h" -#include "modules/Burrows.h" -#include "modules/Buildings.h" -#include "modules/Constructions.h" +#include "modules/Maps.h" +#include "modules/Materials.h" #include "modules/Random.h" -#include "modules/Filesystem.h" +#include "modules/Screen.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "modules/World.h" #include "LuaWrapper.h" #include "LuaTools.h" #include "MiscUtils.h" +#include "df/activity_entry.h" +#include "df/activity_event.h" #include "df/job.h" #include "df/job_item.h" #include "df/building.h" @@ -82,6 +88,7 @@ distribution. #include "df/dfhack_material_category.h" #include "df/job_material_category.h" #include "df/burrow.h" +#include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/region_map_entry.h" #include "df/flow_info.h" @@ -89,6 +96,8 @@ distribution. #include "df/proj_itemst.h" #include "df/itemdef.h" #include "df/enabler.h" +#include "df/feature_init.h" +#include "df/plant.h" #include #include @@ -1364,6 +1373,7 @@ static void OpenModule(lua_State *state, const char *mname, #define WRAPM(module, function) { #function, df::wrap_function(module::function,true) } #define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) } +#define CWRAP(name, function) { #name, &Lua::CallWithCatchWrapper } /***** DFHack module *****/ @@ -1385,6 +1395,16 @@ static std::string getOSType() } } +static int getArchitecture() +{ + return sizeof(void*) * 8; +} + +static std::string getArchitectureName() +{ + return getArchitecture() == 64 ? "x86_64" : "x86"; +} + static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); } static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); } @@ -1402,6 +1422,8 @@ static std::string df2console(std::string s) { return DF2CONSOLE(s); } static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), + WRAP(getArchitecture), + WRAP(getArchitectureName), WRAP(getDFVersion), WRAP(getDFPath), WRAP(getTickCount), @@ -1427,6 +1449,26 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { /***** Gui module *****/ +static int gui_getDwarfmodeViewDims(lua_State *state) +{ + auto dims = Gui::getDwarfmodeViewDims(); + lua_newtable(state); + Lua::TableInsert(state, "map_x1", dims.map_x1); + Lua::TableInsert(state, "map_x2", dims.map_x2); + Lua::TableInsert(state, "menu_x1", dims.menu_x1); + Lua::TableInsert(state, "menu_x2", dims.menu_x2); + Lua::TableInsert(state, "area_x1", dims.area_x1); + Lua::TableInsert(state, "area_x2", dims.area_x2); + Lua::TableInsert(state, "y1", dims.y1); + Lua::TableInsert(state, "y2", dims.y2); + Lua::TableInsert(state, "map_y1", dims.map_y1); + Lua::TableInsert(state, "map_y2", dims.map_y2); + Lua::TableInsert(state, "menu_on", dims.menu_on); + Lua::TableInsert(state, "area_on", dims.area_on); + Lua::TableInsert(state, "menu_forced", dims.menu_forced); + return 1; +} + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), @@ -1436,6 +1478,11 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedBuilding), + WRAPM(Gui, getSelectedPlant), + WRAPM(Gui, getAnyUnit), + WRAPM(Gui, getAnyItem), + WRAPM(Gui, getAnyBuilding), + WRAPM(Gui, getAnyPlant), WRAPM(Gui, writeToGamelog), WRAPM(Gui, makeAnnouncement), WRAPM(Gui, addCombatReport), @@ -1444,13 +1491,33 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showAutoAnnouncement), + WRAPM(Gui, resetDwarfmodeView), + WRAPM(Gui, revealInDwarfmodeMap), + WRAPM(Gui, refreshSidebar), + WRAPM(Gui, inRenameBuilding), + WRAPM(Gui, getDepthAt), + { NULL, NULL } +}; + +static const luaL_Reg dfhack_gui_funcs[] = { + { "getDwarfmodeViewDims", gui_getDwarfmodeViewDims }, { NULL, NULL } }; /***** Job module *****/ -static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } -static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } +static bool jobEqual(const df::job *job1, const df::job *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[] = { WRAPM(Job,cloneJobStruct), @@ -1469,6 +1536,9 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,getName), WRAPM(Job,linkIntoWorld), WRAPM(Job,removePostings), + WRAPM(Job,disconnectJobItem), + WRAPM(Job,disconnectJobGeneralRef), + WRAPM(Job,removeJob), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } @@ -1520,6 +1590,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isSane), WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), + WRAPM(Units, isVisible), WRAPM(Units, getAge), WRAPM(Units, getKillCount), WRAPM(Units, getNominalSkill), @@ -1564,6 +1635,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isUndead), WRAPM(Units, isGelded), WRAPM(Units, isDomesticated), + WRAPM(Units, getMainSocialActivity), + WRAPM(Units, getMainSocialEvent), { NULL, NULL } }; @@ -1584,9 +1657,40 @@ static int units_getNoblePositions(lua_State *state) return 1; } +static int units_getUnitsInBox(lua_State *state) +{ + std::vector units; + int x1 = luaL_checkint(state, 1); + int y1 = luaL_checkint(state, 2); + int z1 = luaL_checkint(state, 3); + int x2 = luaL_checkint(state, 4); + int y2 = luaL_checkint(state, 5); + int z2 = luaL_checkint(state, 6); + + bool ok = Units::getUnitsInBox(units, x1, y1, z1, x2, y2, z2); + + if (ok && !lua_isnone(state, 7)) + { + luaL_checktype(state, 7, LUA_TFUNCTION); + units.erase(std::remove_if(units.begin(), units.end(), [&state](df::unit *unit) -> bool { + lua_dup(state); // copy function + Lua::PushDFObject(state, unit); + lua_call(state, 1, 1); + bool ret = lua_toboolean(state, -1); + lua_pop(state, 1); // remove return value + return !ret; + }), units.end()); + } + + Lua::PushVector(state, units); + lua_pushboolean(state, ok); + return 2; +} + static const luaL_Reg dfhack_units_funcs[] = { { "getPosition", units_getPosition }, { "getNoblePositions", units_getNoblePositions }, + { "getUnitsInBox", units_getUnitsInBox }, { NULL, NULL } }; @@ -1652,6 +1756,11 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getItemBaseValue), WRAPM(Items, getValue), WRAPM(Items, createItem), + WRAPM(Items, checkMandates), + WRAPM(Items, canTrade), + WRAPM(Items, canTradeWithContents), + WRAPM(Items, isRouteVehicle), + WRAPM(Items, isSquadEquipment), WRAPN(moveToGround, items_moveToGround), WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToInventory, items_moveToInventory), @@ -1733,6 +1842,13 @@ static int maps_isValidTilePos(lua_State *L) return 1; } +static int maps_isTileVisible(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + lua_pushboolean(L, Maps::isTileVisible(pos)); + return 1; +} + static int maps_getTileBlock(lua_State *L) { auto pos = CheckCoordXYZ(L, 1, true); @@ -1781,6 +1897,7 @@ static int maps_getTileBiomeRgn(lua_State *L) static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, + { "isTileVisible", maps_isTileVisible }, { "getTileBlock", maps_getTileBlock }, { "ensureTileBlock", maps_ensureTileBlock }, { "getTileType", maps_getTileType }, @@ -1885,6 +2002,8 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { WRAPM(Buildings, constructWithItems), WRAPM(Buildings, constructWithFilters), WRAPM(Buildings, deconstruct), + WRAPM(Buildings, markedForRemoval), + WRAPM(Buildings, getRoomDescription), WRAPM(Buildings, isActivityZone), WRAPM(Buildings, isPenPasture), WRAPM(Buildings, isPitPond), @@ -1970,13 +2089,22 @@ static int buildings_getStockpileContents(lua_State *state) return 1; } +static int buildings_getCageOccupants(lua_State *state) +{ + std::vector units; + Buildings::getCageOccupants(Lua::CheckDFObject(state, 1), units); + Lua::PushVector(state, units); + return 1; +} + static const luaL_Reg dfhack_buildings_funcs[] = { { "findAtTile", buildings_findAtTile }, { "findCivzonesAt", buildings_findCivzonesAt }, { "getCorrectSize", buildings_getCorrectSize }, - { "setSize", &Lua::CallWithCatchWrapper }, - { "getStockpileContents", buildings_getStockpileContents}, - { "findPenPitAt", buildings_findPenPitAt}, + CWRAP(setSize, buildings_setSize), + CWRAP(getStockpileContents, buildings_getStockpileContents), + { "findPenPitAt", buildings_findPenPitAt }, + CWRAP(getCageOccupants, buildings_getCageOccupants), { NULL, NULL } }; @@ -2042,7 +2170,8 @@ static int screen_paintTile(lua_State *L) } if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) pen.tile = luaL_checkint(L, 5); - lua_pushboolean(L, Screen::paintTile(pen, x, y)); + bool map = lua_toboolean(L, 6); + lua_pushboolean(L, Screen::paintTile(pen, x, y, map)); return 1; } @@ -2050,7 +2179,8 @@ static int screen_readTile(lua_State *L) { int x = luaL_checkint(L, 1); int y = luaL_checkint(L, 2); - Pen pen = Screen::readTile(x, y); + bool map = lua_toboolean(L, 3); + Pen pen = Screen::readTile(x, y, map); Lua::Push(L, pen); return 1; } @@ -2062,7 +2192,8 @@ static int screen_paintString(lua_State *L) int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); const char *text = luaL_checkstring(L, 4); - lua_pushboolean(L, Screen::paintString(pen, x, y, text)); + bool map = lua_toboolean(L, 5); + lua_pushboolean(L, Screen::paintString(pen, x, y, text, map)); return 1; } @@ -2074,7 +2205,8 @@ static int screen_fillRect(lua_State *L) int y1 = luaL_checkint(L, 3); int x2 = luaL_checkint(L, 4); int y2 = luaL_checkint(L, 5); - lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2)); + bool map = lua_toboolean(L, 6); + lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2, map)); return 1; } @@ -2120,7 +2252,8 @@ int screen_show(lua_State *L) static int screen_dismiss(lua_State *L) { df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); - Screen::dismiss(screen); + bool to_first = lua_toboolean(L, 2); + Screen::dismiss(screen, to_first); return 0; } @@ -2202,9 +2335,9 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, - { "show", &Lua::CallWithCatchWrapper }, - { "dismiss", screen_dismiss }, - { "isDismissed", screen_isDismissed }, + CWRAP(show, screen_show), + CWRAP(dismiss, screen_dismiss), + CWRAP(isDismissed, screen_isDismissed), { "_doSimulateInput", screen_doSimulateInput }, { "keyToChar", screen_keyToChar }, { "charToKey", screen_charToKey }, @@ -2242,7 +2375,7 @@ static int filesystem_listdir(lua_State *L) return 3; } lua_newtable(L); - for(int i=0;i(state, 1))); +} + +static const luaL_Reg dfhack_designations_funcs[] = { + {"getPlantDesignationTile", designations_getPlantDesignationTile}, + {NULL, NULL} +}; + +/***** Kitchen module *****/ + +static const LuaWrapper::FunctionReg dfhack_kitchen_module[] = { + WRAPM(Kitchen, findExclusion), + WRAPM(Kitchen, addExclusion), + WRAPM(Kitchen, removeExclusion), + {NULL, NULL} +}; + +/***** Console module *****/ + +namespace console { + void clear() { + Core::getInstance().getConsole().clear(); + } + void flush() { + Core::getInstance().getConsole() << std::flush; + } +} + +static const LuaWrapper::FunctionReg dfhack_console_module[] = { + WRAPM(console, clear), + WRAPM(console, flush), + { NULL, NULL } +}; + /***** Internal module *****/ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) @@ -2310,16 +2490,20 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) return rv; } -static uint32_t getImageBase() { return Core::getInstance().p->getBase(); } -static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } +static md5wrapper md5_wrap; + +static uintptr_t getImageBase() { return Core::getInstance().p->getBase(); } +static intptr_t getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static int8_t getModstate() { return Core::getInstance().getModstate(); } static std::string internal_strerror(int n) { return strerror(n); } +static std::string internal_md5(std::string s) { return md5_wrap.getHashFromString(s); } static const LuaWrapper::FunctionReg dfhack_internal_module[] = { WRAP(getImageBase), WRAP(getRebaseDelta), WRAP(getModstate), WRAPN(strerror, internal_strerror), + WRAPN(md5, internal_md5), { NULL, NULL } }; @@ -2344,9 +2528,9 @@ static int internal_getPE(lua_State *L) static int internal_getAddress(lua_State *L) { const char *name = luaL_checkstring(L, 1); - uint32_t addr = Core::getInstance().vinfo->getAddress(name); + uintptr_t addr = Core::getInstance().vinfo->getAddress(name); if (addr) - lua_pushnumber(L, addr); + lua_pushinteger(L, addr); else lua_pushnil(L); return 1; @@ -2355,7 +2539,7 @@ static int internal_getAddress(lua_State *L) static int internal_setAddress(lua_State *L) { std::string name = luaL_checkstring(L, 1); - uint32_t addr = (uint32_t)checkaddr(L, 2, true); + uintptr_t addr = (uintptr_t)checkaddr(L, 2, true); internal_getAddress(L); // Set the address @@ -2372,8 +2556,8 @@ static int internal_setAddress(lua_State *L) } // Print via printerr, so that it is definitely logged to stderr.log. - uint32_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta(); - fprintf(stderr, "Setting global '%s' to %x (%x)\n", name.c_str(), addr, iaddr); + uintptr_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta(); + fprintf(stderr, "Setting global '%s' to %p (%p)\n", name.c_str(), (void*)addr, (void*)iaddr); fflush(stderr); return 1; @@ -2382,9 +2566,9 @@ static int internal_setAddress(lua_State *L) static int internal_getVTable(lua_State *L) { const char *name = luaL_checkstring(L, 1); - uint32_t addr = (uint32_t)Core::getInstance().vinfo->getVTable(name); + uintptr_t addr = (uintptr_t)Core::getInstance().vinfo->getVTable(name); if (addr) - lua_pushnumber(L, addr); + lua_pushinteger(L, addr); else lua_pushnil(L); return 1; @@ -2412,9 +2596,9 @@ static int internal_getMemRanges(lua_State *L) for(size_t i = 0; i < ranges.size(); i++) { lua_newtable(L); - lua_pushnumber(L, (uint32_t)ranges[i].start); + lua_pushinteger(L, (uintptr_t)ranges[i].start); lua_setfield(L, -2, "start_addr"); - lua_pushnumber(L, (uint32_t)ranges[i].end); + lua_pushinteger(L, (uintptr_t)ranges[i].end); lua_setfield(L, -2, "end_addr"); lua_pushstring(L, ranges[i].name); lua_setfield(L, -2, "name"); @@ -2491,7 +2675,7 @@ static int internal_patchBytes(lua_State *L) { uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); int isnum; - uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum); + lua_tounsignedx(L, -1, &isnum); if (!isnum) luaL_error(L, "invalid value in write table"); lua_pop(L, 1); @@ -2553,6 +2737,9 @@ static int internal_memscan(lua_State *L) for (int i = 0; i <= hcount; i++) { uint8_t *p = haystack + i*hstep; + if (p + nsize > haystack + (hcount * hstep)) { + break; + } if (memcmp(p, needle, nsize) == 0) { lua_pushinteger(L, i); lua_pushinteger(L, (lua_Integer)p); @@ -2719,6 +2906,56 @@ static int internal_findScript(lua_State *L) return 1; } +static int internal_threadid(lua_State *L) +{ + std::stringstream ss; + ss << tthread::this_thread::get_id(); + int i; + ss >> i; + lua_pushinteger(L, i); + return 1; +} + +static int internal_md5file(lua_State *L) +{ + const char *s = luaL_checkstring(L, 1); + uint32_t len; + char *first_kb_raw = nullptr; + std::vector first_kb; + if (lua_toboolean(L, 2)) + first_kb_raw = new char[1024]; + + std::string hash = md5_wrap.getHashFromFile(s, len, first_kb_raw); + bool err = (hash.find("file") != std::string::npos); + + if (first_kb_raw) + { + first_kb.assign(first_kb_raw, first_kb_raw + 1024); + delete[] first_kb_raw; + } + + if (err) + { + lua_pushnil(L); + lua_pushstring(L, hash.c_str()); + return 2; + } + else + { + lua_pushstring(L, hash.c_str()); + lua_pushinteger(L, len); + if (!first_kb.empty()) + { + Lua::PushVector(L, first_kb); + return 3; + } + else + { + return 2; + } + } +} + static const luaL_Reg dfhack_internal_funcs[] = { { "getPE", internal_getPE }, { "getMD5", internal_getmd5 }, @@ -2740,6 +2977,8 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "removeScriptPath", internal_removeScriptPath }, { "getScriptPaths", internal_getScriptPaths }, { "findScript", internal_findScript }, + { "threadid", internal_threadid }, + { "md5File", internal_md5file }, { NULL, NULL } }; @@ -2757,7 +2996,7 @@ void OpenDFHackApi(lua_State *state) OpenRandom(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); - OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); @@ -2765,8 +3004,11 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs); OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_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, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); + OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs); + OpenModule(state, "kitchen", dfhack_kitchen_module); + OpenModule(state, "console", dfhack_console_module); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 008cb00c2..24d6917ff 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -319,7 +319,7 @@ static int yield_helper(lua_State *S) } namespace { - int dfhack_lineedit_cont(lua_State *L, int status, int) + int dfhack_lineedit_cont(lua_State *L, int status, lua_KContext) { if (Lua::IsSuccess(status)) return lua_gettop(L) - 2; @@ -636,7 +636,7 @@ static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space } namespace { - int safecall_cont(lua_State *L, int status, int) + int safecall_cont(lua_State *L, int status, lua_KContext) { bool success = do_finish_pcall(L, Lua::IsSuccess(status)); @@ -953,6 +953,7 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std env_idx = lua_absindex(state, env_idx); int base = lua_gettop(state); + (void)base; // used in assert() // Parse the code if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK) @@ -1138,7 +1139,7 @@ static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool succes return success; } -int dfhack_cleanup_cont(lua_State *L, int status, int) +int dfhack_cleanup_cont(lua_State *L, int status, lua_KContext) { bool success = Lua::IsSuccess(status); @@ -1246,6 +1247,11 @@ static int dfhack_open_plugin(lua_State *L) return 0; } +static int gettop_wrapper(lua_State *L, int, lua_KContext) +{ + return lua_gettop(L); +} + static int dfhack_curry_wrap(lua_State *L) { int nargs = lua_gettop(L); @@ -1261,7 +1267,7 @@ static int dfhack_curry_wrap(lua_State *L) for (int i = 1; i <= ncurry; i++) lua_copy(L, lua_upvalueindex(i+1), i); - lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop); + lua_callk(L, scount-1, LUA_MULTRET, 0, gettop_wrapper); return lua_gettop(L); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index fa9445a81..f744f3eba 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -115,12 +115,26 @@ void enum_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int va base_type->lua_write(state, fname_idx, ptr, val_index); } -void df::number_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +void df::integer_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushinteger(state, read(ptr)); +} + +void df::integer_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + int is_num = 0; + auto value = lua_tointegerx(state, val_index, &is_num); + if (!is_num) + field_error(state, fname_idx, "integer expected", "write"); + write(ptr, value); +} + +void df::float_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) { lua_pushnumber(state, read(ptr)); } -void df::number_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +void df::float_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) { if (!lua_isnumber(state, val_index)) field_error(state, fname_idx, "number expected", "write"); @@ -305,7 +319,7 @@ void container_identity::lua_item_write(lua_State *state, int fname_idx, void *p id->lua_write(state, fname_idx, pitem, val_index); } -bool container_identity::lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +bool container_identity::lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) { auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); @@ -351,7 +365,7 @@ void ptr_container_identity::lua_item_write(lua_State *state, int fname_idx, voi df::pointer_identity::lua_write(state, fname_idx, pitem, id, val_index); } -bool ptr_container_identity::lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +bool ptr_container_identity::lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) { auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); @@ -887,7 +901,7 @@ static int method_container_insert(lua_State *state) int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN); int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true); - if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3)) + if (!id->lua_insert2(state, UPVAL_METHOD_NAME, ptr, idx, 3)) field_error(state, UPVAL_METHOD_NAME, "not supported", "call"); return 0; } @@ -898,6 +912,7 @@ static int method_container_insert(lua_State *state) static int meta_bitfield_len(lua_State *state) { uint8_t *ptr = get_object_addr(state, 1, 0, "get size"); + (void)ptr; auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); lua_pushinteger(state, id->getNumBits()); return 1; @@ -931,7 +946,7 @@ static int meta_bitfield_index(lua_State *state) { size_t intv = 0; memcpy(&intv, ptr, std::min(sizeof(intv), size_t(id->byte_size()))); - lua_pushnumber(state, intv); + lua_pushinteger(state, intv); return 1; } @@ -1061,15 +1076,8 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id try { id->invoke(state, 1); } - catch (Error::NullPointer &e) { - const char *vn = e.varname(); - 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::All &e) { + field_error(state, UPVAL_METHOD_NAME, e.what(), "invoke"); } catch (std::exception &e) { std::string tmp = stl_sprintf("C++ exception: %s", e.what()); @@ -1087,13 +1095,8 @@ int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *cont try { return fn(state); } - catch (Error::NullPointer &e) { - const char *vn = e.varname(); - 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::All &e) { + return luaL_error(state, "%s: %s", context, e.what()); } catch (std::exception &e) { return luaL_error(state, "%s: C++ exception: %s", context, e.what()); @@ -1175,9 +1178,8 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo continue; case struct_field_info::POINTER: - // Skip class-typed pointers within unions - if ((fields[i].count & 2) != 0 && fields[i].type && - fields[i].type->type() == IDTYPE_CLASS) + // Skip class-typed pointers within unions and other bad pointers + if ((fields[i].count & 2) != 0 && fields[i].type) add_to_enum = false; break; diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index dac458709..fe8309736 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -46,8 +46,6 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; -static luaL_Reg no_functions[] = { { NULL, NULL } }; - /** * Report an error while accessing a field (index = field name). */ @@ -449,9 +447,12 @@ Lua::ObjectClass Lua::IsDFObject(lua_State *state, int val_index) static const char *const primitive_types[] = { "string", "ptr-string", + "char", "int8_t", "uint8_t", "int16_t", "uint16_t", "int32_t", "uint32_t", "int64_t", "uint64_t", - "bool", "float", "double", + "intptr_t", "uintptr_t", "long", "unsigned long", + "bool", + "float", "double", "pointer", "ptr-vector", "bit-vector", @@ -461,10 +462,13 @@ static const char *const primitive_types[] = { static type_identity *const primitive_identities[] = { df::identity_traits::get(), df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), + df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), df::identity_traits::get(), @@ -572,7 +576,7 @@ static int meta_sizeof(lua_State *state) if (lua_isnil(state, 1) || lua_islightuserdata(state, 1)) { lua_pushnil(state); - lua_pushnumber(state, (size_t)lua_touserdata(state, 1)); + lua_pushinteger(state, (size_t)lua_touserdata(state, 1)); return 2; } @@ -595,7 +599,7 @@ static int meta_sizeof(lua_State *state) // Add the address if (lua_isuserdata(state, 1)) { - lua_pushnumber(state, (size_t)get_object_ref(state, 1)); + lua_pushinteger(state, (size_t)get_object_ref(state, 1)); return 2; } else @@ -990,10 +994,23 @@ static int meta_ptr_tostring(lua_State *state) { uint8_t *ptr = get_object_addr(state, 1, 0, "access"); + bool has_length = false; + uint64_t length = 0; + auto *cid = dynamic_cast(get_object_identity(state, 1, "__tostring()", true, true)); + + if (cid && (cid->type() == IDTYPE_CONTAINER || cid->type() == IDTYPE_STL_PTR_VECTOR)) + { + has_length = true; + length = cid->lua_item_count(state, ptr, container_identity::COUNT_LEN); + } + lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); - lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (unsigned)ptr).c_str()); + if (has_length) + lua_pushstring(state, stl_sprintf("<%s[%llu]: %p>", cname, length, (void*)ptr).c_str()); + else + lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str()); return 1; } @@ -1008,11 +1025,23 @@ static int meta_enum_attr_index(lua_State *state) luaL_error(state, "Invalid index in enum.attrs[]"); auto id = (enum_identity*)lua_touserdata(state, lua_upvalueindex(2)); + auto *complex = id->getComplex(); int64_t idx = lua_tonumber(state, 2); - if (idx < id->getFirstItem() || idx > id->getLastItem()) - idx = id->getLastItem()+1; - idx -= id->getFirstItem(); + 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()) + idx = id->getLastItem()+1; + idx -= id->getFirstItem(); + } uint8_t *ptr = (uint8_t*)id->getAttrs(); auto atype = id->getAttrType(); @@ -1332,6 +1361,68 @@ static int wtype_next_item(lua_State *state) 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 &children); void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name) @@ -1354,24 +1445,36 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit int base = lua_gettop(state); lua_newtable(state); + auto *complex = eid->getComplex(); + // For enums, set mapping between keys and values - for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++) + if (complex) { - if (keys[j]) - AssociateId(state, base+1, i, keys[j]); + 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++) + { + if (keys[j]) + AssociateId(state, base+1, i, keys[j]); + } } - if (eid->getFirstItem() <= eid->getLastItem()) + if (complex) { - lua_pushvalue(state, base+1); - lua_pushinteger(state, eid->getFirstItem()-1); - lua_pushinteger(state, eid->getLastItem()); - lua_pushcclosure(state, wtype_ipairs, 3); + 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, eid->getFirstItem()); - lua_pushinteger(state, eid->getLastItem()); - lua_pushcclosure(state, wtype_next_item, 2); + 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()); @@ -1379,6 +1482,34 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit 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()) + { + lua_pushvalue(state, base + 1); + lua_pushinteger(state, eid->getFirstItem() - 1); + lua_pushinteger(state, eid->getLastItem()); + lua_pushcclosure(state, wtype_ipairs, 3); + lua_setfield(state, ix_meta, "__ipairs"); + + lua_pushinteger(state, eid->getFirstItem()); + lua_pushinteger(state, eid->getLastItem()); + lua_pushcclosure(state, wtype_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, false); + lua_setfield(state, ftable, "_complex"); + } } SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); @@ -1575,8 +1706,6 @@ static void RenderTypeChildren(lua_State *state, const std::vector @@ -37,18 +36,11 @@ distribution. #include #include #include +#include #include #include -const char *DFHack::Error::NullPointer::what() const throw() { - return "DFHack::Error::NullPointer"; -} - -const char *DFHack::Error::InvalidArgument::what() const throw() { - return "DFHack::Error::InvalidArgument"; -} - std::string stl_sprintf(const char *fmt, ...) { va_list lst; va_start(lst, fmt); @@ -61,11 +53,14 @@ std::string stl_vsprintf(const char *fmt, va_list args) { std::vector buf; buf.resize(4096); for (;;) { - int rsz = vsnprintf(&buf[0], buf.size(), fmt, args); + va_list args2; + va_copy(args2, args); + int rsz = vsnprintf(&buf[0], buf.size(), fmt, args2); + va_end(args2); if (rsz < 0) buf.resize(buf.size()*2); - else if (unsigned(rsz) > buf.size()) + else if (unsigned(rsz) >= buf.size()) buf.resize(rsz+1); else return std::string(&buf[0], rsz); @@ -125,6 +120,35 @@ std::string toLower(const std::string &str) return rv; } +bool word_wrap(std::vector *out, const std::string &str, size_t line_length) +{ + out->clear(); + std::istringstream input(str); + std::string out_line; + std::string word; + if (input >> word) + { + out_line += word; + // size_t remaining = line_length - std::min(line_length, word.length()); + while (input >> word) + { + if (out_line.length() + word.length() + 1 <= line_length) + { + out_line += ' '; + out_line += word; + } + else + { + out->push_back(out_line); + out_line = word; + } + } + if (out_line.length()) + out->push_back(out_line); + } + return true; +} + bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail) { size_t ksize = key.size(); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 774d19143..8e3f436cb 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -184,8 +184,8 @@ struct Plugin::LuaEvent : public Lua::Event::Owner { Plugin::Plugin(Core * core, const std::string & path, const std::string &name, PluginManager * pm) - :name(name), - path(path), + :path(path), + name(name), parent(pm) { plugin_lib = 0; @@ -276,6 +276,7 @@ bool Plugin::load(color_ostream &con) plugin_check_symbol("plugin_name") plugin_check_symbol("plugin_version") + plugin_check_symbol("plugin_abi_version") plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_globals") @@ -287,11 +288,19 @@ bool Plugin::load(color_ostream &con) return false; } 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"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); const char *dfhack_version = Version::dfhack_version(); const char *dfhack_git_desc = Version::git_description(); 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) { con.printerr("Plugin %s was not built for this version of DFHack.\n" @@ -492,7 +501,6 @@ command_result Plugin::invoke(color_ostream &out, const std::string & command, s bool Plugin::can_invoke_hotkey(const std::string & command, df::viewscreen *top ) { - Core & c = Core::getInstance(); bool cr = false; access->lock_add(); if(state == PS_LOADED) @@ -798,6 +806,25 @@ PluginManager::~PluginManager() void PluginManager::init() { 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) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 6f159e5fe..2091d9a96 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -41,6 +41,7 @@ using namespace std; #include #include "MemAccess.h" +#include "Memory.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" @@ -118,8 +119,8 @@ Process::~Process() string Process::doReadClassName (void * vptr) { //FIXME: BAD!!!!! - char * typeinfo = Process::readPtr(((char *)vptr - 0x4)); - char * typestring = Process::readPtr(typeinfo + 0x4); + char * typeinfo = Process::readPtr(((char *)vptr - sizeof(void*))); + char * typestring = Process::readPtr(typeinfo + sizeof(void*)); string raw = readCString(typestring); size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers size_t end = raw.length(); @@ -151,9 +152,15 @@ void Process::getMemRanges( vector & ranges ) the_task = mach_task_self(); +#ifdef DFHACK64 + mach_vm_size_t vmsize; + mach_vm_address_t address; + vm_region_basic_info_data_64_t info; +#else vm_size_t vmsize; vm_address_t address; vm_region_basic_info_data_t info; +#endif mach_msg_type_number_t info_count; vm_region_flavor_t flavor; memory_object_name_t object; @@ -162,10 +169,18 @@ void Process::getMemRanges( vector & ranges ) address = 0; do { +#ifdef DFHACK64 + flavor = VM_REGION_BASIC_INFO_64; + info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = mach_vm_region(the_task, &address, &vmsize, flavor, + (vm_region_info_64_t)&info, &info_count, &object); +#else flavor = VM_REGION_BASIC_INFO; info_count = VM_REGION_BASIC_INFO_COUNT; kr = vm_region(the_task, &address, &vmsize, flavor, (vm_region_info_t)&info, &info_count, &object); +#endif + if (kr == KERN_SUCCESS) { if (info.reserved==1) { address += vmsize; @@ -193,8 +208,10 @@ void Process::getMemRanges( vector & ranges ) if (log_ranges) { fprintf(stderr, - "%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", - address, (address + vmsize), (vmsize >> 10), + "%p-%p %8zuK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", + (void*)address, + (void*)(address + vmsize), + size_t(vmsize >> 10), (info.protection & VM_PROT_READ) ? 'r' : '-', (info.protection & VM_PROT_WRITE) ? 'w' : '-', (info.protection & VM_PROT_EXECUTE) ? 'x' : '-', @@ -227,7 +244,7 @@ void Process::getMemRanges( vector & ranges ) uintptr_t Process::getBase() { - return 0x1000; + return DEFAULT_BASE_ADDR; // Memory.h } int Process::adjustOffset(int offset, bool /*to_file*/) @@ -235,22 +252,6 @@ int Process::adjustOffset(int offset, bool /*to_file*/) return offset; } -static int getdir (string dir, vector &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() { struct timeval tp; @@ -274,6 +275,11 @@ string Process::getPath() if (_NSGetExecutablePath(path, &size) == 0) { real_path = realpath(path, NULL); } + else { + fprintf(stderr, "_NSGetExecutablePath failed!\n"); + cached_path = "."; + return cached_path; + } std::string path_string(real_path); int last_slash = path_string.find_last_of("/"); cached_path = path_string.substr(0,last_slash); diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 1a1e754d0..fa06d28e4 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -39,6 +39,7 @@ using namespace std; #include #include "MemAccess.h" +#include "Memory.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" @@ -122,8 +123,8 @@ Process::~Process() string Process::doReadClassName (void * vptr) { //FIXME: BAD!!!!! - char * typeinfo = Process::readPtr(((char *)vptr - 0x4)); - char * typestring = Process::readPtr(typeinfo + 0x4); + char * typeinfo = Process::readPtr(((char *)vptr - sizeof(void*))); + char * typestring = Process::readPtr(typeinfo + sizeof(void*)); string raw = readCString(typestring); size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers size_t end = raw.length(); @@ -167,7 +168,7 @@ void Process::getMemRanges( vector & ranges ) uintptr_t Process::getBase() { - return 0x8048000; + return DEFAULT_BASE_ADDR; // Memory.h } int Process::adjustOffset(int offset, bool /*to_file*/) @@ -175,22 +176,6 @@ int Process::adjustOffset(int offset, bool /*to_file*/) return offset; } -static int getdir (string dir, vector &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() { struct timeval tp; diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 5474d7cfb..3a1da0ac4 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -43,6 +43,7 @@ using namespace std; #include "VersionInfoFactory.h" #include "Error.h" #include "MemAccess.h" +#include "Memory.h" using namespace DFHack; namespace DFHack { @@ -306,7 +307,7 @@ uintptr_t Process::getBase() { if(d) return (uintptr_t) d->base; - return 0x400000; + return DEFAULT_BASE_ADDR; // Memory.h } int Process::adjustOffset(int offset, bool to_file) @@ -341,12 +342,21 @@ int Process::adjustOffset(int offset, bool to_file) return -1; } - string Process::doReadClassName (void * vptr) { - char * rtti = readPtr((char *)vptr - 0x4); + char * rtti = readPtr((char *)vptr - sizeof(void*)); +#ifdef DFHACK64 + void *base; + if (!RtlPcToFileHeader(rtti, &base)) + return "dummy"; + char * typeinfo = (char *)base + readDWord(rtti + 0xC); + string raw = readCString(typeinfo + 0x10+4); // skips the .?AV +#else char * typeinfo = readPtr(rtti + 0xC); string raw = readCString(typeinfo + 0xC); // skips the .?AV +#endif + if (!raw.length()) + return "dummy"; raw.resize(raw.length() - 2);// trim @@ from end return raw; } diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 09861ad5f..07f9c6961 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -155,7 +155,7 @@ bool RemoteClient::connect(int port) return false; } - if (!socket->Open((const uint8 *)"localhost", port)) + if (!socket->Open("localhost", port)) { default_output().printerr("Could not connect to localhost: %d\n", port); return false; diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 06a9f859c..e4204014f 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -63,6 +63,8 @@ using namespace DFHack; #include "tinythread.h" using namespace tthread; +#include "jsoncpp.h" + using dfproto::CoreTextNotification; using dfproto::CoreTextFragment; using google::protobuf::MessageLite; @@ -117,6 +119,7 @@ ServerConnection::ServerConnection(CActiveSocket *socket) core_service->finalize(this, &functions); thread = new tthread::thread(threadFn, (void*)this); + thread->detach(); } ServerConnection::~ServerConnection() @@ -124,6 +127,7 @@ ServerConnection::~ServerConnection() in_error = true; socket->Close(); delete socket; + delete thread; for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it) delete it->second; @@ -259,7 +263,7 @@ void ServerConnection::threadFn() break; } - std::auto_ptr buf(new uint8_t[header.size]); + std::unique_ptr buf(new uint8_t[header.size]); if (!readFullBuffer(socket, buf.get(), header.size)) { @@ -282,7 +286,11 @@ void ServerConnection::threadFn() } 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); } @@ -363,6 +371,7 @@ ServerMain::~ServerMain() { socket->Close(); delete socket; + delete thread; } bool ServerMain::listen(int port) @@ -372,10 +381,47 @@ bool ServerMain::listen(int port) socket->Initialize(); - if (!socket->Listen((const uint8 *)"127.0.0.1", port)) - return false; + 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(); + port = configJson.get("port", port).asInt(); + } + + configJson["allow_remote"] = allow_remote; + configJson["port"] = port; + + std::ofstream outFile(filename, std::ios_base::trunc); + + if (outFile.is_open()) + { + outFile << configJson; + outFile.close(); + } + + if (allow_remote) + { + if (!socket->Listen(NULL, port)) + return false; + } + else + { + if (!socket->Listen("127.0.0.1", port)) + return false; + } thread = new tthread::thread(threadFn, this); + thread->detach(); return true; } diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 77374ea0c..d0bdee00f 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -395,6 +395,7 @@ static command_result GetWorldInfo(color_ostream &stream, { case game_type::DWARF_MAIN: case game_type::DWARF_RECLAIM: + case game_type::DWARF_UNRETIRE: out->set_mode(GetWorldInfoOut::MODE_DWARF); out->set_civ_id(ui->civ_id); out->set_site_id(ui->site_id); @@ -403,6 +404,7 @@ static command_result GetWorldInfo(color_ostream &stream, break; case game_type::ADVENTURE_MAIN: + case game_type::ADVENTURE_ARENA: out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); if (auto unit = vector_get(world->units.active, 0)) @@ -652,29 +654,29 @@ CoreService::CoreService() { suspend_depth = 0; // 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); // Add others here: - addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); - addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); + addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); + addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); addMethod("RunLua", &CoreService::RunLua); // Functions: - addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); - addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); + addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); + 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("ListJobSkills", ListJobSkills, 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 | SF_ALLOW_REMOTE); - addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); - addFunction("ListUnits", ListUnits); - addFunction("ListSquads", ListSquads); + addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE | SF_ALLOW_REMOTE); + addFunction("ListUnits", ListUnits, SF_ALLOW_REMOTE); + addFunction("ListSquads", ListSquads, SF_ALLOW_REMOTE); - addFunction("SetUnitLabors", SetUnitLabors); + addFunction("SetUnitLabors", SetUnitLabors, SF_ALLOW_REMOTE); } CoreService::~CoreService() diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index eb8dec861..520d91061 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -82,7 +82,7 @@ using namespace DFHack; // multiple, but not virtual inheritance. struct MSVC_MPTR { void *method; - intptr_t this_shift; + uint32_t this_shift; // was intptr_t pre-0.43.05 }; // Debug builds sometimes use additional thunks that @@ -96,6 +96,11 @@ static uint32_t *follow_jmp(void *ptr) { switch (*p) { +#ifdef DFHACK64 + case 0x48: // REX prefix + p++; + break; +#endif case 0xE9: // jmp near rel32 p += 5 + *(int32_t*)(p+1); break; @@ -304,8 +309,8 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v * - interpose_method comes from method_pointer_to_addr_ */ - fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x (%s)\n", - vmethod_idx, unsigned(interpose_method), name_str); + fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %p (%s)\n", + vmethod_idx, interpose_method, name_str); fflush(stderr); abort(); } diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index f6bd5cba9..7033fd598 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -34,6 +34,7 @@ using namespace std; #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" +#include "Memory.h" using namespace DFHack; #include @@ -69,7 +70,7 @@ VersionInfo * VersionInfoFactory::getVersionInfoByMD5(string hash) return 0; } -VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uint32_t timestamp) +VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) { for(size_t i = 0; i < versions.size();i++) { @@ -98,28 +99,27 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) if(os == "windows") { mem->setOS(OS_WINDOWS); - // set default image base. this is fixed for base relocation later - mem->setBase(0x400000); } else if(os == "linux") { mem->setOS(OS_LINUX); - // this is wrong... I'm not going to do base image relocation on linux though. - mem->setBase(0x8048000); } else if(os == "darwin") { mem->setOS(OS_APPLE); - // this is wrong... I'm not going to do base image relocation on linux though. - mem->setBase(0x1000); } else { return; // ignore it if it's invalid } + mem->setBase(DEFAULT_BASE_ADDR); // Memory.h // process additional entries //cout << "Entry " << cstr_version << " " << cstr_os << endl; + if (!entry->FirstChildElement()) { + cerr << "Empty symbol table: " << entry->Attribute("name") << endl; + return; + } pMemEntry = entry->FirstChildElement()->ToElement(); for(;pMemEntry;pMemEntry=pMemEntry->NextSiblingElement()) { @@ -140,7 +140,11 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) } if ((is_vtable && no_vtables) || (!is_vtable && no_globals)) continue; - uint32_t addr = strtol(cstr_value, 0, 0); +#ifdef DFHACK64 + uintptr_t addr = strtoull(cstr_value, 0, 0); +#else + uintptr_t addr = strtol(cstr_value, 0, 0); +#endif if (is_vtable) mem->setVTable(cstr_key, addr); else @@ -149,7 +153,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) else if (type == "md5-hash") { 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) throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); mem->addMD5(cstr_value); @@ -157,7 +161,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) else if (type == "binary-timestamp") { 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) throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); mem->addPE(strtol(cstr_value, 0, 16)); diff --git a/library/doc/CMakeLists.txt b/library/doc/CMakeLists.txt deleted file mode 100644 index 085f21119..000000000 --- a/library/doc/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# repurposed from libnoise: http://github.com/qknight/libnoise/tree/master/doc/ -# following code and comments is by the original author, with some changes by -# me (peterix) -# ------------------------------------------------------------------------------ -# -# many thanks go to Philippe Poilbarbe for writing the code this file is based on -# http://www.cmake.org/pipermail/cmake/2006-August/010794.html -# -# much later i also found this: -# http://tobias.rautenkranz.ch/cmake/doxygen/ -# but it is hard to understand... - -FIND_PACKAGE(Doxygen QUIET) - -IF(DOXYGEN_FOUND) - SET(DOXYGEN_LANGUAGE "English" CACHE STRING "Language used by doxygen") - MARK_AS_ADVANCED(DOXYGEN_LANGUAGE) - - # you could also set the version with this, see Doxygen.in - # there you will find a line like this: - # PROJECT_NUMBER = @DFHACK_VERSION@ - # @DFHACK_VERSION@ is then replaced by our global DFHACK_VERSION - # - # for instance you could uncomment the next 3 lines and change the version for testing - # SET(DFHACK_VERSION - # "1.2.3-foo500" - # ) - - # doxygen can reference external images with IMAGE_PATH, this is how we set it dynamically - SET( CMAKE_DOXYGEN_IMAGE_PATH - "${CMAKE_CURRENT_SOURCE_DIR}/img" - ) - - # doxygen searches for source code (defined in FILE_PATTERNS, for example: *.cpp *.h) - # with DOXYGEN_SOURCE_DIR we fill a list of directories and later we write it into - # the Doxyfile with a REGEX REPLACE (see below) - SET( DOXYGEN_SOURCE_DIR - "${dfhack_SOURCE_DIR}/library" - ) - - STRING(REGEX REPLACE ";" " " CMAKE_DOXYGEN_INPUT_LIST "${DOXYGEN_SOURCE_DIR}") - set(DOXYFILE_DOT "NO") - if(DOXYGEN_DOT_EXECUTABLE) - set(DOXYFILE_DOT "YES") - endif() - CONFIGURE_FILE(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - ADD_CUSTOM_TARGET(doxygen ALL - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - - INSTALL( DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html/" DESTINATION ${DFHACK_DEVDOC_DESTINATION}/doxygen ) - INSTALL( FILES "Doxygen.html" DESTINATION ${DFHACK_DEVDOC_DESTINATION}) -ELSE(DOXYGEN_FOUND) - MESSAGE (WARNING "Doxygen binary couldn't be found. Can't build development documentation.'") -ENDIF(DOXYGEN_FOUND) \ No newline at end of file diff --git a/library/doc/Doxyfile.in b/library/doc/Doxyfile.in deleted file mode 100644 index 5ee8f500e..000000000 --- a/library/doc/Doxyfile.in +++ /dev/null @@ -1,1554 +0,0 @@ -# Doxyfile 1.6.3 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = dfhack - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = "@DFHACK_VERSION@" - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = html -# @DOXYGEN_OUTPUT_DIR@ - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. - -OUTPUT_LANGUAGE = @DOXYGEN_LANGUAGE@ - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = NO - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) - -JAVADOC_AUTOBRIEF = YES - -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it parses. -# With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this tag. -# The format is ext=language, where ext is a file extension, and language is one of -# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, -# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = YES - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. - -TYPEDEF_HIDES_STRUCT = NO - -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penality. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols - -SYMBOL_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = NO - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by -# doxygen. The layout file controls the global structure of the generated output files -# in an output format independent way. The create the layout file that represents -# doxygen's defaults, run doxygen with the -l option. You can optionally specify a -# file name after the option, if omitted DoxygenLayout.xml will be used as the name -# of the layout file. - -LAYOUT_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = YES - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = @CMAKE_DOXYGEN_INPUT_LIST@ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 - -FILE_PATTERNS = *.cpp \ - *.h \ - *.dxgen - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = Private - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = @CMAKE_DOXYGEN_IMAGE_PATH@ - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = NO - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = . - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. - -HTML_TIMESTAMP = YES - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). - -HTML_DYNAMIC_SECTIONS = NO - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. - -GENERATE_DOCSET = NO - -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. - -CHM_INDEX_ENCODING = - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER -# are set, an additional index file will be generated that can be used as input for -# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated -# HTML documentation. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders - -QHP_VIRTUAL_FOLDER = doc - -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. -# For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see -# Qt Help Project / Custom Filters. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's -# filter section matches. -# Qt Help Project / Filter Attributes. - -QHP_SECT_FILTER_ATTRS = - -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before the help appears. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 8 - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. - -GENERATE_TREEVIEW = NO - -# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list. - -USE_INLINE_TREES = NO - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. - -FORMULA_FONTSIZE = 10 - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. - -SEARCHENGINE = NO - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be implemented using a PHP enabled web server instead of at the web client using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server based approach is that it scales better to large projects and allows full text search. The disadvances is that it is more difficult to setup -# and does not have live searching capabilities. - -SERVER_BASED_SEARCH = NO - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4wide - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = NO - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = NO - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. - -LATEX_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = NO - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = @DOXYFILE_DOT@ - -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. - -DOT_FONTNAME = FreeSans - -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = YES - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = "@DOXYGEN_DOT_PATH@" - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES diff --git a/library/doc/Doxygen.html b/library/doc/Doxygen.html deleted file mode 100644 index da9a6fb65..000000000 --- a/library/doc/Doxygen.html +++ /dev/null @@ -1,9 +0,0 @@ - - - -REDIRECT! - - -This is a redirect to the doxygen stuff. - - diff --git a/library/doc/index.dxgen b/library/doc/index.dxgen deleted file mode 100644 index bfb6b2312..000000000 --- a/library/doc/index.dxgen +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -/*! \page index -

-\htmlonly -

DFHack

- -\endhtmlonly -
- -

Introduction

-DFHack is a Dwarf Fortress memory access library and a set of basic tools using -this library. The library is a work in progress, so things might change as more -tools are written for it. - -It is an attempt to unite the various ways tools access DF memory and allow for -easier development of new tools. In general, you can use it to move memory -objects in and out of Dwarf Fortress really fast, regardless of DF version or OS. -*/ - diff --git a/library/git-describe.cmake b/library/git-describe.cmake index 9a22617ad..f19b556d2 100644 --- a/library/git-describe.cmake +++ b/library/git-describe.cmake @@ -1,10 +1,24 @@ -execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long +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}) AND + NOT(${dfhack_SOURCE_DIR}/library/git-describe.cmake 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 --exact-match +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) @@ -17,9 +31,6 @@ execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY "${dfhack_SOURCE_DIR}/library/xml" OUTPUT_VARIABLE DFHACK_GIT_XML_COMMIT) -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) - file(WRITE ${git_describe_tmp_h} "") macro(git_describe_definition var) diff --git a/library/include/Core.h b/library/include/Core.h index f76ba2b73..fa65645a1 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -109,13 +109,14 @@ namespace DFHack friend void ::DFH_SDL_Quit(void); friend int ::DFH_SDL_PollEvent(SDL::Event *); friend int ::DFH_SDL_Init(uint32_t flags); + friend int ::DFH_wgetch(WINDOW * w); #else friend int ::SDL_NumJoysticks(void); friend void ::SDL_Quit(void); friend int ::SDL_PollEvent(SDL::Event *); friend int ::SDL_Init(uint32_t flags); -#endif friend int ::wgetch(WINDOW * w); +#endif friend int ::egg_init(void); friend int ::egg_shutdown(void); friend int ::egg_tick(void); @@ -169,6 +170,14 @@ namespace DFHack std::vector ListKeyBindings(std::string keyspec); int8_t getModstate() { return modstate; } + bool AddAlias(const std::string &name, const std::vector &command, bool replace = false); + bool RemoveAlias(const std::string &name); + bool IsAlias(const std::string &name); + bool RunAlias(color_ostream &out, const std::string &name, + const std::vector ¶meters, command_result &result); + std::map> ListAliases(); + std::string GetAliasCommand(const std::string &name, const std::string &default_ = ""); + std::string getHackPath(); bool isWorldLoaded() { return (last_world_data_ptr != NULL); } @@ -255,6 +264,9 @@ namespace DFHack tthread::mutex * HotkeyMutex; tthread::condition_variable * HotkeyCond; + std::map> aliases; + tthread::recursive_mutex * alias_mutex; + bool SelectHotkey(int key, int modifiers); // for state change tracking diff --git a/library/include/DFHackVersion.h b/library/include/DFHackVersion.h index afe8a03a0..c89b94ba5 100644 --- a/library/include/DFHackVersion.h +++ b/library/include/DFHackVersion.h @@ -4,6 +4,7 @@ namespace DFHack { const char *dfhack_version(); const char *df_version(); const char *dfhack_release(); + int dfhack_abi_version(); const char *git_description(); const char *git_commit(); const char *git_xml_commit(); @@ -18,6 +19,7 @@ namespace DFHack { #define DF_VERSION (DFHack::Version::df_version()) #define DFHACK_RELEASE (DFHack::Version::dfhack_release()) #define DFHACK_VERSION (DFHack::Version::dfhack_version()) + #define DFHACK_ABI_VERSION (DFHack::Version::dfhack_abi_version()) #define DFHACK_GIT_DESCRIPTION (DFHack::Version::git_description()) #define DFHACK_GIT_COMMIT (DFHack::Version::git_commit()) #define DFHACK_GIT_XML_COMMIT (DFHack::Version::git_xml_commit()) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index bccc9b3f5..31a949df1 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -24,11 +24,13 @@ distribution. #pragma once -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include "Core.h" #include "BitArray.h" @@ -188,9 +190,22 @@ namespace DFHack class struct_identity; class DFHACK_EXPORT enum_identity : public compound_identity { + public: + struct ComplexData { + std::map value_index_map; + std::vector index_value_map; + ComplexData(std::initializer_list values); + size_t size() const { + return index_value_map.size(); + } + }; + + private: const char *const *keys; + const ComplexData *complex; int64_t first_item_value; int64_t last_item_value; + int count; type_identity *base_type; @@ -209,14 +224,16 @@ namespace DFHack type_identity *base_type, int64_t first_item_value, int64_t last_item_value, const char *const *keys, + const ComplexData *complex, const void *attrs, struct_identity *attr_type); virtual identity_type type() { return IDTYPE_ENUM; } int64_t getFirstItem() { return first_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 ComplexData *getComplex() { return complex; } type_identity *getBaseType() { return base_type; } const void *getAttrs() { return attrs; } @@ -398,7 +415,7 @@ int linear_index(const DFHack::enum_list_attr &lst, T val) { inline int linear_index(const DFHack::enum_list_attr &lst, const std::string &val) { for (size_t i = 0; i < lst.size; i++) if (lst.items[i] == val) - return i; + return (int)i; return -1; } @@ -423,12 +440,15 @@ namespace df using DFHack::BitArray; using DFHack::DfArray; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" template void *allocator_fn(void *out, const void *in) { if (out) { *(T*)out = *(const T*)in; return out; } else if (in) { delete (T*)in; return (T*)in; } else return new T(); } +#pragma GCC diagnostic pop template void *allocator_nodel_fn(void *out, const void *in) { @@ -496,33 +516,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 - inline typename df::enum_traits::enum_type next_enum_item(T v) { + inline typename std::enable_if< + !df::enum_traits::is_complex, + typename df::enum_traits::enum_type + >::type next_enum_item(T v, bool wrap = true) + { typedef df::enum_traits traits; typedef typename traits::base_type base_type; 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 + inline typename std::enable_if< + df::enum_traits::is_complex, + typename df::enum_traits::enum_type + >::type next_enum_item(T v, bool wrap = true) + { + typedef df::enum_traits 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. */ template - inline bool is_valid_enum_item(T v) { + inline typename std::enable_if< + !df::enum_traits::is_complex, + bool + >::type is_valid_enum_item(T v) + { return df::enum_traits::is_valid(v); } + template + inline typename std::enable_if< + df::enum_traits::is_complex, + bool + >::type is_valid_enum_item(T v) + { + const auto &complex = df::enum_traits::complex; + return complex.value_index_map.find(v) != complex.value_index_map.end(); + } + /** * Return the enum item key string pointer, or NULL if none. */ template - inline const char *enum_item_raw_key(T val) { + inline typename std::enable_if< + !df::enum_traits::is_complex, + const char * + >::type enum_item_raw_key(T val) { typedef df::enum_traits traits; return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL; } + template + inline typename std::enable_if< + df::enum_traits::is_complex, + const char * + >::type enum_item_raw_key(T val) { + typedef df::enum_traits 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. */ @@ -700,7 +787,7 @@ namespace DFHack { #define ENUM_NEXT_ITEM(enum,val) \ (DFHack::next_enum_item(val)) #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. diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index cd178263c..6541ae900 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -83,6 +83,11 @@ namespace df { typedef RT type; \ typedef CT class_type; \ static const bool is_method = true; \ + }; \ + template struct return_type { \ + typedef RT type; \ + typedef CT class_type; \ + static const bool is_method = true; \ }; #define INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \ @@ -99,10 +104,20 @@ namespace df { static void execute(lua_State *state, int base, void (CT::*cb) FArgs) { \ LOAD_CLASS() Loads; INVOKE_VOID((self->*cb) Args); } \ }; \ + template struct function_wrapper { \ + static const int num_args = Count+1; \ + static void execute(lua_State *state, int base, void (CT::*cb) FArgs const) { \ + LOAD_CLASS() Loads; INVOKE_VOID((self->*cb) Args); } \ + }; \ template struct function_wrapper { \ static const int num_args = Count+1; \ static void execute(lua_State *state, int base, RT (CT::*cb) FArgs) { \ LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ + }; \ + template struct function_wrapper { \ + static const int num_args = Count+1; \ + static void execute(lua_State *state, int base, RT (CT::*cb) FArgs const) { \ + LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ }; #define INSTANTIATE_WRAPPERS(Count, FArgs, OFArgs, Args, OArgs, Loads) \ @@ -206,6 +221,29 @@ INSTANTIATE_WRAPPERS(11, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11), LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11);) #undef FW_TARGS +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11, class A12 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12)) +INSTANTIATE_WRAPPERS(12, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8); + LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11); LOAD_ARG(A12);) +#undef FW_TARGS + +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11, class A12, class A13 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13)) +INSTANTIATE_WRAPPERS(13, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12,vA13), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12,vA13), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8); + LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11); LOAD_ARG(A12); + LOAD_ARG(A13);) +#undef FW_TARGS + #undef FW_TARGSC #undef INSTANTIATE_WRAPPERS #undef INSTANTIATE_WRAPPERS2 diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index e6dfc6a18..62a9ff274 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -132,7 +132,7 @@ namespace DFHack virtual bool erase(void *ptr, int index) { return false; } virtual bool insert(void *ptr, int index, void *pitem) { return false; } - virtual bool lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + virtual bool lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); protected: virtual int item_count(void *ptr, CountMode cnt) = 0; @@ -153,7 +153,7 @@ namespace DFHack virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); - virtual bool lua_insert(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + virtual bool lua_insert2(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); }; class DFHACK_EXPORT bit_container_identity : public container_identity { @@ -196,6 +196,29 @@ namespace df std::string getFullName() { return name; } + virtual void lua_read(lua_State *state, int fname_idx, void *ptr) = 0; + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) = 0; + + }; + + class DFHACK_EXPORT integer_identity_base : public number_identity_base { + public: + integer_identity_base(size_t size, const char *name) + : number_identity_base(size, name) {} + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + + protected: + virtual int64_t read(void *ptr) = 0; + virtual void write(void *ptr, int64_t val) = 0; + }; + + class DFHACK_EXPORT float_identity_base : public number_identity_base { + public: + float_identity_base(size_t size, const char *name) + : number_identity_base(size, name) {} + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); @@ -205,9 +228,20 @@ namespace df }; template - class number_identity : public number_identity_base { + class integer_identity : public integer_identity_base { + public: + integer_identity(const char *name) : integer_identity_base(sizeof(T), name) {} + + protected: + virtual int64_t read(void *ptr) { return int64_t(*(T*)ptr); } + virtual void write(void *ptr, int64_t val) { *(T*)ptr = T(val); } + }; + + template + class float_identity : public float_identity_base { public: - number_identity(const char *name) : number_identity_base(sizeof(T), name) {} + float_identity(const char *name) : float_identity_base(sizeof(T), name) {} + protected: virtual double read(void *ptr) { return double(*(T*)ptr); } virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } @@ -285,7 +319,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode) { - return ((container*)ptr)->size(); + return (int)((container*)ptr)->size(); }; virtual void *item_pointer(type_identity *, void *ptr, int idx) { return &(*(container*)ptr)[idx]; @@ -351,7 +385,7 @@ namespace df } protected: - virtual int item_count(void *ptr, CountMode) { return ((T*)ptr)->size(); } + virtual int item_count(void *ptr, CountMode) { return (int)((T*)ptr)->size(); } virtual void *item_pointer(type_identity *item, void *ptr, int idx) { return &(*(T*)ptr)[idx]; } @@ -376,7 +410,7 @@ namespace df virtual bool insert(void *ptr, int idx, void *item) { return false; } protected: - virtual int item_count(void *ptr, CountMode) { return ((T*)ptr)->size(); } + virtual int item_count(void *ptr, CountMode) { return (int)((T*)ptr)->size(); } virtual void *item_pointer(type_identity *item, void *ptr, int idx) { auto iter = (*(T*)ptr).begin(); for (; idx > 0; idx--) ++iter; @@ -438,7 +472,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode) { - return ((container*)ptr)->size(); + return (int)((container*)ptr)->size(); } virtual bool get_item(void *ptr, int idx) { return (*(container*)ptr)[idx]; @@ -464,7 +498,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode cm) { - return cm == COUNT_WRITE ? 0 : ((container*)ptr)->size; + return cm == COUNT_WRITE ? 0 : (int)((container*)ptr)->size; } virtual void *item_pointer(type_identity *item, void *ptr, int idx) { return (void*)&((container*)ptr)->items[idx]; @@ -472,23 +506,28 @@ namespace df }; #endif -#define NUMBER_IDENTITY_TRAITS(type) \ +#define NUMBER_IDENTITY_TRAITS(category, type) \ template<> struct DFHACK_EXPORT identity_traits { \ - static number_identity identity; \ - static number_identity_base *get() { return &identity; } \ - }; - - NUMBER_IDENTITY_TRAITS(char); - NUMBER_IDENTITY_TRAITS(int8_t); - NUMBER_IDENTITY_TRAITS(uint8_t); - NUMBER_IDENTITY_TRAITS(int16_t); - NUMBER_IDENTITY_TRAITS(uint16_t); - NUMBER_IDENTITY_TRAITS(int32_t); - NUMBER_IDENTITY_TRAITS(uint32_t); - NUMBER_IDENTITY_TRAITS(int64_t); - NUMBER_IDENTITY_TRAITS(uint64_t); - NUMBER_IDENTITY_TRAITS(float); - NUMBER_IDENTITY_TRAITS(double); + static category##_identity identity; \ + static category##_identity_base *get() { return &identity; } \ + }; + +#define INTEGER_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(integer, type) +#define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type) + + INTEGER_IDENTITY_TRAITS(char); + INTEGER_IDENTITY_TRAITS(signed char); + INTEGER_IDENTITY_TRAITS(unsigned char); + INTEGER_IDENTITY_TRAITS(short); + INTEGER_IDENTITY_TRAITS(unsigned short); + INTEGER_IDENTITY_TRAITS(int); + INTEGER_IDENTITY_TRAITS(unsigned int); + INTEGER_IDENTITY_TRAITS(long); + INTEGER_IDENTITY_TRAITS(unsigned long); + INTEGER_IDENTITY_TRAITS(long long); + INTEGER_IDENTITY_TRAITS(unsigned long long); + FLOAT_IDENTITY_TRAITS(float); + FLOAT_IDENTITY_TRAITS(double); template<> struct DFHACK_EXPORT identity_traits { static bool_identity identity; @@ -531,6 +570,8 @@ namespace df }; #undef NUMBER_IDENTITY_TRAITS +#undef INTEGER_IDENTITY_TRAITS +#undef FLOAT_IDENTITY_TRAITS // Container declarations diff --git a/library/include/Error.h b/library/include/Error.h index 4e3ff269c..29564d69d 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -24,11 +24,13 @@ distribution. #pragma once +#include +#include +#include + #include "Export.h" +#include "MiscUtils.h" #include "Pragma.h" -#include -#include -#include namespace DFHack { @@ -38,100 +40,94 @@ namespace DFHack * our wrapper for the C++ exception. used to differentiate * the whole array of DFHack exceptions from the rest */ - class DFHACK_EXPORT All : public std::exception{}; - +#ifdef _MSC_VER +#pragma push +/** + * C4275 - The warning officially is non dll-interface class 'std::exception' used as base for + * dll-interface class + * + * 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 + */ +#pragma warning(disable: 4275) +#endif + 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 +#pragma pop +#endif class DFHACK_EXPORT NullPointer : public All { - const char *varname_; public: - NullPointer(const char *varname_ = NULL) : varname_(varname_) {} - const char *varname() const { return varname_; } - virtual const char *what() const throw(); + const char *const varname; + NullPointer(const char *varname = NULL, const char *func = NULL); }; #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 { - const char *expr_; public: - InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {} - const char *expr() const { return expr_; } - virtual const char *what() const throw(); + const char *const expr; + InvalidArgument(const char *expr = NULL, const char *func = NULL); }; #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 { + public: + const char *const name; + VTableMissing(const char *name = NULL); + }; - 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 class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols { public: - 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; + SymbolsXmlParse(const char* desc, int id, int row, int col); const std::string desc; const int id; const int row; 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: - SymbolsXmlBadAttribute(const char* _attr) : attr(_attr) - { - std::stringstream s; - s << "attribute is either missing or invalid: " << attr; - full = s.str(); - } - std::string full; + SymbolsXmlBadAttribute(const char* 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: - SymbolsXmlNoRoot() {} - virtual ~SymbolsXmlNoRoot() throw(){}; - virtual const char* what() const throw() - { - return "Symbol file is missing root element."; - } + SymbolsXmlNoRoot(); }; - class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public All + class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public AllSymbols { public: - SymbolsXmlUnderspecifiedEntry(const char * _where) : where(_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(){}; + SymbolsXmlUnderspecifiedEntry(const char *where); std::string where; - std::string full; - virtual const char* what() const throw() - { - return full.c_str(); - } }; } } diff --git a/library/include/Hooks.h b/library/include/Hooks.h index f29290cc3..97caecfa5 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -52,6 +52,7 @@ DFhackCExport int DFH_SDL_NumJoysticks(void); DFhackCExport void DFH_SDL_Quit(void); DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event); DFhackCExport int DFH_SDL_Init(uint32_t flags); +DFhackCExport int DFH_wgetch(WINDOW * win); #endif DFhackCExport int SDL_NumJoysticks(void); DFhackCExport void SDL_Quit(void); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index a36459d68..e1d828271 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -28,6 +28,7 @@ distribution. #include #include #include +#include #include "DataDefs.h" @@ -182,11 +183,9 @@ namespace DFHack {namespace Lua { } // Internal helper - template - int TailPCallK_Thunk(lua_State *state) { - int tmp; - int rv = lua_getctx(state, &tmp); - return cb(state, rv, tmp); + template + int TailPCallK_Thunk(lua_State *state, int rv, lua_KContext ctx) { + return cb(state, rv, ctx); } /** @@ -194,9 +193,9 @@ namespace DFHack {namespace Lua { * specifically, the callback is called with the same kind of arguments * in both yield and non-yield case. */ - template + template int TailPCallK(lua_State *state, int narg, int nret, int errfun, int ctx) { - int rv = lua_pcallk(state, narg, nret, errfun, ctx, &TailPCallK_Thunk); + int rv = lua_pcallk(state, narg, nret, errfun, ctx, cb); return cb(state, rv, ctx); } @@ -287,7 +286,14 @@ namespace DFHack {namespace Lua { NUMBER_PUSH(float) NUMBER_PUSH(double) #undef NUMBER_PUSH #else - template inline void Push(lua_State *state, T value) { + template + inline typename std::enable_if::value || std::is_enum::value>::type + Push(lua_State *state, T value) { + lua_pushinteger(state, value); + } + template + inline typename std::enable_if::value>::type + Push(lua_State *state, T value) { lua_pushnumber(state, lua_Number(value)); } #endif @@ -335,6 +341,14 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); + template + inline void TableInsert(lua_State *state, T_Key key, T_Value value) + { + Lua::Push(state, key); + Lua::Push(state, value); + lua_settable(state, -3); + } + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); DFHACK_EXPORT bool IsCoreContext(lua_State *state); diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 36100b400..3022688f9 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -207,7 +207,7 @@ namespace DFHack * attempt to copy a string from source address to target address. may truncate or leak, depending on platform * @return length copied */ - size_t copySTLString(const void * address, const uint32_t target) + size_t copySTLString(const void * address, const uintptr_t target) { std::string * strsrc = (std::string *) address; std::string * str = (std::string *) target; diff --git a/library/include/Memory.h b/library/include/Memory.h new file mode 100644 index 000000000..0f465c834 --- /dev/null +++ b/library/include/Memory.h @@ -0,0 +1,22 @@ +// Default base address +#if defined(_WIN32) + #ifdef DFHACK64 + #define DEFAULT_BASE_ADDR 0x140000000 + #else + #define DEFAULT_BASE_ADDR 0x400000 + #endif +#elif defined(_DARWIN) + #ifdef DFHACK64 + #define DEFAULT_BASE_ADDR 0x100000000 + #else + #define DEFAULT_BASE_ADDR 0x1000 + #endif +#elif defined(_LINUX) + #ifdef DFHACK64 + #define DEFAULT_BASE_ADDR 0x400000 + #else + #define DEFAULT_BASE_ADDR 0x8048000 + #endif +#else + #error Unknown OS +#endif diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 653e37f47..378b7a728 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -36,6 +36,14 @@ using std::ostream; using std::stringstream; 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 + template void print_bits ( T val, ostream& out ) { @@ -321,6 +329,10 @@ DFHACK_EXPORT std::string join_strings(const std::string &separator, const std:: DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str); +DFHACK_EXPORT bool word_wrap(std::vector *out, + const std::string &str, + size_t line_length = 80); + inline bool bits_match(unsigned required, unsigned ok, unsigned mask) { return (required & mask) == (required & mask & ok); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 42bebb2d3..c8776d8ed 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -61,6 +61,7 @@ namespace DFHack namespace Version { const char *dfhack_version(); const char *git_description(); + int dfhack_abi_version(); } // anon type, pretty much @@ -296,7 +297,8 @@ namespace DFHack DFhackDataExport const char * plugin_name = m_plugin_name;\ DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\ 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 _plugin_globals;\ DFhackDataExport std::vector* plugin_globals = &_plugin_globals; \ DFhackDataExport bool plugin_dev = is_dev; diff --git a/library/include/Pragma.h b/library/include/Pragma.h index 8f82b8d3c..00472e732 100644 --- a/library/include/Pragma.h +++ b/library/include/Pragma.h @@ -58,6 +58,8 @@ distribution. #pragma warning( disable: 4482) // nonstandard extension used: 'extern' before template explicit instantiation #pragma warning( disable: 4231) + // ignore warnings about putting a vector index into an int + #pragma warning( disable: 4267) #endif #endif diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 1e8264cec..e1a2c512a 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -66,7 +66,7 @@ namespace DFHack }; struct RPCMessageHeader { - static const int MAX_MESSAGE_SIZE = 8*1048576; + static const int MAX_MESSAGE_SIZE = 64*1048576; int16_t id; int32_t size; diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index df2f42712..514f91250 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -46,7 +46,10 @@ namespace DFHack SF_CALLED_ONCE = 1, // Don't automatically suspend the core around the call. // 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 { diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index cf7dd60c0..dc959f1db 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -25,13 +25,13 @@ distribution. #pragma once -#include "Pragma.h" -#include "Export.h" -/* #include "Types.h" */ +#include #include #include #include -#include + +#include "Export.h" +#include "Pragma.h" namespace DFHack { @@ -49,11 +49,11 @@ namespace DFHack { private: std::vector md5_list; - std::vector PE_list; - std::map Addresses; - std::map VTables; - uint32_t base; - int rebase_delta; + std::vector PE_list; + std::map Addresses; + std::map VTables; + uintptr_t base; + intptr_t rebase_delta; std::string version; OSType OS; public: @@ -75,10 +75,10 @@ namespace DFHack OS = rhs.OS; }; - uint32_t getBase () const { return base; }; - int getRebaseDelta() const { return rebase_delta; } - void setBase (const uint32_t _base) { base = _base; }; - void rebaseTo(const uint32_t new_base) + uintptr_t getBase () const { return base; }; + intptr_t getRebaseDelta() const { return rebase_delta; } + void setBase (const uintptr_t _base) { base = _base; }; + void rebaseTo(const uintptr_t new_base) { int64_t old = base; int64_t newx = new_base; @@ -100,11 +100,11 @@ namespace DFHack return std::find(md5_list.begin(), md5_list.end(), _md5) != md5_list.end(); }; - void addPE (uint32_t PE_) + void addPE (uintptr_t PE_) { PE_list.push_back(PE_); }; - bool hasPE (uint32_t PE_) const + bool hasPE (uintptr_t PE_) const { return std::find(PE_list.begin(), PE_list.end(), PE_) != PE_list.end(); }; @@ -115,7 +115,7 @@ namespace DFHack }; std::string getVersion() const { return version; }; - void setAddress (const std::string& key, const uint32_t value) + void setAddress (const std::string& key, const uintptr_t value) { Addresses[key] = value; }; @@ -129,7 +129,7 @@ namespace DFHack return true; }; - uint32_t getAddress (const std::string& key) const + uintptr_t getAddress (const std::string& key) const { auto i = Addresses.find(key); if(i == Addresses.end()) @@ -137,7 +137,7 @@ namespace DFHack return (*i).second; } - void setVTable (const std::string& key, const uint32_t value) + void setVTable (const std::string& key, const uintptr_t value) { VTables[key] = value; }; diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index f69f37fee..8a2cabdc3 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -40,7 +40,7 @@ namespace DFHack bool loadFile( std::string path_to_xml); bool isInErrorState() const {return error;}; VersionInfo * getVersionInfoByMD5(std::string md5string); - VersionInfo * getVersionInfoByPETimestamp(uint32_t timestamp); + VersionInfo * getVersionInfoByPETimestamp(uintptr_t timestamp); std::vector versions; // trash existing list void clear(); diff --git a/library/include/df/custom/coord2d_path.methods.inc b/library/include/df/custom/coord2d_path.methods.inc index c36c5c0fa..623dfe758 100644 --- a/library/include/df/custom/coord2d_path.methods.inc +++ b/library/include/df/custom/coord2d_path.methods.inc @@ -1,13 +1,13 @@ -unsigned size() const { return x.size(); } +size_t size() const { return x.size(); } -coord2d operator[] (unsigned idx) const { +coord2d operator[] (size_t idx) const { if (idx >= x.size()) return coord2d(); else return coord2d(x[idx], y[idx]); } -void erase(unsigned idx) { +void erase(size_t idx) { if (idx < x.size()) { x.erase(x.begin()+idx); y.erase(y.begin()+idx); diff --git a/library/include/df/custom/coord_path.methods.inc b/library/include/df/custom/coord_path.methods.inc index 5421796e3..cc9a67fa1 100644 --- a/library/include/df/custom/coord_path.methods.inc +++ b/library/include/df/custom/coord_path.methods.inc @@ -1,5 +1,5 @@ bool empty() const { return x.empty(); } -unsigned size() const { return x.size(); } +size_t size() const { return x.size(); } void clear() { x.clear(); @@ -7,14 +7,14 @@ void clear() { z.clear(); } -coord operator[] (unsigned idx) const { +coord operator[] (size_t idx) const { if (idx >= x.size()) return coord(); else return coord(x[idx], y[idx], z[idx]); } -void erase(unsigned idx) { +void erase(size_t idx) { if (idx < x.size()) { x.erase(x.begin()+idx); y.erase(y.begin()+idx); diff --git a/library/include/df/custom/creature_handler.methods.inc b/library/include/df/custom/creature_handler.methods.inc new file mode 100644 index 000000000..811128d05 --- /dev/null +++ b/library/include/df/custom/creature_handler.methods.inc @@ -0,0 +1 @@ +friend struct world_raws; diff --git a/library/include/df/custom/job_handler.methods.inc b/library/include/df/custom/job_handler.methods.inc new file mode 100644 index 000000000..c160f5470 --- /dev/null +++ b/library/include/df/custom/job_handler.methods.inc @@ -0,0 +1 @@ +friend struct df::world; diff --git a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc new file mode 100644 index 000000000..c1fe1a564 --- /dev/null +++ b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc @@ -0,0 +1,13 @@ +df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() const +{ + int32_t value = category * 32; + for (int32_t i = 0; i < 32; i++) + { + if (flags.whole & (1 << i)) + { + value += i; + break; + } + } + return df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag(value); +} diff --git a/library/include/df/custom/viewscreen.methods.inc b/library/include/df/custom/viewscreen.methods.inc index c5d277716..b1f07242e 100644 --- a/library/include/df/custom/viewscreen.methods.inc +++ b/library/include/df/custom/viewscreen.methods.inc @@ -1 +1,6 @@ friend struct df::interfacest; +void feed_key(df::interface_key key) { + std::set input; + input.insert(key); + feed(&input); +} diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 1d939665f..4032b96af 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -26,26 +26,29 @@ distribution. #include "Export.h" #include "DataDefs.h" #include "Types.h" +#include "modules/Items.h" +#include "modules/Maps.h" + #include "df/building.h" #include "df/building_stockpilest.h" #include "df/building_type.h" #include "df/civzone_type.h" +#include "df/construction_type.h" #include "df/furnace_type.h" #include "df/item.h" -#include "df/workshop_type.h" -#include "df/construction_type.h" #include "df/shop_type.h" #include "df/siegeengine_type.h" #include "df/trap_type.h" -#include "modules/Items.h" -#include "modules/Maps.h" +#include "df/workshop_type.h" namespace df { - struct job_item; - struct item; - struct building_extents; + struct building_cagest; struct building_civzonest; + struct building_extents; + struct item; + struct job_item; + struct unit; } namespace DFHack @@ -187,9 +190,23 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vectorx1, stockpile->y1, stockpile->z); - current = 0; - } - - while (current >= block->items.size()) { - // Out of items in this block; find the next block to search. - if (block->map_pos.x + 16 < stockpile->x2) { - block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z); - current = 0; - } else if (block->map_pos.y + 16 < stockpile->y2) { - block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z); - current = 0; - } else { - // All items in all blocks have been checked. - block = NULL; - item = NULL; - return *this; - } - } - - // If the current item isn't properly stored, move on to the next. - item = df::item::find(block->items[current]); - if (!item->flags.bits.on_ground) { - continue; - } - - if (!Buildings::containsTile(stockpile, item->pos, false)) { - continue; - } - - // Ignore empty bins, barrels, and wheelbarrows assigned here. - if (item->isAssignedToThisStockpile(stockpile->id)) { - auto ref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_ITEM); - if (!ref) continue; - } - - // Found a valid item; yield it. - break; - } - - return *this; - } + StockpileIterator& operator++(); void begin(df::building_stockpilest* sp) { stockpile = sp; @@ -292,7 +261,14 @@ DFHACK_EXPORT bool isActivityZone(df::building * building); DFHACK_EXPORT bool isPenPasture(df::building * building); DFHACK_EXPORT bool isPitPond(df::building * building); DFHACK_EXPORT bool isActive(df::building * building); +DFHACK_EXPORT bool isHospital(df::building * building); +DFHACK_EXPORT bool isAnimalTraining(df::building * building); DFHACK_EXPORT df::building* findPenPitAt(df::coord coord); + +/** + * Returns the units currently in the given cage + */ +DFHACK_EXPORT bool getCageOccupants(df::building_cagest *cage, std::vector &units); } } diff --git a/library/include/modules/Designations.h b/library/include/modules/Designations.h new file mode 100644 index 000000000..914605e58 --- /dev/null +++ b/library/include/modules/Designations.h @@ -0,0 +1,21 @@ +#pragma once + +namespace df { + struct plant; +} + +namespace DFHack { + namespace Designations { + // Mark or un-mark a plant (e.g. fell trees, gather plants) + // Return value indicates whether the plant's designation was changed or not + // (This can be false if markPlant() is called on an already-designated plant, for example) + DFHACK_EXPORT bool markPlant(const df::plant *plant); + DFHACK_EXPORT bool unmarkPlant(const df::plant *plant); + DFHACK_EXPORT bool canMarkPlant(const df::plant *plant); + DFHACK_EXPORT bool canUnmarkPlant(const df::plant *plant); + DFHACK_EXPORT bool isPlantMarked(const df::plant *plant); + + // Return the tile that should be designated for this plant + DFHACK_EXPORT df::coord getPlantDesignationTile(const df::plant *plant); + } +} diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 6e4be47be..d22fa2310 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -116,7 +116,7 @@ namespace std { std::size_t operator()(const DFHack::EventManager::EventHandler& h) const { size_t r = 17; const size_t m = 65537; - r = m*(r+(int32_t)h.eventHandler); + r = m*(r+(intptr_t)h.eventHandler); r = m*(r+h.freq); return r; } diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index ae53e7603..819ad1558 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -45,6 +45,7 @@ namespace df { struct job; struct unit; struct item; + struct plant; }; /** @@ -104,6 +105,11 @@ namespace DFHack DFHACK_EXPORT df::building *getAnyBuilding(df::viewscreen *top); DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false); + // A plant is selected, e.g. via 'k' + DFHACK_EXPORT bool any_plant_hotkey(df::viewscreen *top); + DFHACK_EXPORT df::plant *getAnyPlant(df::viewscreen *top); + DFHACK_EXPORT df::plant *getSelectedPlant(color_ostream &out, bool quiet = false); + // Low-level API that gives full control over announcements and reports DFHACK_EXPORT void writeToGamelog(std::string message); @@ -134,7 +140,7 @@ namespace DFHack int map_y1, map_y2; bool menu_on, area_on, menu_forced; - rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); } + rect2d map() { return mkrect_xy(map_x1, map_y1, map_x2, map_y2); } rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); } }; @@ -142,6 +148,9 @@ namespace DFHack DFHACK_EXPORT void resetDwarfmodeView(bool pause = false); DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false); + DFHACK_EXPORT bool refreshSidebar(); + + DFHACK_EXPORT bool inRenameBuilding(); 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); diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index d77b5b369..776e935eb 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -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 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); + } } diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index fbbed9ff3..814f1e062 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -66,6 +66,21 @@ namespace DFHack DFHACK_EXPORT void setJobCooldown(df::building *workshop, df::unit *worker, int cooldown = 100); DFHACK_EXPORT bool removeWorker(df::job *job, int cooldown = 100); + // This helpful method only removes the backref from the item to the job, but it doesn't + // remove the item ref from the job's vector, or delete it or anything. Think of it as a method + // that does all the needful to make an item ref ready to delete. + DFHACK_EXPORT void disconnectJobItem(df::job *job, df::job_item_ref *item); + // This helpful method only removes the backref from whatever the general_ref points to, + // it doesn't remove the general_ref from the job's vector, or delete it or anything. + // Think of it as a method that does all the needful to make a ref ready to delete. + // If it returns false, you've found a ref that the method doesn't know how to handle. Congratulations! + // You should report that and/or check in a fix. + DFHACK_EXPORT bool disconnectJobGeneralRef(df::job *job, df::general_ref *ref); + // Delete a job & remove all refs from everywhere. + // This method DELETES the job object! Everything related to it will be wiped + // clean from the earth, so make sure you pull what you need out before calling this! + DFHACK_EXPORT bool removeJob(df::job *job); + // Instruct the game to check and assign workers DFHACK_EXPORT void checkBuildingsNow(); DFHACK_EXPORT void checkDesignationsNow(); diff --git a/library/include/modules/kitchen.h b/library/include/modules/Kitchen.h similarity index 77% rename from library/include/modules/kitchen.h rename to library/include/modules/Kitchen.h index ca88e9163..1ddf239bd 100644 --- a/library/include/modules/kitchen.h +++ b/library/include/modules/Kitchen.h @@ -32,6 +32,7 @@ distribution. #include "VersionInfo.h" #include "Core.h" #include "modules/Items.h" +#include "df/kitchen_exc_type.h" /** * \defgroup grp_kitchen Kitchen settings @@ -42,14 +43,11 @@ namespace DFHack { namespace Kitchen { -typedef uint8_t t_exclusionType; - 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_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 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. @@ -79,5 +77,21 @@ DFHACK_EXPORT void setLimit(t_materialIndex materialIndex, unsigned int limit); DFHACK_EXPORT void clearLimits(); 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); + } } diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index b1d411ab8..36625719b 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -38,8 +38,6 @@ distribution. #include "df/item.h" #include "df/inclusion_type.h" -using namespace DFHack; - namespace df { struct world_region_details; } @@ -47,6 +45,8 @@ namespace df { namespace MapExtras { +using namespace DFHack; + class DFHACK_EXPORT MapCache; class Block; @@ -276,6 +276,9 @@ public: return true; } + int32_t priorityAt(df::coord2d p); + bool setPriorityAt(df::coord2d p, int32_t priority); + df::tile_occupancy OccupancyAt(df::coord2d p) { return index_tile(occupancy,p); @@ -542,19 +545,37 @@ class DFHACK_EXPORT MapCache df::tile_designation designationAt (DFCoord tilecoord) { Block * b= BlockAtTile(tilecoord); - return b ? b->DesignationAt(tilecoord) : df::tile_designation(0); + 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 = -1) { - if(Block * b= BlockAtTile(tilecoord)) - return b->setDesignationAt(tilecoord, des); + if (Block *b = BlockAtTile(tilecoord)) + { + if (!b->setDesignationAt(tilecoord, des)) + return false; + if (priority >= 0 && b->setPriorityAt(tilecoord, priority)) + 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) { Block * b= BlockAtTile(tilecoord); - return b ? b->OccupancyAt(tilecoord) : df::tile_occupancy(0); + return b ? b->OccupancyAt(tilecoord) : df::tile_occupancy(); } bool setOccupancyAt (DFCoord tilecoord, df::tile_occupancy occ) { @@ -626,4 +647,4 @@ private: std::map blocks; }; } -#endif \ No newline at end of file +#endif diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 7ce594522..6b6e62f0a 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -36,22 +36,34 @@ distribution. #include "BitArray.h" #include "modules/Materials.h" -#include "df/world.h" -#include "df/world_data.h" -#include "df/map_block.h" -#include "df/block_square_event.h" -#include "df/block_square_event_mineralst.h" -#include "df/block_square_event_frozen_liquidst.h" -#include "df/block_square_event_world_constructionst.h" -#include "df/block_square_event_material_spatterst.h" -#include "df/block_square_event_grassst.h" -#include "df/block_square_event_spoorst.h" -#include "df/block_square_event_item_spatterst.h" -#include "df/tile_liquid.h" +#include "df/block_flags.h" +#include "df/feature_type.h" +#include "df/flow_type.h" #include "df/tile_dig_designation.h" +#include "df/tile_liquid.h" #include "df/tile_traffic.h" -#include "df/feature_init.h" -#include "df/flow_type.h" +#include "df/tiletype.h" + +namespace df { + struct block_square_event; + struct block_square_event_designation_priorityst; + struct block_square_event_frozen_liquidst; + struct block_square_event_grassst; + struct block_square_event_item_spatterst; + struct block_square_event_material_spatterst; + struct block_square_event_mineralst; + struct block_square_event_spoorst; + struct block_square_event_world_constructionst; + struct feature_init; + struct map_block; + struct map_block_column; + struct region_map_entry; + struct world; + struct world_data; + struct world_geo_biome; + union tile_designation; + union tile_occupancy; +} /** * \defgroup grp_maps Maps module and its types @@ -252,6 +264,9 @@ extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); extern DFHACK_EXPORT bool isValidTilePos(int32_t x, int32_t y, int32_t z); inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, pos.z); } +extern DFHACK_EXPORT bool isTileVisible(int32_t x, int32_t y, int32_t z); +inline bool isTileVisible(df::coord pos) { return isTileVisible(pos.x, pos.y, pos.z); } + /** * Get the map block or NULL if block is not valid */ @@ -307,7 +322,8 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, std::vector* grass = 0, std::vector* constructions = 0, std::vector* spoors = 0, - std::vector* items = 0 + std::vector* items = 0, + std::vector* priorities = 0 ); /// remove a block event from the block by address diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 2c4a29062..673533f3e 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -44,6 +44,7 @@ namespace df struct item; struct unit; struct building; + struct plant; } /** @@ -188,7 +189,7 @@ namespace DFHack DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y, bool map = false); /// Retrieves one screen tile from the buffer - DFHACK_EXPORT Pen readTile(int x, int y); + DFHACK_EXPORT Pen readTile(int x, int y, bool map = false); /// Paint a string onto the screen. Ignores ch and tile of pen. DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text, bool map = false); @@ -296,7 +297,8 @@ namespace DFHack }; namespace Hooks { - GUI_HOOK_DECLARE(set_tile, void, (const Pen &pen, int x, int y, bool map)); + GUI_HOOK_DECLARE(get_tile, Pen, (int x, int y, bool map)); + GUI_HOOK_DECLARE(set_tile, bool, (const Pen &pen, int x, int y, bool map)); } } @@ -326,10 +328,11 @@ namespace DFHack virtual std::string getFocusString() = 0; virtual void onShow() {}; virtual void onDismiss() {}; - virtual df::unit *getSelectedUnit() { return NULL; } - virtual df::item *getSelectedItem() { return NULL; } - virtual df::job *getSelectedJob() { return NULL; } - virtual df::building *getSelectedBuilding() { return NULL; } + virtual df::unit *getSelectedUnit() { return nullptr; } + virtual df::item *getSelectedItem() { return nullptr; } + virtual df::job *getSelectedJob() { return nullptr; } + virtual df::building *getSelectedBuilding() { return nullptr; } + virtual df::plant *getSelectedPlant() { return nullptr; } }; class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { @@ -369,5 +372,6 @@ namespace DFHack virtual df::item *getSelectedItem(); virtual df::job *getSelectedJob(); virtual df::building *getSelectedBuilding(); + virtual df::plant *getSelectedPlant(); }; } diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 79b8ff18c..7394c5300 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -23,10 +23,9 @@ distribution. */ #pragma once -#ifndef CL_MOD_CREATURES -#define CL_MOD_CREATURES + /* - * Creatures + * Units */ #include "Export.h" #include "modules/Items.h" @@ -41,6 +40,8 @@ distribution. namespace df { + struct activity_entry; + struct activity_event; struct nemesis_record; struct burrow; struct identity; @@ -58,151 +59,24 @@ namespace DFHack { namespace Units { -/** - * \ingroup grp_units - */ -struct t_skill -{ - uint32_t id; - uint32_t rating; - uint32_t experience; -}; -/** - * \ingroup grp_units - */ -struct t_job -{ - bool active; - uint32_t jobId; - uint8_t jobType; - uint32_t occupationPtr; -}; -/** - * \ingroup grp_units - */ -struct t_like -{ - int16_t type; - int16_t itemClass; - int16_t itemIndex; - t_matglossPair material; - bool active; - uint32_t mystery; -}; -// FIXME: THIS IS VERY, VERY BAD. -#define NUM_CREATURE_LABORS 96 -#define NUM_CREATURE_TRAITS 30 -#define NUM_CREATURE_MENTAL_ATTRIBUTES 13 -#define NUM_CREATURE_PHYSICAL_ATTRIBUTES 6 -/** - * Structure for holding a copy of a DF unit's soul - * \ingroup grp_units - */ -struct t_soul -{ - uint8_t numSkills; - t_skill skills[256]; - //uint8_t numLikes; - //t_like likes[32]; - uint16_t traits[NUM_CREATURE_TRAITS]; - t_attrib analytical_ability; - t_attrib focus; - t_attrib willpower; - t_attrib creativity; - t_attrib intuition; - t_attrib patience; - t_attrib memory; - t_attrib linguistic_ability; - t_attrib spatial_sense; - t_attrib musicality; - t_attrib kinesthetic_sense; - t_attrib empathy; - t_attrib social_awareness; -}; -#define MAX_COLORS 15 -struct df_unit; -/** - * Structure for holding a limited copy of a DF unit - * \ingroup grp_units - */ -struct t_unit -{ - df::unit * origin; - uint16_t x; - uint16_t y; - uint16_t z; - uint32_t race; - int32_t civ; - - df::unit_flags1 flags1; - df::unit_flags2 flags2; - df::unit_flags3 flags3; - - t_name name; - - int16_t mood; - int16_t mood_skill; - t_name artifact_name; - - uint8_t profession; - std::string custom_profession; - - // enabled labors - uint8_t labors[NUM_CREATURE_LABORS]; - t_job current_job; - - uint32_t happiness; - uint32_t id; - t_attrib strength; - t_attrib agility; - t_attrib toughness; - t_attrib endurance; - t_attrib recuperation; - t_attrib disease_resistance; - int32_t squad_leader_id; - uint8_t sex; - uint16_t caste; - uint32_t pregnancy_timer; //Countdown timer to giving birth - //bool has_default_soul; - //t_soul defaultSoul; - uint32_t nbcolors; - uint32_t color[MAX_COLORS]; - - int32_t birth_year; - uint32_t birth_time; -}; +static const int MAX_COLORS = 15; + /** - * The Creatures module - allows reading all non-vermin creatures and their properties - * \ingroup grp_modules - * \ingroup grp_units + * The Units module - allows reading all non-vermin units and their properties */ -DFHACK_EXPORT bool isValid(); - /* Read Functions */ -// Read creatures in a box, starting with index. Returns -1 if no more creatures -// found. Call repeatedly do get all creatures in a specified box (uses tile coords) -DFHACK_EXPORT int32_t getNumCreatures(); -DFHACK_EXPORT int32_t GetCreatureInBox(const int32_t index, df::unit ** furball, - const uint16_t x1, const uint16_t y1,const uint16_t z1, - const uint16_t x2, const uint16_t y2,const uint16_t z2); -DFHACK_EXPORT df::unit * GetCreature(const int32_t index); -DFHACK_EXPORT void CopyCreature(df::unit * source, t_unit & target); - -DFHACK_EXPORT bool ReadJob(const df::unit * unit, std::vector & mat); - -DFHACK_EXPORT bool ReadInventoryByIdx(const uint32_t index, std::vector & item); -DFHACK_EXPORT bool ReadInventoryByPtr(const df::unit * unit, std::vector & item); - -DFHACK_EXPORT int32_t FindIndexById(int32_t id); +// Read units in a box, starting with index. Returns -1 if no more units +// found. Call repeatedly do get all units in a specified box (uses tile coords) +DFHACK_EXPORT int32_t getNumUnits(); +DFHACK_EXPORT df::unit *getUnit(const int32_t index); +DFHACK_EXPORT bool getUnitsInBox(std::vector &units, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2); -/* Getters */ -DFHACK_EXPORT uint32_t GetDwarfRaceIndex ( void ); -DFHACK_EXPORT int32_t GetDwarfCivId ( void ); - -DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); +DFHACK_EXPORT int32_t findIndexById(int32_t id); /// Returns the true position of the unit (non-trivial in case of caged). DFHACK_EXPORT df::coord getPosition(df::unit *unit); @@ -242,6 +116,7 @@ DFHACK_EXPORT bool isAvailableForAdoption(df::unit* unit); DFHACK_EXPORT bool isOwnCiv(df::unit* unit); DFHACK_EXPORT bool isOwnGroup(df::unit* unit); DFHACK_EXPORT bool isOwnRace(df::unit* unit); +DFHACK_EXPORT bool isVisible(df::unit* unit); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit); @@ -301,6 +176,9 @@ DFHACK_EXPORT int8_t getProfessionColor(df::unit *unit, bool ignore_noble = fals DFHACK_EXPORT int8_t getCasteProfessionColor(int race, int caste, df::profession pid); DFHACK_EXPORT std::string getSquadName(df::unit *unit); + +DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit); +DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit); + } } -#endif diff --git a/library/lua/binpatch.lua b/library/lua/binpatch.lua index d8e95b29f..0cae97e5b 100644 --- a/library/lua/binpatch.lua +++ b/library/lua/binpatch.lua @@ -17,6 +17,7 @@ local function load_patch(name) local old_bytes = {} local new_bytes = {} + local has_bytes = false for line in file:lines() do if string.match(line, '^%x+:') then @@ -34,10 +35,14 @@ local function load_patch(name) old_bytes[offset] = oldv new_bytes[offset] = newv + has_bytes = true end end 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 } end diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 900607c08..6acbe27e8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -543,23 +543,78 @@ function dfhack.run_script_with_env(envVars, name, flags, ...) end env.dfhack_flags = flags env.moduleMode = flags.module - local f + local script_code local perr local time = dfhack.filesystem.mtime(file) if time == scripts[file].mtime and scripts[file].run then - f = scripts[file].run + script_code = scripts[file].run else --reload - f, perr = loadfile(file, 't', env) - if not f then + script_code, perr = loadfile(file, 't', env) + if not script_code then error(perr) end -- avoid updating mtime if the script failed to load scripts[file].mtime = time end scripts[file].env = env - scripts[file].run = f - return f(...), env + scripts[file].run = script_code + return script_code(...), env +end + +local function 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 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(...) diff --git a/library/lua/dfhack/workshops.lua b/library/lua/dfhack/workshops.lua index 9fbe4c08a..a09849a57 100644 --- a/library/lua/dfhack/workshops.lua +++ b/library/lua/dfhack/workshops.lua @@ -126,19 +126,6 @@ jobs_workshop={ job_fields={job_type=df.job_type.CatchLiveFish} }, -- no items? }, - [df.workshop_type.Still]={ - { - name="brew drink", - items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}}, - job_fields={job_type=df.job_type.BrewDrink} - }, - { - name="extract from plants", - items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}}, - job_fields={job_type=df.job_type.ExtractFromPlants} - }, - --mead from raws? - }, [df.workshop_type.Masons]={ defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true}, { @@ -336,20 +323,20 @@ jobs_workshop={ }, [df.workshop_type.Kitchen]={ --mat_type=2,3,4 - defaults={flags1={unrotten=true,cookable=true}}, + defaults={flags1={unrotten=true}}, { name="prepare easy meal", - items={{flags1={solid=true}},{}}, + items={{flags1={solid=true,cookable=true}},{flags1={cookable=true}}}, job_fields={job_type=df.job_type.PrepareMeal,mat_type=2} }, { name="prepare fine meal", - items={{flags1={solid=true}},{},{}}, + items={{flags1={solid=true,cookable=true}},{flags1={cookable=true}},{flags1={cookable=true}}}, job_fields={job_type=df.job_type.PrepareMeal,mat_type=3} }, { name="prepare lavish meal", - items={{flags1={solid=true}},{},{},{}}, + items={{flags1={solid=true,cookable=true}},{flags1={cookable=true}},{flags1={cookable=true}},{flags1={cookable=true}}}, job_fields={job_type=df.job_type.PrepareMeal,mat_type=4} }, }, @@ -489,7 +476,7 @@ local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2) end local function scanRawsReaction(buildingId,workshopId,customId) 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 if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then table.insert(ret,reaction) @@ -588,4 +575,4 @@ function getJobs(buildingId,workshopId,customId) --get jobs, add in from raws return ret end -return _ENV \ No newline at end of file +return _ENV diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 603c7ab44..e92ef4d26 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -105,10 +105,11 @@ local function parse_inset(inset) l = inset or 0 t,r,b = l,l,l end - return l,r,t,b + return l,t,r,b end function inset_frame(rect, inset, gap) + if not rect then return mkdims_wh(0, 0, 0, 0) end gap = gap or 0 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) @@ -221,6 +222,7 @@ function Painter:init(args) self.y = self.y1 self.cur_pen = to_pen(args.pen or COLOR_GREY) self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN) + self.to_map = false end function Painter.new(rect, pen) @@ -295,6 +297,11 @@ function Painter:key_pen(pen,...) return self end +function Painter:map(to_map) + self.to_map = to_map + return self +end + function Painter:clear() dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2) return self @@ -308,20 +315,20 @@ function Painter:fill(x1,y1,x2,y2,pen,bg,bold) y1 = math.max(y1+self.y1,self.clip_y1) x2 = math.min(x2+self.x1,self.clip_x2) y2 = math.min(y2+self.y1,self.clip_y2) - dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) + dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2,self.to_map) return self end function Painter:char(char,pen,...) if self:isValidPos() then - dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char) + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, nil, self.to_map) end return self:advance(1, nil) end function Painter:tile(char,tile,pen,...) if self:isValidPos() then - dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile) + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile, self.to_map) end return self:advance(1, nil) end @@ -340,7 +347,8 @@ function Painter:string(text,pen,...) dscreen.paintString( to_pen(self.cur_pen, pen, ...), self.x+dx, self.y, - string.sub(text,dx+1,len) + string.sub(text,dx+1,len), + self.to_map ) end end @@ -354,6 +362,10 @@ function Painter:key(code,pen,...) ) end +function Painter:key_string(code, text, ...) + return self:key(code):string(': '):string(text, ...) +end + -------------------------- -- Abstract view object -- -------------------------- diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 5f23053d8..e98ba2080 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -14,34 +14,33 @@ local world_map = df.global.world.map AREA_MAP_WIDTH = 23 MENU_WIDTH = 30 +refreshSidebar = dfhack.gui.refreshSidebar + function getPanelLayout() - local sw, sh = dscreen.getWindowSize() - local view_height = sh-2 - local view_rb = sw-1 - local area_x2 = sw-AREA_MAP_WIDTH-2 - local menu_x2 = sw-MENU_WIDTH-2 - local menu_x1 = area_x2-MENU_WIDTH-1 - local area_pos = df.global.ui_area_map_width - local menu_pos = df.global.ui_menu_width - local rv = {} - - if area_pos < 3 then - rv.area_map = gui.mkdims_xy(area_x2+1,1,view_rb-1,view_height) - view_rb = area_x2 + local dims = dfhack.gui.getDwarfmodeViewDims() + local area_pos = df.global.ui_menu_width[1] + local menu_pos = df.global.ui_menu_width[0] + + if dims.menu_forced then + menu_pos = area_pos - 1 end - if menu_pos < area_pos or df.global.ui.main.mode ~= 0 then - if menu_pos >= area_pos then - rv.menu_forced = true - menu_pos = area_pos-1 - end - local menu_x = menu_x2 - if menu_pos < 2 then menu_x = menu_x1 end - rv.menu = gui.mkdims_xy(menu_x+1,1,view_rb-1,view_height) - view_rb = menu_x + + local rv = { + menu_pos = menu_pos, + area_pos = area_pos, + map = gui.mkdims_xy(dims.map_x1, dims.map_y1, dims.map_x2, dims.map_y2), + } + + if dims.menu_forced then + rv.menu_forced = true end - rv.area_pos = area_pos - rv.menu_pos = menu_pos - rv.map = gui.mkdims_xy(1,1,view_rb-1,view_height) + if dims.menu_on then + rv.menu = gui.mkdims_xy(dims.menu_x1, dims.y1, dims.menu_x2, dims.y2) + end + if dims.area_on then + rv.area_map = gui.mkdims_xy(dims.area_x1, dims.y1, dims.area_x2, dims.y2) + end + return rv end @@ -476,7 +475,7 @@ function WorkshopOverlay:render(dc) if is_slated_for_remove(self.workshop) then return end - + WorkshopOverlay.super.render(self, dc) end return _ENV diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 4bc48c1b5..741d722dd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -109,6 +109,10 @@ function Pages:getSelected() return self.selected, self.subviews[self.selected] end +function Pages:getSelectedPage() + return self.subviews[self.selected] +end + ---------------- -- Edit field -- ---------------- @@ -121,6 +125,7 @@ EditField.ATTRS{ on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, + key = DEFAULT_NIL, } function EditField:onRenderBody(dc) @@ -131,8 +136,14 @@ function EditField:onRenderBody(dc) cursor = ' ' end local txt = self.text .. cursor - if #txt > dc.width then - txt = string.char(27)..string.sub(txt, #txt-dc.width+2) + local dx = dc.x + if self.key then + dc:key_string(self.key, '') + end + dx = dc.x - dx + local max_width = dc.width - dx + if #txt > max_width then + txt = string.char(27)..string.sub(txt, #txt-max_width+2) end dc:string(txt) end @@ -248,16 +259,17 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) if token.text or token.key then local text = ''..(getval(token.text) or '') - local keypen + local keypen = dfhack.pen.parse(token.key_pen or COLOR_LIGHTGREEN) if dc then local tpen = getval(token.pen) if disabled or is_disabled(token) then dc:pen(getval(token.dpen) or tpen or dpen) - keypen = COLOR_GREEN + if keypen.fg ~= COLOR_BLACK then + keypen.bold = false + end else dc:pen(tpen or pen) - keypen = COLOR_LIGHTGREEN end end @@ -644,6 +656,7 @@ FilteredList = defclass(FilteredList, Widget) FilteredList.ATTRS { edit_below = false, + edit_key = DEFAULT_NIL, } function FilteredList:init(info) @@ -652,6 +665,8 @@ function FilteredList:init(info) frame = { l = info.icon_width, t = 0, h = 1 }, on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), + key = self.edit_key, + active = (self.edit_key == nil), } self.list = List{ frame = { t = 2 }, @@ -696,10 +711,24 @@ function FilteredList:init(info) end end +function FilteredList:onInput(keys) + if self.edit_key and keys[self.edit_key] and not self.edit.active then + self.edit.active = true + elseif keys.LEAVESCREEN and self.edit.active then + self.edit.active = false + else + self:inputToSubviews(keys) + end +end + function FilteredList:getChoices() return self.choices end +function FilteredList:getVisibleChoices() + return self.list.choices +end + function FilteredList:setChoices(choices, pos) choices = choices or {} self.choices = choices diff --git a/library/lua/json.lua b/library/lua/json.lua index ac7aa6327..2b4b3a66a 100644 --- a/library/lua/json.lua +++ b/library/lua/json.lua @@ -21,6 +21,9 @@ function encode_file(data, path, ...) end local contents = encode(data, ...) local f = io.open(path, 'w') + if not f then + error('Could not write to ' .. tostring(path)) + end f:write(contents) f:close() end @@ -32,7 +35,7 @@ end function decode_file(path, ...) local f = io.open(path) if not f then - error('Could not open ' .. path) + error('Could not read from ' .. tostring(path)) end local contents = f:read('*all') f:close() diff --git a/library/lua/makeown.lua b/library/lua/makeown.lua index bf3d8077c..2b16db1df 100644 --- a/library/lua/makeown.lua +++ b/library/lua/makeown.lua @@ -144,14 +144,14 @@ function make_citizen(unit) hf.caste = unit.caste hf.sex = unit.sex hf.appeared_year = dfg.cur_year - hf.born_year = unit.relations.birth_year - hf.born_seconds = unit.relations.birth_time - hf.curse_year = unit.relations.curse_year - hf.curse_seconds = unit.relations.curse_time - hf.anon_1 = unit.relations.anon_2 - hf.anon_2 = unit.relations.anon_3 - hf.old_year = unit.relations.old_year - hf.old_seconds = unit.relations.old_time + hf.born_year = unit.birth_year + hf.born_seconds = unit.birth_time + hf.curse_year = unit.curse_year + hf.curse_seconds = unit.curse_time + hf.birth_year_bias=unit.bias_birth_bias + hf.birth_time_bias=unit.birth_time_bias + hf.old_year = unit.old_year + hf.old_seconds = unit.old_time hf.died_year = -1 hf.died_seconds = -1 hf.name:assign(unit.name) @@ -299,4 +299,4 @@ end -return _ENV \ No newline at end of file +return _ENV diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index afee78aee..78a9e7b8c 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -39,6 +39,9 @@ function CheckedArray:__newindex(idx, val) end self.data[idx] = val end +function CheckedArray:__tostring() + return (''):format(self.type, self.count) +end function CheckedArray:addr2idx(addr, round) local off = addr - self.start if off >= 0 and off < self.size and (round or (off % self.esize) == 0) then @@ -155,8 +158,19 @@ function MemoryArea.new(astart, aend) uint16_t = CheckedArray.new('uint16_t',astart,aend), int32_t = CheckedArray.new('int32_t',astart,aend), uint32_t = CheckedArray.new('uint32_t',astart,aend), - float = CheckedArray.new('float',astart,aend) + int64_t = CheckedArray.new('int64_t',astart,aend), + uint64_t = CheckedArray.new('uint64_t',astart,aend), + float = CheckedArray.new('float',astart,aend), + intptr_t = CheckedArray.new('intptr_t',astart,aend), + uintptr_t = CheckedArray.new('uintptr_t',astart,aend), } + if dfhack.getOSType() == 'windows' then + -- always 32 bits on Windows + obj.long = obj.int32_t + else + -- size of pointer on Linux/OS X + obj.long = obj.intptr_t + end setmetatable(obj, MemoryArea) return obj end @@ -296,14 +310,12 @@ function field_ref(handle,...) end function field_offset(type,...) - local handle = df.new(type) - local _,haddr = df.sizeof(handle) - local _,addr = df.sizeof(field_ref(handle,...)) - -- to aid in diagnosis of bad virtual dtors - io.stderr:write('memscan: deleting instance of '..tostring(type) .. '\n'):flush() - df.delete(handle) - io.stderr:write('successfully deleted\n'):flush() - return addr-haddr + local tmp = df.new('intptr_t') -- pointer to nullptr + local _, haddr = df.sizeof(tmp) + local handle = df.reinterpret_cast(type, tmp) + local _, addr = df.sizeof(field_ref(handle,...)) + df.delete(tmp) + return addr - haddr end function MemoryArea:object_by_field(addr,type,...) @@ -321,7 +333,7 @@ end -- Validation function is_valid_vector(ref,size) - local ints = df.reinterpret_cast('uint32_t', ref) + local ints = df.reinterpret_cast('uintptr_t', ref) return ints[0] <= ints[1] and ints[1] <= ints[2] and (size == nil or (ints[1] - ints[0]) % size == 0) end diff --git a/library/lua/repeat-util.lua b/library/lua/repeat-util.lua index a7ccbefaa..c229ea70d 100644 --- a/library/lua/repeat-util.lua +++ b/library/lua/repeat-util.lua @@ -17,7 +17,9 @@ function cancel(name) if not repeating[name] then return false end - dfhack.timeout_active(repeating[name],nil) + if repeating[name] ~= -1 then + dfhack.timeout_active(repeating[name],nil) + end repeating[name] = nil return true end @@ -26,8 +28,11 @@ function scheduleEvery(name,time,timeUnits,func) cancel(name) local function helper() func() - repeating[name] = dfhack.timeout(time,timeUnits,helper) + if repeating[name] then + repeating[name] = dfhack.timeout(time,timeUnits,helper) + end end + repeating[name] = -1 helper() end diff --git a/library/lua/syndrome-util.lua b/library/lua/syndrome-util.lua index 52cd8b008..efbaa5dea 100644 --- a/library/lua/syndrome-util.lua +++ b/library/lua/syndrome-util.lua @@ -22,6 +22,15 @@ ResetPolicy = ResetPolicy or utils.invert({ "NewInstance" }) +function eraseSyndromeData(unit,syndromeId,oldestFirst) + for i = (oldestFirst and 0) or #unit.body.wounds-1,(oldestFirst and #unit.body.wounds-1) or 0,(oldestFirst and 1) or -1 do + if unit.body.wounds[i].syndrome_id == syndromeId then + unit.body.wounds:erase(i) + break + end + end +end + function eraseSyndrome(unit,syndromeId,oldestFirst) local i1 local iN @@ -38,6 +47,7 @@ function eraseSyndrome(unit,syndromeId,oldestFirst) local syndromes = unit.syndromes.active for i=i1,iN,d do if syndromes[i]['type'] == syndromeId then + eraseSyndromeData(unit,syndromeId,oldestFirst) syndromes:erase(i) return true end @@ -50,6 +60,7 @@ local function eraseSyndromeClassHelper(unit,synclass) local syndrome = df.syndrome.find(unitSyndrome.type) for _,class in ipairs(syndrome.syn_class) do if class.value == synclass then + eraseSyndromeData(unit,syndrome.id) unit.syndromes.active:erase(i) return true end @@ -172,4 +183,3 @@ function infectWithSyndromeIfValidTarget(target,syndrome,resetPolicy) end return _ENV - diff --git a/library/lua/tile-material.lua b/library/lua/tile-material.lua new file mode 100644 index 000000000..5661de73e --- /dev/null +++ b/library/lua/tile-material.lua @@ -0,0 +1,400 @@ +-- tile-material: Functions to help retrieve the material for a tile. + +--[[ +Copyright 2015-2016 Milo Christiansen + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim +that you wrote the original software. If you use this software in a product, an +acknowledgment in the product documentation would be appreciated but is not +required. + +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. +]] + +local _ENV = mkmodule("tile-material") + +--[====[ +tile-material +============= + +This module contains functions for finding the material of a tile. + +There is a function that will find the material of the tile based on it's type (in other words +it will return the material DF is using for that tile), and there are functions that will attempt +to return only a certain class of materials. + +Most users will be most interested in the generic "GetTileMat" function, but the other functions +should be useful in certain cases. For example "GetLayerMat" will always return the material of +the stone (or soil) in the current layer, ignoring any veins or other inclusions. + +Some tile types/materials have special behavior with the "GetTileMat" function. + +* Open space and other "material-less" tiles (such as semi-molten rock or eerie glowing pits) + will return nil. +* Ice will return the hard-coded water material ("WATER:NONE"). + +The specialized functions will return nil if a material of their type is not possible for a tile. +For example calling "GetVeinMat" for a tile that does not have (and has never had) a mineral vein +will always return nil. + +There are two functions for dealing with constructions, one to get the material of the construction +and one that gets the material of the tile the construction was built over. + +All the functions take coordinates as either three arguments (x, y, z) or one argument containing +a table with numeric x, y, and z keys. + +I am not sure how caved in tiles are handled, but after some quick testing it appears that the +game creates mineral veins for them. I am not 100% sure if these functions will reliably work +with all caved in tiles, but I can confirm that they do in at least some cases... +]====] + +-- Since there isn't any consistent style for module documentation I documented every function in +-- the style used by GoDoc (which is what I am most used to). + +-- Internal +local function prepPos(x, y, z) + if x ~= nil and y == nil and z == nil then + if type(x) ~= "table" or type(x.x) ~= "number" or type(x.y) ~= "number" or type(x.z) ~= "number" or x.x == -30000 then + error "Invalid coordinate argument(s)." + end + return x + else + if type(x) ~= "number" or type(y) ~= "number" or type(z) ~= "number" or x == -30000 then + error "Invalid coordinate argument(s)." + end + return {x = x, y = y, z = z} + end +end + +-- Internal +local function fixedMat(id) + local mat = dfhack.matinfo.find(id) + return function(x, y, z) + return mat + end +end + +-- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table covers the common case of returning plant materials for plant tiles and other +-- materials for the remaining tiles. +BasicMats = { + [df.tiletype_material.AIR] = nil, -- Empty + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.MAGMA] = nil, -- SMR + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, + [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? +} + +-- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will ignore plants, returning layer materials (or nil for trees) instead. +NoPlantMats = { + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, + [df.tiletype_material.GRASS_DARK] = GetLayerMat, + [df.tiletype_material.GRASS_DRY] = GetLayerMat, + [df.tiletype_material.GRASS_DEAD] = GetLayerMat, + [df.tiletype_material.PLANT] = GetLayerMat, + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, +} + +-- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. +OnlyPlantMats = { + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, +} + +-- GetLayerMat returns the layer material for the given tile. +-- AFAIK this will never return nil. +function GetLayerMat(x, y, z) + local pos = prepPos(x, y, z) + + local region_info = dfhack.maps.getRegionBiome(dfhack.maps.getTileBiomeRgn(pos)) + local map_block = dfhack.maps.ensureTileBlock(pos) + + local biome = df.world_geo_biome.find(region_info.geo_index) + + local layer_index = map_block.designation[pos.x%16][pos.y%16].geolayer_index + local layer_mat_index = biome.layers[layer_index].mat_index + + return dfhack.matinfo.decode(0, layer_mat_index) +end + +-- GetLavaStone returns the biome lava stone material (generally obsidian). +function GetLavaStone(x, y, z) + local pos = prepPos(x, y, z) + + local regions = df.global.world.world_data.region_details + + local rx, ry = dfhack.maps.getTileBiomeRgn(pos) + + for _, region in ipairs(regions) do + if region.pos.x == rx and region.pos.y == ry then + return dfhack.matinfo.decode(0, region.lava_stone) + end + end + return nil +end + +-- GetVeinMat returns the vein material of the given tile or nil if the tile has no veins. +-- Multiple veins in one tile should be handled properly (smallest vein type, last in the list wins, +-- which seems to be the rule DF uses). +function GetVeinMat(x, y, z) + local pos = prepPos(x, y, z) + + local map_block = dfhack.maps.ensureTileBlock(pos) + + local events = {} + for _, event in ipairs(map_block.block_events) do + if getmetatable(event) == "block_square_event_mineralst" then + if dfhack.maps.getTileAssignment(event.tile_bitmask, pos.x, pos.y) then + table.insert(events, event) + end + end + end + + if #events == 0 then + return nil + end + + local event_priority = function(event) + if event.flags.cluster then + return 1 + elseif event.flags.vein then + return 2 + elseif event.flags.cluster_small then + return 3 + elseif event.flags.cluster_one then + return 4 + else + return 5 + end + end + + local priority = events[1] + for _, event in ipairs(events) do + if event_priority(event) >= event_priority(priority) then + priority = event + end + end + + return dfhack.matinfo.decode(0, priority.inorganic_mat) +end + +-- GetConstructionMat returns the material of the construction at the given tile or nil if the tile +-- has no construction. +function GetConstructionMat(x, y, z) + local pos = prepPos(x, y, z) + + for _, construction in ipairs(df.global.world.constructions) do + if construction.pos.x == pos.x and construction.pos.y == pos.y and construction.pos.z == pos.z then + return dfhack.matinfo.decode(construction) + end + end + return nil +end + +-- GetConstructOriginalTileMat returns the material of the tile under the construction at the given +-- tile or nil if the tile has no construction. +function GetConstructOriginalTileMat(x, y, z) + local pos = prepPos(x, y, z) + + for _, construction in ipairs(df.global.world.constructions) do + if construction.pos.x == pos.x and construction.pos.y == pos.y and construction.pos.z == pos.z then + return GetTileTypeMat(construction.original_tile, BasicMats, pos) + end + end + return nil +end + +-- GetTreeMat returns the material of the tree at the given tile or nil if the tile does not have a +-- tree or giant mushroom. +-- Currently roots are ignored. +function GetTreeMat(x, y, z) + local pos = prepPos(x, y, z) + + local function coordInTree(pos, tree) + local x1 = tree.pos.x - math.floor(tree.tree_info.dim_x / 2) + local x2 = tree.pos.x + math.floor(tree.tree_info.dim_x / 2) + local y1 = tree.pos.y - math.floor(tree.tree_info.dim_y / 2) + local y2 = tree.pos.y + math.floor(tree.tree_info.dim_y / 2) + local z1 = tree.pos.z + local z2 = tree.pos.z + tree.tree_info.body_height + + if not ((pos.x >= x1 and pos.x <= x2) and (pos.y >= y1 and pos.y <= y2) and (pos.z >= z1 and pos.z <= z2)) then + return false + end + + return not tree.tree_info.body[pos.z - tree.pos.z]:_displace((pos.y - y1) * tree.tree_info.dim_x + (pos.x - x1)).blocked + end + + for _, tree in ipairs(df.global.world.plants.all) do + if tree.tree_info ~= nil then + if coordInTree(pos, tree) then + return dfhack.matinfo.decode(419, tree.material) + end + end + end + return nil +end + +-- GetShrubMat returns the material of the shrub at the given tile or nil if the tile does not +-- contain a shrub or sapling. +function GetShrubMat(x, y, z) + local pos = prepPos(x, y, z) + + for _, shrub in ipairs(df.global.world.plants.all) do + if shrub.tree_info == nil then + if shrub.pos.x == pos.x and shrub.pos.y == pos.y and shrub.pos.z == pos.z then + return dfhack.matinfo.decode(419, shrub.material) + end + end + end + return nil +end + +-- GetGrassMat returns the material of the grass at the given tile or nil if the tile is not +-- covered in grass. +function GetGrassMat(x, y, z) + local pos = prepPos(x, y, z) + + local map_block = dfhack.maps.ensureTileBlock(pos) + + for _, event in ipairs(map_block.block_events) do + if getmetatable(event) == "block_square_event_grassst" then + local amount = event.amount[pos.x%16][pos.y%16] + if amount > 0 then + return df.plant_raw.find(event.plant_index).material + end + end + end + return nil +end + +-- GetFeatureMat returns the material of the feature (adamantine tube, underworld surface, etc) at +-- the given tile or nil if the tile is not made of a feature stone. +function GetFeatureMat(x, y, z) + local pos = prepPos(x, y, z) + + local map_block = dfhack.maps.ensureTileBlock(pos) + + if df.tiletype.attrs[map_block.tiletype[pos.x%16][pos.y%16]].material ~= df.tiletype_material.FEATURE then + return nil + end + + if map_block.designation[pos.x%16][pos.y%16].feature_local then + -- adamantine tube, etc + for id, idx in ipairs(df.global.world.features.feature_local_idx) do + if idx == map_block.local_feature then + return dfhack.matinfo.decode(df.global.world.features.map_features[id]) + end + end + elseif map_block.designation[pos.x%16][pos.y%16].feature_global then + -- cavern, magma sea, underworld, etc + for id, idx in ipairs(df.global.world.features.feature_global_idx) do + if idx == map_block.global_feature then + return dfhack.matinfo.decode(df.global.world.features.map_features[id]) + end + end + end + + return nil +end + +-- GetTileMat will return the material of the specified tile as determined by its tile type and the +-- world geology data, etc. +-- The returned material should exactly match the material reported by DF except in cases where is +-- is impossible to get a material. +-- This is equivalent to calling GetTileMatSpec with the BasicMats matspec table. +function GetTileMat(x, y, z) + return GetTileMatSpec(BasicMats, x, y, z) +end + +-- GetTileMatSpec is exactly like GetTileMat except you may specify an explicit matspec table. +-- +-- "matspec" tables are simply tables with tiletype material classes as keys and functions +-- taking a coordinate table and returning a material as values. These tables are used to +-- determine how a specific material for a given tiletype material classification is determined. +-- Any tiletype material class that is unset (left nil) in a matspec table will result in tiles +-- of that type returning nil for their material. +function GetTileMatSpec(matspec, x, y, z) + local pos = prepPos(x, y, z) + + local typ = dfhack.maps.getTileType(pos) + if typ == nil then + return nil + end + + return GetTileTypeMat(typ, matspec, pos) +end + +-- GetTileTypeMat returns the material of the given tile assuming it is the given tiletype. +-- +-- Use this function when you want to check to see what material a given tile would be if it +-- was a specific tiletype. For example you can check to see if the tile used to be part of +-- a mineral vein or similar. Note that you can do the same basic thing by calling the individual +-- material finders directly, but this is sometimes simpler. +-- +-- Unless the tile could be the given type this function will probably return nil. +function GetTileTypeMat(typ, matspec, x, y, z) + local pos = prepPos(x, y, z) + + local type_mat = df.tiletype.attrs[typ].material + + local mat_getter = matspec[type_mat] + if mat_getter == nil then + return nil + end + return mat_getter(pos) +end + +return _ENV + diff --git a/library/lua/utils.lua b/library/lua/utils.lua index a677af3bf..a6007a08f 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -611,15 +611,72 @@ function processArgs(args, validArgs) end function fillTable(table1,table2) - for k,v in pairs(table2) do - table1[k] = v - end + for k,v in pairs(table2) do + table1[k] = v + end end function unfillTable(table1,table2) - for k,v in pairs(table2) do - table1[k] = nil - end + for k,v in pairs(table2) do + table1[k] = nil + end +end + +function df_shortcut_var(k) + if k == 'scr' or k == 'screen' then + return dfhack.gui.getCurViewscreen() + elseif k == 'bld' or k == 'building' then + return dfhack.gui.getSelectedBuilding() + elseif k == 'item' then + return dfhack.gui.getSelectedItem() + elseif k == 'job' then + return dfhack.gui.getSelectedJob() + elseif k == 'wsjob' or k == 'workshop_job' then + return dfhack.gui.getSelectedWorkshopJob() + elseif k == 'unit' then + return dfhack.gui.getSelectedUnit() + elseif k == 'plant' then + return dfhack.gui.getSelectedPlant() + else + for g in pairs(df.global) do + if g == k then + return df.global[k] + end + end + + return _G[k] + end +end + +function df_shortcut_env() + local env = {} + setmetatable(env, {__index = function(self, k) return df_shortcut_var(k) end}) + return env +end + +df_env = df_shortcut_env() + +function df_expr_to_ref(expr) + expr = expr:gsub('%["(.-)"%]', function(field) return '.' .. field end) + :gsub('%[\'(.-)\'%]', function(field) return '.' .. field end) + :gsub('%[(%d+)]', function(field) return '.' .. field end) + local parts = split_string(expr, '%.') + local obj = df_env[parts[1]] + for i = 2, #parts do + local key = tonumber(parts[i]) or parts[i] + local cur = obj[key] + if i == #parts and ((type(cur) ~= 'userdata') or + type(cur) == 'userdata' and getmetatable(cur) == nil) then + obj = obj:_field(key) + else + obj = obj[key] + end + end + return obj +end + +function addressof(obj) + return select(2, obj:sizeof()) end return _ENV diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index a6589bac1..732956a3e 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -49,10 +49,12 @@ using namespace std; using namespace DFHack; #include "DataDefs.h" + #include "df/building_axle_horizontalst.h" #include "df/building_bars_floorst.h" #include "df/building_bars_verticalst.h" #include "df/building_bridgest.h" +#include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/building_coffinst.h" #include "df/building_def.h" @@ -70,13 +72,19 @@ using namespace DFHack; #include "df/building_workshopst.h" #include "df/buildings_other_id.h" #include "df/d_init.h" +#include "df/dfhack_room_quality_level.h" #include "df/general_ref_building_holderst.h" +#include "df/general_ref_contains_unitst.h" #include "df/item.h" +#include "df/item_cagest.h" #include "df/job.h" #include "df/job_item.h" +#include "df/map_block.h" +#include "df/tile_occupancy.h" #include "df/ui.h" #include "df/ui_look_list.h" #include "df/unit.h" +#include "df/unit_relationship_type.h" #include "df/world.h" using namespace df::enums; @@ -131,7 +139,7 @@ void buildings_onUpdate(color_ostream &out) { buildings_do_onupdate = false; - df::job_list_link *link = world->job_list.next; + df::job_list_link *link = world->jobs.list.next; for (; link; link = link->next) { df::job *job = link->item; @@ -224,7 +232,7 @@ bool Buildings::setOwner(df::building *bld, df::unit *unit) auto &blist = bld->owner->owned_buildings; vector_erase_at(blist, linear_index(blist, bld)); - if (auto spouse = df::unit::find(bld->owner->relations.spouse_id)) + if (auto spouse = df::unit::find(bld->owner->relationship_ids[df::unit_relationship_type::Spouse])) { auto &blist = spouse->owned_buildings; vector_erase_at(blist, linear_index(blist, bld)); @@ -235,15 +243,20 @@ bool Buildings::setOwner(df::building *bld, df::unit *unit) if (unit) { + bld->owner_id = unit->id; unit->owned_buildings.push_back(bld); - if (auto spouse = df::unit::find(unit->relations.spouse_id)) + if (auto spouse = df::unit::find(unit->relationship_ids[df::unit_relationship_type::Spouse])) { auto &blist = spouse->owned_buildings; if (bld->canUseSpouseRoom() && linear_index(blist, bld) < 0) blist.push_back(bld); } } + else + { + bld->owner_id = -1; + } return true; } @@ -1162,6 +1175,19 @@ bool Buildings::deconstruct(df::building *bld) return true; } +bool Buildings::markedForRemoval(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + for (df::job *job : bld->jobs) + { + if (job && job->job_type == df::job_type::DestroyBuilding) + { + return true; + } + } + return false; +} + static unordered_map corner1; static unordered_map corner2; @@ -1173,7 +1199,7 @@ void Buildings::clearBuildings(color_ostream& out) { void Buildings::updateBuildings(color_ostream& out, void* ptr) { - int32_t id = (int32_t)ptr; + int32_t id = *((int32_t*)ptr); auto building = df::building::find(id); if (building) @@ -1221,6 +1247,70 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) } } +static std::map> room_quality_names = { + {df::building_type::Bed, { + "Meager Quarters", + "Modest Quarters", + "Quarters", + "Decent Quarters", + "Fine Quarters", + "Great Bedroom", + "Grand Bedroom", + "Royal Bedroom"}}, + {df::building_type::Table, { + "Meager Dining Room", + "Modest Dining Room", + "Dining Room", + "Decent Dining Room", + "Fine Dining Room", + "Great Dining Room", + "Grand Dining Room", + "Royal Dining Room"}}, + {df::building_type::Chair, { + "Meager Office", + "Modest Office", + "Office", + "Decent Office", + "Splendid Office", + "Throne Room", + "Opulent Throne Room", + "Royal Throne Room"}}, + {df::building_type::Coffin, { + "Grave", + "Servant's Burial Chamber", + "Burial Chamber", + "Tomb", + "Fine Tomb", + "Mausoleum", + "Grand Mausoleum", + "Royal Mausoleum"}} +}; + +std::string Buildings::getRoomDescription(df::building *building, df::unit *unit) +{ + CHECK_NULL_POINTER(building); + // unit can be null + + if (!building->is_room) + return ""; + + auto btype = building->getType(); + if (room_quality_names.find(btype) == room_quality_names.end()) + return ""; + + int32_t value = building->getRoomValue(unit); + auto level = ENUM_FIRST_ITEM(dfhack_room_quality_level); + for (auto i_level = level; is_valid_enum_item(i_level); i_level = next_enum_item(i_level, false)) + { + if (value >= ENUM_ATTR(dfhack_room_quality_level, min_value, i_level)) + { + level = i_level; + } + } + + return vector_get(room_quality_names[btype], size_t(level), string("")); +} + void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector *items) { CHECK_NULL_POINTER(stockpile); @@ -1263,6 +1353,20 @@ bool Buildings::isActive(df::building * building) return ((df::building_civzonest*) building)->zone_flags.bits.active != 0; } +bool Buildings::isHospital(df::building * building) + { + if (!isActivityZone(building)) + return false; + return ((df::building_civzonest*) building)->zone_flags.bits.hospital != 0; + } + + bool Buildings::isAnimalTraining(df::building * building) + { + if (!isActivityZone(building)) + return false; + return ((df::building_civzonest*) building)->zone_flags.bits.animal_training != 0; + } + // returns building of pen/pit at cursor position (NULL if nothing found) df::building* Buildings::findPenPitAt(df::coord coord) { @@ -1275,3 +1379,81 @@ df::building* Buildings::findPenPitAt(df::coord coord) } return NULL; } + +using Buildings::StockpileIterator; +StockpileIterator& StockpileIterator::operator++() { + while (stockpile) { + if (block) { + // Check the next item in the current block. + ++current; + } else { + // Start with the top-left block covering the stockpile. + block = Maps::getTileBlock(stockpile->x1, stockpile->y1, stockpile->z); + current = 0; + } + + while (current >= block->items.size()) { + // Out of items in this block; find the next block to search. + if (block->map_pos.x + 16 < stockpile->x2) { + block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z); + current = 0; + } else if (block->map_pos.y + 16 < stockpile->y2) { + block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z); + current = 0; + } else { + // All items in all blocks have been checked. + block = NULL; + item = NULL; + return *this; + } + } + + // If the current item isn't properly stored, move on to the next. + item = df::item::find(block->items[current]); + if (!item->flags.bits.on_ground) { + continue; + } + + if (!Buildings::containsTile(stockpile, item->pos, false)) { + continue; + } + + // Ignore empty bins, barrels, and wheelbarrows assigned here. + if (item->isAssignedToThisStockpile(stockpile->id)) { + auto ref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_ITEM); + if (!ref) continue; + } + + // Found a valid item; yield it. + break; + } + + return *this; +} + +bool Buildings::getCageOccupants(df::building_cagest *cage, vector &units) +{ + CHECK_NULL_POINTER(cage); + if (!world) + return false; + if (cage->contained_items.empty()) + return false; + + auto *cage_item = virtual_cast(cage->contained_items[0]->item); + if (!cage_item) + return false; + + units.clear(); + for (df::general_ref *gref : cage_item->general_refs) + { + auto ref = virtual_cast(gref); + if (ref) + { + df::unit *unit = df::unit::find(ref->unit_id); + if (unit) + units.push_back(unit); + } + } + + return true; +} diff --git a/library/modules/Burrows.cpp b/library/modules/Burrows.cpp index 48a2ca3a8..f8fbbb23f 100644 --- a/library/modules/Burrows.cpp +++ b/library/modules/Burrows.cpp @@ -29,20 +29,21 @@ distribution. #include using namespace std; -#include "Error.h" #include "Core.h" +#include "DataDefs.h" +#include "Error.h" +#include "MiscUtils.h" #include "modules/Burrows.h" #include "modules/Maps.h" #include "modules/Units.h" -#include "MiscUtils.h" - -#include "DataDefs.h" -#include "df/ui.h" -#include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" +#include "df/burrow.h" +#include "df/map_block.h" +#include "df/ui.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 98410c331..9cec2eab9 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -31,21 +31,21 @@ distribution. using namespace std; -#include "VersionInfo.h" +#include "Core.h" #include "MemAccess.h" +#include "TileTypes.h" #include "Types.h" -#include "Core.h" +#include "VersionInfo.h" -#include "modules/Constructions.h" #include "modules/Buildings.h" +#include "modules/Constructions.h" #include "modules/Maps.h" -#include "TileTypes.h" - -#include "df/world.h" -#include "df/job_item.h" -#include "df/building_type.h" #include "df/building_constructionst.h" +#include "df/building_type.h" +#include "df/job_item.h" +#include "df/map_block.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; diff --git a/library/modules/Designations.cpp b/library/modules/Designations.cpp new file mode 100644 index 000000000..1d41cfb49 --- /dev/null +++ b/library/modules/Designations.cpp @@ -0,0 +1,153 @@ +#include "DataDefs.h" +#include "Error.h" + +#include "modules/Designations.h" +#include "modules/Job.h" +#include "modules/Maps.h" + +#include "df/job.h" +#include "df/map_block.h" +#include "df/plant.h" +#include "df/plant_tree_info.h" +#include "df/plant_tree_tile.h" +#include "df/tile_dig_designation.h" +#include "df/world.h" + +using namespace DFHack; +using namespace df::enums; + +using df::global::world; + +static df::map_block *getPlantBlock(const df::plant *plant) +{ + if (!world) + return nullptr; + return Maps::getTileBlock(Designations::getPlantDesignationTile(plant)); +} + +df::coord Designations::getPlantDesignationTile(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (!plant->tree_info) + return plant->pos; + + int dimx = plant->tree_info->dim_x; + int dimy = plant->tree_info->dim_y; + int cx = dimx / 2; + int cy = dimy / 2; + + // Find the southeast trunk tile + int x = cx; + int y = cy; + + while (x + 1 < dimx && y + 1 < dimy) + { + if (plant->tree_info->body[0][(y * dimx) + (x + 1)].bits.trunk) + ++x; + else if (plant->tree_info->body[0][((y + 1) * dimx) + x].bits.trunk) + ++y; + else + break; + } + + return df::coord(plant->pos.x - cx + x, plant->pos.y - cy + y, plant->pos.z); +} + +bool Designations::isPlantMarked(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + df::coord des_pos = getPlantDesignationTile(plant); + df::map_block *block = Maps::getTileBlock(des_pos); + if (!block) + return false; + + if (block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig == tile_dig_designation::Default) + return true; + + for (auto *link = world->jobs.list.next; link; link = link->next) + { + df::job *job = link->item; + if (!job) + continue; + if (job->job_type != job_type::FellTree && job->job_type != job_type::GatherPlants) + continue; + if (job->pos == des_pos) + return true; + } + return false; +} + +bool Designations::canMarkPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (!getPlantBlock(plant)) + return false; + + return !isPlantMarked(plant); +} + +bool Designations::markPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (canMarkPlant(plant)) + { + df::coord des_pos = getPlantDesignationTile(plant); + df::map_block *block = Maps::getTileBlock(des_pos); + block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig = tile_dig_designation::Default; + block->flags.bits.designated = true; + return true; + } + else + { + return false; + } +} + +bool Designations::canUnmarkPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (!getPlantBlock(plant)) + return false; + + return isPlantMarked(plant); +} + +bool Designations::unmarkPlant(const df::plant *plant) +{ + CHECK_NULL_POINTER(plant); + + if (canUnmarkPlant(plant)) + { + df::coord des_pos = getPlantDesignationTile(plant); + df::map_block *block = Maps::getTileBlock(des_pos); + block->designation[des_pos.x % 16][des_pos.y % 16].bits.dig = tile_dig_designation::No; + block->flags.bits.designated = true; + + auto *link = world->jobs.list.next; + while (link) + { + auto *next = link->next; + df::job *job = link->item; + + if (job && + (job->job_type == job_type::FellTree || job->job_type == job_type::GatherPlants) && + job->pos == des_pos) + { + Job::removeJob(job); + } + + link = next; + } + + return true; + } + else + { + return false; + } +} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 9133b48bb..92a4f3f94 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -273,7 +273,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } for ( size_t a = 0; a < df::global::world->buildings.all.size(); a++ ) { df::building* b = df::global::world->buildings.all[a]; - Buildings::updateBuildings(out, (void*)b); + Buildings::updateBuildings(out, (void*)&(b->id)); buildings.insert(b->id); } lastSyndromeTime = -1; @@ -348,7 +348,7 @@ static void manageTickEvent(color_ostream& out) { break; EventHandler handle = (*tickQueue.begin()).second; tickQueue.erase(tickQueue.begin()); - handle.eventHandler(out, (void*)tick); + handle.eventHandler(out, (void*)intptr_t(tick)); toRemove.insert(handle); } if ( toRemove.empty() ) @@ -381,7 +381,7 @@ static void manageJobInitiatedEvent(color_ostream& out) { } multimap copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end()); - for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &df::global::world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; if ( link->item->id <= lastJobId ) @@ -395,10 +395,10 @@ static void manageJobInitiatedEvent(color_ostream& out) { } //helper function for manageJobCompletedEvent -static int32_t getWorkerID(df::job* job) { - auto ref = findRef(job->general_refs, general_ref_type::UNIT_WORKER); - return ref ? ref->getID() : -1; -} +//static int32_t getWorkerID(df::job* job) { +// auto ref = findRef(job->general_refs, general_ref_type::UNIT_WORKER); +// return ref ? ref->getID() : -1; +//} /* TODO: consider checking item creation / experience gain just in case @@ -411,7 +411,7 @@ static void manageJobCompletedEvent(color_ostream& out) { multimap copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); map nowJobs; - for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &df::global::world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; nowJobs[link->item->id] = link->item; @@ -546,7 +546,7 @@ static void manageUnitDeathEvent(color_ostream& out) { continue; for ( auto i = copy.begin(); i != copy.end(); i++ ) { - (*i).second.eventHandler(out, (void*)unit->id); + (*i).second.eventHandler(out, (void*)intptr_t(unit->id)); } livingUnits.erase(unit->id); } @@ -582,7 +582,7 @@ static void manageItemCreationEvent(color_ostream& out) { if ( item->flags.bits.spider_web ) continue; for ( auto i = copy.begin(); i != copy.end(); i++ ) { - (*i).second.eventHandler(out, (void*)item->id); + (*i).second.eventHandler(out, (void*)intptr_t(item->id)); } } nextItem = *df::global::item_next_id; @@ -609,7 +609,7 @@ static void manageBuildingEvent(color_ostream& out) { buildings.insert(a); for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)a); + bob.eventHandler(out, (void*)&a); } } nextBuilding = *df::global::building_next_id; @@ -625,7 +625,7 @@ static void manageBuildingEvent(color_ostream& out) { for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)id); + bob.eventHandler(out, (void*)&id); } a = buildings.erase(a); } @@ -708,7 +708,7 @@ static void manageInvasionEvent(color_ostream& out) { for ( auto a = copy.begin(); a != copy.end(); a++ ) { EventHandler handle = (*a).second; - handle.eventHandler(out, (void*)(nextInvasion-1)); + handle.eventHandler(out, (void*)intptr_t(nextInvasion-1)); } } @@ -831,7 +831,7 @@ static void manageReportEvent(color_ostream& out) { df::report* report = reports[a]; for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler handle = (*b).second; - handle.eventHandler(out, (void*)report->id); + handle.eventHandler(out, (void*)intptr_t(report->id)); } lastReport = report->id; } @@ -1150,7 +1150,7 @@ static void manageInteractionEvent(color_ostream& out) { df::report* lastAttackEvent = NULL; df::unit* lastAttacker = NULL; - df::unit* lastDefender = NULL; + //df::unit* lastDefender = NULL; unordered_map > history; for ( ; a < reports.size(); a++ ) { df::report* report = reports[a]; @@ -1164,7 +1164,7 @@ static void manageInteractionEvent(color_ostream& out) { if ( attack ) { lastAttackEvent = report; lastAttacker = NULL; - lastDefender = NULL; + //lastDefender = NULL; } vector relevantUnits = gatherRelevantUnits(out, lastAttackEvent, report); InteractionData data = getAttacker(out, lastAttackEvent, lastAttacker, attack ? NULL : report, relevantUnits); @@ -1201,7 +1201,7 @@ static void manageInteractionEvent(color_ostream& out) { } //out.print("%s,%d\n",__FILE__,__LINE__); lastAttacker = df::unit::find(data.attacker); - lastDefender = df::unit::find(data.defender); + //lastDefender = df::unit::find(data.defender); //fire event for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler handle = (*b).second; diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 05d4e4fda..430e6351e 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -53,7 +53,7 @@ using namespace DFHack; bool Filesystem::chdir (std::string path) { - return !(bool)::chdir(path.c_str()); + return ::chdir(path.c_str()) == 0; } std::string Filesystem::getcwd () @@ -79,7 +79,7 @@ bool Filesystem::mkdir (std::string path) fail = ::mkdir(path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); #endif - return !(bool)fail; + return fail == 0; } bool Filesystem::rmdir (std::string path) @@ -90,7 +90,7 @@ bool Filesystem::rmdir (std::string path) #else fail = ::rmdir(path.c_str()); #endif - return !(bool)fail; + return fail == 0; } #ifdef _WIN32 @@ -116,13 +116,13 @@ _filetype mode2type (mode_t mode) { bool Filesystem::stat (std::string path, STAT_STRUCT &info) { - return !(bool)(STAT_FUNC(path.c_str(), &info)); + return (STAT_FUNC(path.c_str(), &info)) == 0; } bool Filesystem::exists (std::string path) { STAT_STRUCT info; - return (bool)Filesystem::stat(path, info); + return Filesystem::stat(path, info); } _filetype Filesystem::filetype (std::string path) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 2cc955d63..758395f40 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -46,63 +46,81 @@ using namespace DFHack; #include "modules/Maps.h" #include "DataDefs.h" -#include "df/world.h" + +#include "df/announcement_flags.h" +#include "df/assign_trade_status.h" +#include "df/building_cagest.h" +#include "df/building_civzonest.h" +#include "df/building_furnacest.h" +#include "df/building_trapst.h" +#include "df/building_type.h" +#include "df/building_workshopst.h" +#include "df/d_init.h" +#include "df/game_mode.h" +#include "df/general_ref.h" #include "df/global_objects.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/viewscreen_dungeonmodest.h" -#include "df/viewscreen_dungeon_monsterstatusst.h" -#include "df/viewscreen_jobst.h" -#include "df/viewscreen_joblistst.h" -#include "df/viewscreen_unitlistst.h" +#include "df/graphic.h" +#include "df/historical_figure.h" +#include "df/interfacest.h" +#include "df/item_corpsepiecest.h" +#include "df/item_corpsest.h" +#include "df/job.h" +#include "df/layer_object_listst.h" +#include "df/occupation.h" +#include "df/plant.h" +#include "df/popup_message.h" +#include "df/report.h" +#include "df/route_stockpile_link.h" +#include "df/stop_depart_condition.h" +#include "df/ui_advmode.h" +#include "df/ui_build_selector.h" +#include "df/ui_look_list.h" +#include "df/ui_sidebar_menus.h" +#include "df/ui_unit_view_mode.h" +#include "df/unit.h" +#include "df/unit_inventory_item.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_assign_display_itemst.h" #include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_customize_unitst.h" +#include "df/viewscreen_dungeon_monsterstatusst.h" +#include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_itemst.h" +#include "df/viewscreen_joblistst.h" +#include "df/viewscreen_jobmanagementst.h" +#include "df/viewscreen_jobst.h" #include "df/viewscreen_layer.h" -#include "df/viewscreen_layer_noblelistst.h" -#include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_unit_healthst.h" +#include "df/viewscreen_layer_unit_relationshipst.h" #include "df/viewscreen_locationsst.h" #include "df/viewscreen_petst.h" -#include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_textviewerst.h" +#include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_unitst.h" +#include "df/viewscreen_reportlistst.h" +#include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" -#include "df/ui_unit_view_mode.h" -#include "df/ui_sidebar_menus.h" -#include "df/ui_look_list.h" -#include "df/ui_advmode.h" -#include "df/job.h" -#include "df/ui_build_selector.h" -#include "df/building_workshopst.h" -#include "df/building_furnacest.h" -#include "df/building_trapst.h" -#include "df/building_civzonest.h" -#include "df/general_ref.h" -#include "df/unit_inventory_item.h" -#include "df/report.h" -#include "df/popup_message.h" -#include "df/interfacest.h" -#include "df/graphic.h" -#include "df/layer_object_listst.h" -#include "df/assign_trade_status.h" -#include "df/announcement_flags.h" -#include "df/announcements.h" -#include "df/stop_depart_condition.h" -#include "df/route_stockpile_link.h" -#include "df/game_mode.h" -#include "df/unit.h" -#include "df/occupation.h" +#include "df/world.h" using namespace df::enums; + +using df::global::gamemode; +using df::global::gps; using df::global::gview; using df::global::init; -using df::global::gps; -using df::global::ui; -using df::global::world; using df::global::selection_rect; +using df::global::ui; using df::global::ui_menu_width; -using df::global::ui_area_map_width; -using df::global::gamemode; +using df::global::ui_sidebar_menus; +using df::global::world; static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx) { @@ -556,6 +574,18 @@ DEFINE_GET_FOCUS_STRING_HANDLER(locations) focus += "/" + enum_item_key(screen->menu); } +DEFINE_GET_FOCUS_STRING_HANDLER(jobmanagement) +{ + focus += (screen->in_max_workshops ? "/MaxWorkshops" : "/Main"); +} + +DEFINE_GET_FOCUS_STRING_HANDLER(workquota_condition) +{ + focus += "/" + enum_item_key(screen->mode); + if (screen->item_count_edit) + focus += "/EditCount"; +} + std::string Gui::getFocusString(df::viewscreen *top) { if (!top) @@ -799,6 +829,14 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) using df::global::ui_look_cursor; using df::global::ui_look_list; using df::global::ui_selected_unit; + using df::global::ui_building_in_assign; + using df::global::ui_building_assign_units; + using df::global::ui_building_item_cursor; + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top)) + { + return screen->unit; + } if (VIRTUAL_CAST_VAR(screen, df::viewscreen_joblistst, top)) { @@ -815,6 +853,14 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_dungeon_monsterstatusst, top)) return screen->unit; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_unit_relationshipst, top)) + { + if (VIRTUAL_CAST_VAR(list, df::layer_object_listst, vector_get(screen->layer_objects, 0))) + return vector_get(screen->relation_unit, list->cursor); + + return NULL; + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); @@ -873,11 +919,13 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_petst, top)) { + df::viewscreen_petst::T_animal animal_default; + animal_default.unit = NULL; switch (screen->mode) { case df::viewscreen_petst::List: if (!vector_get(screen->is_vermin, screen->cursor)) - return vector_get(screen->animal, screen->cursor).unit; + return vector_get(screen->animal, screen->cursor, animal_default).unit; return NULL; case df::viewscreen_petst::SelectTrainer: @@ -895,30 +943,145 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top)) + { + if (screen->parent) + return getAnyUnit(screen->parent); + return NULL; + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_reportlistst, top)) + return vector_get(screen->units, screen->cursor); + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_announcelistst, top)) + { + if (world && screen->unit) { + // in (r)eports -> enter + auto *report = vector_get(screen->reports, screen->sel_idx); + if (report) + { + for (df::unit *unit : world->units.all) + { + if (unit && screen->report_type >= 0 && screen->report_type < 3 + && unit != screen->unit) // find 'other' unit related to this report + { + for (int32_t report_id : unit->reports.log[screen->report_type]) + { + if (report_id == report->id) + return unit; + } + } + } + } + } else { + // in (a)nnouncements + return NULL; // cannot determine unit from reports + } + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_militaryst, top)) + { + if (screen->page == df::viewscreen_layer_militaryst::T_page::Positions) { + auto positions = getLayerList(screen, 1); + if (positions && positions->enabled && positions->active) + return vector_get(screen->positions.assigned, positions->cursor); + + auto candidates = getLayerList(screen, 2); + if (candidates && candidates->enabled && candidates->active) + return vector_get(screen->positions.candidates, candidates->cursor); + } + if (screen->page == df::viewscreen_layer_militaryst::T_page::Equip) { + auto positions = getLayerList(screen, 1); + if (positions && positions->enabled && positions->active) + return vector_get(screen->equip.units, positions->cursor); + } + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_unit_healthst, top)) + return screen->unit; + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_customize_unitst, top)) + return screen->unit; + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedUnit(); if (!Gui::dwarfmode_hotkey(top)) return NULL; + if (!ui) + return NULL; + + // general assigning units in building, i.e. (q)uery cage -> (a)ssign + if (ui_building_in_assign && *ui_building_in_assign + && ui_building_assign_units && ui_building_item_cursor + && ui->main.mode != Zones) // dont show for (i) zone + return vector_get(*ui_building_assign_units, *ui_building_item_cursor); + + if (ui->follow_unit != -1) + return df::unit::find(ui->follow_unit); + switch (ui->main.mode) { case ViewUnits: { - if (!ui_selected_unit) + if (!ui_selected_unit || !world) return NULL; return vector_get(world->units.active, *ui_selected_unit); } + case ZonesPitInfo: // (i) zone -> (P)it + case ZonesPenInfo: // (i) zone -> pe(N) + { + if (!ui_building_assign_units || !ui_building_item_cursor) + return NULL; + + return vector_get(*ui_building_assign_units, *ui_building_item_cursor); + } + case Burrows: + { + if (ui->burrows.in_add_units_mode) + return vector_get(ui->burrows.list_units, ui->burrows.unit_cursor_pos); + + return NULL; + } + case QueryBuilding: + { + if (df::building *building = getAnyBuilding(top)) + { + if (VIRTUAL_CAST_VAR(cage, df::building_cagest, building)) + { + if (ui_building_item_cursor) + return df::unit::find(vector_get(cage->assigned_units, *ui_building_item_cursor)); + } + } + return NULL; + } case LookAround: { if (!ui_look_list || !ui_look_cursor) return NULL; - auto item = vector_get(ui_look_list->items, *ui_look_cursor); - if (item && item->type == df::ui_look_list::T_items::Unit) - return item->unit; - else - return NULL; + if (auto item = vector_get(ui_look_list->items, *ui_look_cursor)) + { + if (item->type == df::ui_look_list::T_items::Unit) + return item->unit; + else if (item->type == df::ui_look_list::T_items::Item) + { + if (VIRTUAL_CAST_VAR(corpse, df::item_corpsest, item->item)) + return df::unit::find(corpse->unit_id); // loo(k) at corpse + else if (VIRTUAL_CAST_VAR(corpsepiece, df::item_corpsepiecest, item->item)) + return df::unit::find(corpsepiece->unit_id); // loo(k) at corpse piece + } + else if (item->type == df::ui_look_list::T_items::Spatter) + { + // loo(k) at blood/ichor/.. spatter with a name + MaterialInfo mat; + if (mat.decode(item->spatter_mat_type, item->spatter_mat_index) && mat.figure) + return df::unit::find(mat.figure->unit_id); + } + } + + return NULL; } default: return NULL; @@ -951,6 +1114,18 @@ df::item *Gui::getAnyItem(df::viewscreen *top) using df::global::ui_building_item_cursor; using df::global::ui_sidebar_menus; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top)) + { + // return the main item if the parent screen is a viewscreen_itemst + if (VIRTUAL_CAST_VAR(parent_screen, df::viewscreen_itemst, screen->parent)) + return parent_screen->item; + + if (screen->parent) + return getAnyItem(screen->parent); + + return NULL; + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); @@ -992,6 +1167,15 @@ df::item *Gui::getAnyItem(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_assign_display_itemst, top)) + { + if (screen->sel_column == df::viewscreen_assign_display_itemst::T_sel_column::Items) + return vector_get(screen->items[screen->item_type[screen->sel_type]], + screen->sel_item); + + return NULL; + } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedItem(); @@ -1120,6 +1304,50 @@ df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet) return building; } +df::plant *Gui::getAnyPlant(df::viewscreen *top) +{ + using df::global::cursor; + using df::global::ui; + using df::global::world; + + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedPlant(); + + if (Gui::dwarfmode_hotkey(top)) + { + if (!cursor || !ui || !world) + return nullptr; + + if (ui->main.mode == ui_sidebar_mode::LookAround) + { + for (df::plant *plant : world->plants.all) + { + if (plant->pos.x == cursor->x && plant->pos.y == cursor->y && plant->pos.z == cursor->z) + { + return plant; + } + } + } + } + + return nullptr; +} + +bool Gui::any_plant_hotkey(df::viewscreen *top) +{ + return getAnyPlant(top) != nullptr; +} + +df::plant *Gui::getSelectedPlant(color_ostream &out, bool quiet) +{ + df::plant *plant = getAnyPlant(Core::getTopViewscreen()); + + if (!plant && !quiet) + out.printerr("No plant is selected in the UI.\n"); + + return plant; +} + // DFHACK_EXPORT void Gui::writeToGamelog(std::string message) @@ -1303,16 +1531,16 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r void Gui::showAnnouncement(std::string message, int color, bool bright) { - df::announcement_flags mode(0); + df::announcement_flags mode; mode.bits.D_DISPLAY = mode.bits.A_DISPLAY = true; - makeAnnouncement(df::announcement_type(0), mode, df::coord(), message, color, bright); + makeAnnouncement(df::announcement_type(), mode, df::coord(), message, color, bright); } void Gui::showZoomAnnouncement( df::announcement_type type, df::coord pos, std::string message, int color, bool bright ) { - df::announcement_flags mode(0); + df::announcement_flags mode; mode.bits.D_DISPLAY = mode.bits.A_DISPLAY = true; makeAnnouncement(type, mode, pos, message, color, bright); @@ -1333,13 +1561,13 @@ void Gui::showAutoAnnouncement( df::announcement_type type, df::coord pos, std::string message, int color, bool bright, df::unit *unit1, df::unit *unit2 ) { - using df::global::announcements; + using df::global::d_init; - df::announcement_flags flags(0); + df::announcement_flags flags; flags.bits.D_DISPLAY = flags.bits.A_DISPLAY = true; - if (is_valid_enum_item(type) && announcements) - flags = announcements->flags[type]; + if (is_valid_enum_item(type) && d_init) + flags = d_init->announcements.flags[type]; int id = makeAnnouncement(type, flags, pos, message, color, bright); @@ -1404,13 +1632,17 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default() auto ws = Screen::getWindowSize(); dims.y1 = 1; dims.y2 = ws.y-2; + dims.map_x1 = 1; dims.map_x2 = ws.x-2; + dims.map_y1 = dims.y1; + dims.map_y2 = dims.y2; + dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1; dims.menu_forced = false; - int menu_pos = (ui_menu_width ? *ui_menu_width : 2); - int area_pos = (ui_area_map_width ? *ui_area_map_width : 3); + int menu_pos = (ui_menu_width ? (*ui_menu_width)[0] : 2); + int area_pos = (ui_menu_width ? (*ui_menu_width)[1] : 3); if (ui && ui->main.mode && menu_pos >= area_pos) { @@ -1485,7 +1717,7 @@ bool Gui::revealInDwarfmodeMap(df::coord pos, bool center) auto dims = getDwarfmodeViewDims(); int w = dims.map_x2 - dims.map_x1 + 1; - int h = dims.y2 - dims.y1 + 1; + int h = dims.map_y2 - dims.map_y1 + 1; *window_z = pos.z; @@ -1507,6 +1739,34 @@ bool Gui::revealInDwarfmodeMap(df::coord pos, bool center) return true; } +bool Gui::refreshSidebar() +{ + auto scr = getViewscreenByType(0); + if (scr) + { + if (df::global::window_z && *df::global::window_z == 0) + { + scr->feed_key(interface_key::CURSOR_UP_Z); + scr->feed_key(interface_key::CURSOR_DOWN_Z); + } + else + { + scr->feed_key(interface_key::CURSOR_DOWN_Z); + scr->feed_key(interface_key::CURSOR_UP_Z); + } + return true; + } + return false; +} + +bool Gui::inRenameBuilding() +{ + if (!ui_sidebar_menus) + return false; + + return ui_sidebar_menus->barracks.in_rename; +} + bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) { x = *df::global::window_x; @@ -1596,14 +1856,14 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height) bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width) { - menu_width = *df::global::ui_menu_width; - area_map_width = *df::global::ui_area_map_width; + menu_width = (*df::global::ui_menu_width)[0]; + area_map_width = (*df::global::ui_menu_width)[1]; return true; } bool Gui::setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width) { - *df::global::ui_menu_width = menu_width; - *df::global::ui_area_map_width = area_map_width; + (*df::global::ui_menu_width)[0] = menu_width; + (*df::global::ui_menu_width)[1] = area_map_width; return true; } diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 4c224a0d6..a1b4ace4d 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -75,6 +75,7 @@ using namespace std; #include "df/itemdef_trapcompst.h" #include "df/itemdef_weaponst.h" #include "df/job_item.h" +#include "df/mandate.h" #include "df/map_block.h" #include "df/proj_itemst.h" #include "df/proj_list_link.h" @@ -84,6 +85,7 @@ using namespace std; #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/vehicle.h" #include "df/vermin.h" #include "df/viewscreen_itemst.h" #include "df/world.h" @@ -1062,7 +1064,6 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::CHAIN: case item_type::FLASK: case item_type::GOBLET: - case item_type::INSTRUMENT: case item_type::TOY: case item_type::CAGE: case item_type::BARREL: @@ -1162,10 +1163,13 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::MEAT: case item_type::PLANT: case item_type::PLANT_GROWTH: - case item_type::CHEESE: value = 2; break; + case item_type::CHEESE: + value = 10; + break; + case item_type::FISH: case item_type::FISH_RAW: case item_type::EGG: @@ -1222,6 +1226,10 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat value = 7; break; + case item_type::SHEET: + value = 5; + break; + case item_type::PANTS: if (size_t(item_subtype) < world->raws.itemdefs.pants.size()) value = world->raws.itemdefs.pants[item_subtype]->value; @@ -1250,16 +1258,23 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::FOOD: return 10; -// case item_type::ROCK: - default: - return 0; - case item_type::TOOL: if (size_t(item_subtype) < world->raws.itemdefs.tools.size()) value = world->raws.itemdefs.tools[item_subtype]->value; else value = 10; break; + + case item_type::INSTRUMENT: + if (size_t(item_subtype) < world->raws.itemdefs.instruments.size()) + value = world->raws.itemdefs.instruments[item_subtype]->value; + else + value = 10; + break; + +// case item_type::ROCK: + default: + return 0; } MaterialInfo mat; @@ -1375,8 +1390,9 @@ int32_t Items::createItem(df::item_type item_type, int16_t item_subtype, int16_t df::enums::game_type::game_type type = *df::global::gametype; prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, job_skill::NONE, - df::historical_entity::find(unit->civ_id), - ((type == df::enums::game_type::DWARF_MAIN) || (type == df::enums::game_type::DWARF_RECLAIM)) ? df::world_site::find(df::global::ui->site_id) : NULL); + df::historical_entity::find(unit->civ_id), 0, + ((type == df::enums::game_type::DWARF_MAIN) || (type == df::enums::game_type::DWARF_RECLAIM)) ? df::world_site::find(df::global::ui->site_id) : NULL, + 0); if ( out_items.size() != 1 ) return -1; @@ -1387,3 +1403,106 @@ int32_t Items::createItem(df::item_type item_type, int16_t item_subtype, int16_t return out_items[0]->id; } +/* + * Trade Info + */ + +bool Items::checkMandates(df::item *item) +{ + CHECK_NULL_POINTER(item); + + for (df::mandate *mandate : world->mandates) + { + if (mandate->mode != df::mandate::T_mode::Export) + continue; + + if (item->getType() != mandate->item_type || + (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) + continue; + + if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) + continue; + + if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) + continue; + + return false; + } + + return true; +} + +bool Items::canTrade(df::item *item) +{ + CHECK_NULL_POINTER(item); + + if (item->flags.bits.owned || item->flags.bits.artifact || item->flags.bits.spider_web || item->flags.bits.in_job) + return false; + + for (df::general_ref *ref : item->general_refs) + { + switch (ref->getType()) + { + case general_ref_type::UNIT_HOLDER: + return false; + + case general_ref_type::BUILDING_HOLDER: + return false; + + default: + break; + } + } + + for (df::specific_ref *ref : item->specific_refs) + { + if (ref->type == specific_ref_type::JOB) + { + // Ignore any items assigned to a job + return false; + } + } + + return checkMandates(item); +} + +bool Items::canTradeWithContents(df::item *item) +{ + CHECK_NULL_POINTER(item); + + if (item->flags.bits.in_inventory) + return false; + + if (!canTrade(item)) + return false; + + vector contained_items; + getContainedItems(item, &contained_items); + for (df::item *cit : contained_items) + { + if (!canTrade(cit)) + return false; + } + + return true; +} + +bool Items::isRouteVehicle(df::item *item) +{ + CHECK_NULL_POINTER(item); + int id = item->getVehicleID(); + if (id < 0) return false; + + auto vehicle = df::vehicle::find(id); + return vehicle && vehicle->route_id >= 0; +} + +bool Items::isSquadEquipment(df::item *item) +{ + CHECK_NULL_POINTER(item); + if (!ui) + return false; + + auto &vec = ui->equipment.items_assigned[item->getType()]; + return binsearch_index(vec, &df::item::id, item->id) >= 0; +} diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 2678eadba..7673737b2 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -129,9 +129,6 @@ void DFHack::Job::deleteJobStruct(df::job *job, bool keptEverything) bool DFHack::operator== (const df::job_item &a, const df::job_item &b) { - CHECK_NULL_POINTER(&a); - CHECK_NULL_POINTER(&b); - if (!(CMP(item_type) && CMP(item_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(flags1.whole) && CMP(quantity) && CMP(vector_id) && @@ -152,9 +149,6 @@ bool DFHack::operator== (const df::job_item &a, const df::job_item &b) bool DFHack::operator== (const df::job &a, const df::job &b) { - CHECK_NULL_POINTER(&a); - CHECK_NULL_POINTER(&b); - if (!(CMP(job_type) && CMP(job_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(item_subtype) && CMP(item_category.whole) && @@ -212,7 +206,7 @@ void DFHack::Job::printJobDetails(color_ostream &out, df::job *job) out.color(job->flags.bits.suspend ? COLOR_DARKGREY : COLOR_GREY); out << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type); if (job->flags.whole) - out << " (" << bitfield_to_string(job->flags) << ")"; + out << " (" << bitfield_to_string(job->flags) << ")"; out << endl; out.reset_color(); @@ -304,6 +298,135 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } +void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *ref) { + if (!ref) return; + + auto item = ref->item; + if (!item) return; + + //Work backward through the specific refs & remove/delete all specific refs to this job + int refCount = item->specific_refs.size(); + bool stillHasJobs = false; + for(int refIndex = refCount-1; refIndex >= 0; refIndex--) { + auto ref = item->specific_refs[refIndex]; + + if (ref->type == df::specific_ref_type::JOB) { + if (ref->job == job) { + vector_erase_at(item->specific_refs, refIndex); + delete ref; + } else { + stillHasJobs = true; + } + } + } + + if (!stillHasJobs) item->flags.bits.in_job = false; +} + +bool DFHack::Job::disconnectJobGeneralRef(df::job *job, df::general_ref *ref) { + if (ref == NULL) return true; + + df::building *building = NULL; + df::unit *unit = NULL; + + switch (ref->getType()) { + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + + if (building != NULL) { + int jobIndex = linear_index(building->jobs, job); + if (jobIndex >= 0) { + vector_erase_at(building->jobs, jobIndex); + } + } + break; + case general_ref_type::UNIT_WORKER: + unit = ref->getUnit(); + + if (unit != NULL) { + if (unit->job.current_job == job) { + unit->job.current_job = NULL; + } + } + break; + default: + return false; + } + + return true; +} + +bool DFHack::Job::removeJob(df::job *job) { + using df::global::world; + CHECK_NULL_POINTER(job); + + if (job->flags.bits.special) //I don't think you can cancel these, because DF wasn't build to expect it? + return false; + + //We actually only know how to handle BUILDING_HOLDER and UNIT_WORKER refs- there's probably a great + //way to handle them, but until we have a good example, we'll just fail to remove jobs that have other sorts + //of refs, or any specific refs + if (job->specific_refs.size() > 0) + return false; + + for (auto genRefItr = job->general_refs.begin(); genRefItr != job->general_refs.end(); ++genRefItr) { + auto ref = *genRefItr; + if (ref != NULL && (ref->getType() != general_ref_type::BUILDING_HOLDER && ref->getType() != general_ref_type::UNIT_WORKER)) + return false; + } + + //Disconnect, delete, and wipe all general refs + while (job->general_refs.size() > 0) { + auto ref = job->general_refs[0]; + + //Our code above should have ensured that this won't return false- if it does, there's not + //a great way of recovering since we can't properly destroy the job & we can't leave it + //around. Better to know the moment that becomes a problem. + bool success = disconnectJobGeneralRef(job, ref); + assert(success); + + vector_erase_at(job->general_refs, 0); + if (ref != NULL) delete ref; + } + + //Detach all items from the job + while (job->items.size() > 0) { + auto itemRef = job->items[0]; + disconnectJobItem(job, itemRef); + vector_erase_at(job->items, 0); + if (itemRef != NULL) delete itemRef; + } + + //Remove job from job board + Job::removePostings(job, true); + + //Clean up job_items + while (job->job_items.size() > 0) { + auto jobItem = job->job_items[0]; + vector_erase_at(job->job_items, 0); + if (jobItem) { + delete jobItem; + } + } + + //Remove job from global list + if (job->list_link) { + auto prev = job->list_link->prev; + auto next = job->list_link->next; + + if (prev) + prev->next = next; + + if (next) + next->prev = prev; + + delete job->list_link; + } + + delete job; + return true; +} + bool DFHack::Job::removeWorker(df::job *job, int cooldown) { CHECK_NULL_POINTER(job); @@ -361,10 +484,10 @@ bool DFHack::Job::linkIntoWorld(df::job *job, bool new_id) job->list_link = new df::job_list_link(); job->list_link->item = job; - linked_list_append(&world->job_list, job->list_link); + linked_list_append(&world->jobs.list, job->list_link); return true; } else { - df::job_list_link *ins_pos = &world->job_list; + df::job_list_link *ins_pos = &world->jobs.list; while (ins_pos->next && ins_pos->next->item->id < job->id) ins_pos = ins_pos->next; @@ -385,18 +508,19 @@ bool DFHack::Job::removePostings(df::job *job, bool remove_all) bool removed = false; if (!remove_all) { - if (job->posting_index >= 0 && job->posting_index < world->job_postings.size()) + if (job->posting_index >= 0 && size_t(job->posting_index) < world->jobs.postings.size()) { - world->job_postings[job->posting_index]->flags.bits.dead = true; + world->jobs.postings[job->posting_index]->flags.bits.dead = true; removed = true; } } else { - for (auto it = world->job_postings.begin(); it != world->job_postings.end(); ++it) + for (auto it = world->jobs.postings.begin(); it != world->jobs.postings.end(); ++it) { if ((**it).job == job) { + (**it).job = NULL; (**it).flags.bits.dead = true; removed = true; } @@ -423,7 +547,7 @@ bool DFHack::Job::listNewlyCreated(std::vector *pvec, int *id_var) pvec->reserve(std::min(20,cur_id - old_id)); - df::job_list_link *link = world->job_list.next; + df::job_list_link *link = world->jobs.list.next; for (; link; link = link->next) { int id = link->item->id; diff --git a/library/modules/kitchen.cpp b/library/modules/Kitchen.cpp similarity index 75% rename from library/modules/kitchen.cpp rename to library/modules/Kitchen.cpp index aa235780d..705cb7bee 100644 --- a/library/modules/kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -13,7 +13,7 @@ using namespace std; #include "MemAccess.h" #include "Types.h" #include "Error.h" -#include "modules/kitchen.h" +#include "modules/Kitchen.h" #include "ModuleFactory.h" #include "Core.h" using namespace DFHack; @@ -57,7 +57,7 @@ void Kitchen::allowPlantSeedCookery(t_materialIndex materialIndex) { if(ui->kitchen.mat_indices[i] == materialIndex && (ui->kitchen.item_types[i] == item_type::SEEDS || ui->kitchen.item_types[i] == item_type::PLANT) - && ui->kitchen.exc_types[i] == cookingExclusion + && ui->kitchen.exc_types[i] == df::kitchen_exc_type::Cook ) { match = true; @@ -73,7 +73,7 @@ void Kitchen::allowPlantSeedCookery(t_materialIndex materialIndex) ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + matchIndex); } } while(match); -}; +} void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) { @@ -83,7 +83,7 @@ void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) for(std::size_t i = 0; i < size(); ++i) { if(ui->kitchen.mat_indices[i] == materialIndex - && ui->kitchen.exc_types[i] == cookingExclusion) + && ui->kitchen.exc_types[i] == df::kitchen_exc_type::Cook) { if(ui->kitchen.item_types[i] == item_type::SEEDS) SeedAlreadyIn = true; @@ -97,7 +97,7 @@ void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) ui->kitchen.item_subtypes.push_back(organicSubtype); ui->kitchen.mat_types.push_back(type->material_defs.type_seed); ui->kitchen.mat_indices.push_back(materialIndex); - ui->kitchen.exc_types.push_back(cookingExclusion); + ui->kitchen.exc_types.push_back(df::kitchen_exc_type::Cook); } if(!PlantAlreadyIn) { @@ -105,9 +105,9 @@ void Kitchen::denyPlantSeedCookery(t_materialIndex materialIndex) ui->kitchen.item_subtypes.push_back(organicSubtype); ui->kitchen.mat_types.push_back(type->material_defs.type_basic_mat); ui->kitchen.mat_indices.push_back(materialIndex); - ui->kitchen.exc_types.push_back(cookingExclusion); + ui->kitchen.exc_types.push_back(df::kitchen_exc_type::Cook); } -}; +} void Kitchen::fillWatchMap(std::map& watchMap) { @@ -119,7 +119,7 @@ void Kitchen::fillWatchMap(std::map& watchMap) watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i]; } } -}; +} void Kitchen::removeLimit(t_materialIndex materialIndex) { @@ -148,7 +148,7 @@ void Kitchen::removeLimit(t_materialIndex materialIndex) ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + matchIndex); } } while(match); -}; +} void Kitchen::setLimit(t_materialIndex materialIndex, unsigned int limit) { @@ -162,7 +162,7 @@ void Kitchen::setLimit(t_materialIndex materialIndex, unsigned int limit) ui->kitchen.mat_indices.push_back(materialIndex); ui->kitchen.mat_types.push_back((t_materialType) (limit < seedLimit) ? limit : seedLimit); ui->kitchen.exc_types.push_back(limitExclusion); -}; +} void Kitchen::clearLimits() { @@ -190,9 +190,58 @@ void Kitchen::clearLimits() ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + matchIndex); } } while(match); -}; +} size_t Kitchen::size() { return ui->kitchen.item_types.size(); -}; +} + +int Kitchen::findExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index) +{ + for (size_t i = 0; i < size(); i++) + { + if (ui->kitchen.item_types[i] == item_type && + ui->kitchen.item_subtypes[i] == item_subtype && + ui->kitchen.mat_types[i] == mat_type && + ui->kitchen.mat_indices[i] == mat_index && + ui->kitchen.exc_types[i] == type) + { + return int(i); + } + } + return -1; +} + +bool Kitchen::addExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index) +{ + if (findExclusion(type, item_type, item_subtype, mat_type, mat_index) >= 0) + return false; + + ui->kitchen.item_types.push_back(item_type); + ui->kitchen.item_subtypes.push_back(item_subtype); + ui->kitchen.mat_types.push_back(mat_type); + ui->kitchen.mat_indices.push_back(mat_index); + ui->kitchen.exc_types.push_back(type); + return true; +} + +bool Kitchen::removeExclusion(df::kitchen_exc_type type, + df::item_type item_type, int16_t item_subtype, + int16_t mat_type, int32_t mat_index) +{ + int i = findExclusion(type, item_type, item_subtype, mat_type, mat_index); + if (i < 0) + return false; + + ui->kitchen.item_types.erase(ui->kitchen.item_types.begin() + i); + ui->kitchen.item_subtypes.erase(ui->kitchen.item_subtypes.begin() + i); + ui->kitchen.mat_types.erase(ui->kitchen.mat_types.begin() + i); + ui->kitchen.mat_indices.erase(ui->kitchen.mat_indices.begin() + i); + ui->kitchen.exc_types.erase(ui->kitchen.exc_types.begin() + i); + return true; +} diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index 2ebfd9644..c0a2e4bed 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -33,39 +33,42 @@ distribution. #include using namespace std; -#include "modules/Maps.h" -#include "modules/MapCache.h" #include "ColorText.h" +#include "Core.h" +#include "DataDefs.h" #include "Error.h" -#include "VersionInfo.h" #include "MemAccess.h" -#include "ModuleFactory.h" -#include "Core.h" #include "MiscUtils.h" +#include "ModuleFactory.h" +#include "VersionInfo.h" #include "modules/Buildings.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" #include "modules/Materials.h" -#include "DataDefs.h" -#include "df/world_data.h" -#include "df/world_underground_region.h" -#include "df/world_geo_biome.h" -#include "df/world_geo_layer.h" -#include "df/feature_init.h" -#include "df/world_data.h" -#include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" -#include "df/world_region_details.h" -#include "df/builtin_mats.h" +#include "df/block_square_event_designation_priorityst.h" +#include "df/block_square_event_frozen_liquidst.h" #include "df/block_square_event_grassst.h" -#include "df/z_level_flags.h" -#include "df/region_map_entry.h" +#include "df/building_type.h" +#include "df/builtin_mats.h" +#include "df/burrow.h" +#include "df/feature_init.h" #include "df/flow_info.h" #include "df/plant.h" #include "df/plant_tree_info.h" #include "df/plant_tree_tile.h" -#include "df/building_type.h" +#include "df/region_map_entry.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_region_details.h" +#include "df/world_underground_region.h" +#include "df/z_level_flags.h" using namespace DFHack; using namespace MapExtras; @@ -269,6 +272,43 @@ bool MapExtras::Block::setTiletypeAt(df::coord2d pos, df::tiletype tt, bool forc return true; } +static df::block_square_event_designation_priorityst *getPriorityEvent(df::map_block *block, bool write) +{ + vector events; + Maps::SortBlockEvents(block, 0, 0, 0, 0, 0, 0, 0, &events); + if (events.empty()) + { + if (!write) + return NULL; + + auto event = df::allocate(); + block->block_events.push_back((df::block_square_event*)event); + return event; + } + return events[0]; +} + +int32_t MapExtras::Block::priorityAt(df::coord2d pos) +{ + if (!block) + return false; + + if (auto event = getPriorityEvent(block, false)) + return event->priority[pos.x % 16][pos.y % 16]; + + return 0; +} + +bool MapExtras::Block::setPriorityAt(df::coord2d pos, int32_t priority) +{ + if (!block || priority < 0) + return false; + + auto event = getPriorityEvent(block, true); + event->priority[pos.x % 16][pos.y % 16] = priority; + return true; +} + bool MapExtras::Block::setVeinMaterialAt(df::coord2d pos, int16_t mat, df::inclusion_type type) { using namespace df::enums::tiletype_material; @@ -889,6 +929,7 @@ t_matpair MapExtras::BlockInfo::getBaseMaterial(df::tiletype tt, df::coord2d pos case CONSTRUCTION: // just a fallback case MAGMA: case HFS: + case UNDERWORLD_GATE: // use generic 'rock' break; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 1114107ec..20255dbe3 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -33,36 +33,37 @@ distribution. #include using namespace std; -#include "modules/Maps.h" -#include "modules/MapCache.h" #include "ColorText.h" +#include "Core.h" +#include "DataDefs.h" #include "Error.h" -#include "VersionInfo.h" #include "MemAccess.h" -#include "ModuleFactory.h" -#include "Core.h" #include "MiscUtils.h" +#include "ModuleFactory.h" +#include "VersionInfo.h" #include "modules/Buildings.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" -#include "DataDefs.h" -#include "df/world_data.h" -#include "df/world_underground_region.h" -#include "df/world_geo_biome.h" -#include "df/world_geo_layer.h" -#include "df/feature_init.h" -#include "df/world_data.h" -#include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" -#include "df/world_region_details.h" -#include "df/builtin_mats.h" #include "df/block_square_event_grassst.h" -#include "df/z_level_flags.h" -#include "df/region_map_entry.h" -#include "df/flow_info.h" #include "df/building_type.h" +#include "df/builtin_mats.h" +#include "df/burrow.h" +#include "df/feature_init.h" +#include "df/flow_info.h" #include "df/plant.h" +#include "df/region_map_entry.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_region_details.h" +#include "df/world_underground_region.h" +#include "df/z_level_flags.h" using namespace DFHack; using namespace df::enums; @@ -90,7 +91,7 @@ const char * DFHack::sa_feature(df::feature_type index) return "Cavern"; case feature_type::magma_core_from_layer: return "Magma sea"; - case feature_type::feature_underworld_from_layer: + case feature_type::underworld_from_layer: return "Underworld"; default: return "Unknown/Error"; @@ -165,6 +166,17 @@ bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z) return true; } +bool Maps::isTileVisible(int32_t x, int32_t y, int32_t z) +{ + df::map_block *block = getTileBlock(x, y, z); + if (!block) + return false; + if (block->designation[x % 16][y % 16].bits.hidden) + return false; + + return true; +} + df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) { if (!isValidTilePos(x,y,z)) @@ -194,7 +206,7 @@ df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z) slot->map_pos.z = z; // Assume sky - df::tile_designation dsgn(0); + df::tile_designation dsgn; dsgn.bits.light = true; dsgn.bits.outside = true; @@ -390,7 +402,8 @@ bool Maps::SortBlockEvents(df::map_block *block, vector *grasses, vector *constructions, vector *spoors, - vector *items) + vector *items, + vector *priorities) { if (veins) veins->clear(); @@ -444,6 +457,10 @@ bool Maps::SortBlockEvents(df::map_block *block, if (items) items->push_back((df::block_square_event_item_spatterst *)evt); break; + case block_square_event_type::designation_priority: + if (priorities) + priorities->push_back((df::block_square_event_designation_priorityst *)evt); + break; } } return true; diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 219932657..a8f10d7d2 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -733,7 +733,7 @@ bool Materials::ReadOthers(void) bool Materials::ReadDescriptorColors (void) { - size_t size = world->raws.language.colors.size(); + size_t size = world->raws.descriptors.colors.size(); color.clear(); if(size == 0) @@ -741,7 +741,7 @@ bool Materials::ReadDescriptorColors (void) color.reserve(size); for (size_t i = 0; i < size;i++) { - df::descriptor_color *c = world->raws.language.colors[i]; + df::descriptor_color *c = world->raws.descriptors.colors[i]; t_descriptor_color col; col.id = c->id; col.name = c->name; @@ -751,13 +751,13 @@ bool Materials::ReadDescriptorColors (void) color.push_back(col); } - size = world->raws.language.patterns.size(); + size = world->raws.descriptors.patterns.size(); alldesc.clear(); alldesc.reserve(size); for (size_t i = 0; i < size;i++) { t_matgloss mat; - mat.id = world->raws.language.patterns[i]->id; + mat.id = world->raws.descriptors.patterns[i]->id; alldesc.push_back(mat); } return true; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 71103ffda..6ac39aa05 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -57,6 +57,7 @@ using namespace DFHack; #include "df/job.h" #include "df/building.h" #include "df/renderer.h" +#include "df/plant.h" using namespace df::enums; using df::global::init; @@ -94,8 +95,12 @@ bool Screen::inGraphicsMode() return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS); } -static void doSetTile_default(const Pen &pen, int x, int y, bool map) +static bool doSetTile_default(const Pen &pen, int x, int y, bool map) { + auto dim = Screen::getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) + return false; + int index = ((x * gps->dimy) + y); auto screen = gps->screen + index*4; screen[0] = uint8_t(pen.ch); @@ -107,30 +112,27 @@ static void doSetTile_default(const Pen &pen, int x, int y, bool map) gps->screentexpos_grayscale[index] = (pen.tile_mode == Screen::Pen::TileColor); gps->screentexpos_cf[index] = pen.tile_fg; gps->screentexpos_cbr[index] = pen.tile_bg; + + return true; } GUI_HOOK_DEFINE(Screen::Hooks::set_tile, doSetTile_default); -static void doSetTile(const Pen &pen, int x, int y, bool map) +static bool doSetTile(const Pen &pen, int x, int y, bool map) { - GUI_HOOK_TOP(Screen::Hooks::set_tile)(pen, x, y, map); + return GUI_HOOK_TOP(Screen::Hooks::set_tile)(pen, x, y, map); } bool Screen::paintTile(const Pen &pen, int x, int y, bool map) { if (!gps || !pen.valid()) return false; - auto dim = getWindowSize(); - if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false; - doSetTile(pen, x, y, map); return true; } -Pen Screen::readTile(int x, int y) +static Pen doGetTile_default(int x, int y, bool map) { - if (!gps) return Pen(0,0,0,-1); - - auto dim = getWindowSize(); + auto dim = Screen::getWindowSize(); if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return Pen(0,0,0,-1); @@ -161,6 +163,19 @@ Pen Screen::readTile(int x, int y) return pen; } +GUI_HOOK_DEFINE(Screen::Hooks::get_tile, doGetTile_default); +static Pen doGetTile(int x, int y, bool map) +{ + return GUI_HOOK_TOP(Screen::Hooks::get_tile)(x, y, map); +} + +Pen Screen::readTile(int x, int y, bool map) +{ + if (!gps) return Pen(0,0,0,-1); + + return doGetTile(x, y, map); +} + bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text, bool map) { auto dim = getWindowSize(); @@ -357,12 +372,6 @@ bool Screen::hasActiveScreens(Plugin *plugin) } #ifdef _LINUX -// Link to the libgraphics class directly: -class DFHACK_EXPORT enabler_inputst { - public: - std::string GetKeyDisplay(int binding); -}; - class DFHACK_EXPORT renderer { unsigned char *screen; long *screentexpos; @@ -403,15 +412,6 @@ public: virtual bool get_mouse_coords(int &x, int &y) { return false; } virtual bool uses_opengl(); }; -#else -struct less_sz { - bool operator() (const string &a, const string &b) const { - if (a.size() < b.size()) return true; - if (a.size() > b.size()) return false; - return a < b; - } -}; -static std::map > *keydisplay = NULL; #endif void init_screen_module(Core *core) @@ -420,26 +420,13 @@ void init_screen_module(Core *core) renderer tmp; if (!strict_virtual_cast((virtual_ptr)&tmp)) cerr << "Could not fetch the renderer vtable." << std::endl; -#else - if (!core->vinfo->getAddress("keydisplay", keydisplay)) - keydisplay = NULL; #endif } string Screen::getKeyDisplay(df::interface_key key) { -#ifdef _LINUX - auto enabler = (enabler_inputst*)df::global::enabler; if (enabler) return enabler->GetKeyDisplay(key); -#else - if (keydisplay) - { - auto it = keydisplay->find(key); - if (it != keydisplay->end() && !it->second.empty()) - return *it->second.begin(); - } -#endif return "?"; } @@ -524,8 +511,8 @@ void PenArray::draw(unsigned int x, unsigned int y, unsigned int width, unsigned { for (unsigned int gridy = y; gridy < y + height; gridy++) { - if (gridx >= gps->dimx || - gridy >= gps->dimy || + if (gridx >= unsigned(gps->dimx) || + gridy >= unsigned(gps->dimy) || gridx - x + bufx >= dimx || gridy - y + bufy >= dimy) continue; @@ -934,3 +921,11 @@ df::building *dfhack_lua_viewscreen::getSelectedBuilding() safe_call_lua(do_notify, 1, 1); return Lua::GetDFObject(Lua::Core::State, -1); } + +df::plant *dfhack_lua_viewscreen::getSelectedPlant() +{ + Lua::StackUnwinder frame(Lua::Core::State); + lua_pushstring(Lua::Core::State, "onGetSelectedPlant"); + safe_call_lua(do_notify, 1, 1); + return Lua::GetDFObject(Lua::Core::State, -1); +} diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index a8e4c7452..41e9f2eba 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -42,12 +42,14 @@ using namespace std; // we connect to those #include "modules/Units.h" #include "modules/Items.h" +#include "modules/Maps.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "ModuleFactory.h" #include "Core.h" #include "MiscUtils.h" +#include "df/activity_entry.h" #include "df/burrow.h" #include "df/caste_raw.h" #include "df/creature_raw.h" @@ -70,6 +72,7 @@ using namespace std; #include "df/ui.h" #include "df/unit_inventory_item.h" #include "df/unit_misc_trait.h" +#include "df/unit_relationship_type.h" #include "df/unit_skill.h" #include "df/unit_soul.h" #include "df/unit_wound.h" @@ -81,436 +84,48 @@ using df::global::world; using df::global::ui; using df::global::gamemode; -bool Units::isValid() -{ - return (world->units.all.size() > 0); -} - -int32_t Units::getNumCreatures() +int32_t Units::getNumUnits() { return world->units.all.size(); } -df::unit * Units::GetCreature (const int32_t index) +df::unit *Units::getUnit (const int32_t index) { - if (!isValid()) return NULL; - - // read pointer from vector at position - if(size_t(index) > world->units.all.size()) - return 0; - return world->units.all[index]; + return vector_get(world->units.all, index); } // returns index of creature actually read or -1 if no creature can be found -int32_t Units::GetCreatureInBox (int32_t index, df::unit ** furball, - const uint16_t x1, const uint16_t y1, const uint16_t z1, - const uint16_t x2, const uint16_t y2, const uint16_t z2) +bool Units::getUnitsInBox (std::vector &units, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2) { - if (!isValid()) - return -1; + if (!world) + return false; + + if (x1 > x2) swap(x1, x2); + if (y1 > y2) swap(y1, y2); + if (z1 > z2) swap(z1, z2); - size_t size = world->units.all.size(); - while (size_t(index) < size) + units.clear(); + for (df::unit *u : world->units.all) { - // read pointer from vector at position - df::unit * temp = world->units.all[index]; - if (temp->pos.x >= x1 && temp->pos.x < x2) + if (u->pos.x >= x1 && u->pos.x <= x2) { - if (temp->pos.y >= y1 && temp->pos.y < y2) + if (u->pos.y >= y1 && u->pos.y <= y2) { - if (temp->pos.z >= z1 && temp->pos.z < z2) + if (u->pos.z >= z1 && u->pos.z <= z2) { - *furball = temp; - return index; + units.push_back(u); } } } - index++; - } - *furball = NULL; - return -1; -} - -void Units::CopyCreature(df::unit * source, t_unit & furball) -{ - if(!isValid()) return; - // read pointer from vector at position - furball.origin = source; - - //read creature from memory - // name - Translation::readName(furball.name, &source->name); - - // basic stuff - furball.id = source->id; - furball.x = source->pos.x; - furball.y = source->pos.y; - furball.z = source->pos.z; - furball.race = source->race; - furball.civ = source->civ_id; - furball.sex = source->sex; - furball.caste = source->caste; - furball.flags1.whole = source->flags1.whole; - furball.flags2.whole = source->flags2.whole; - furball.flags3.whole = source->flags3.whole; - // custom profession - furball.custom_profession = source->custom_profession; - // profession - furball.profession = source->profession; - // happiness - furball.happiness = 100;//source->status.happiness; - // physical attributes - memcpy(&furball.strength, source->body.physical_attrs, sizeof(source->body.physical_attrs)); - - // mood stuff - furball.mood = source->mood; - furball.mood_skill = source->job.mood_skill; // FIXME: really? More like currently used skill anyway. - Translation::readName(furball.artifact_name, &source->status.artifact_name); - - // labors - memcpy(&furball.labors, &source->status.labors, sizeof(furball.labors)); - - furball.birth_year = source->relations.birth_year; - furball.birth_time = source->relations.birth_time; - furball.pregnancy_timer = source->relations.pregnancy_timer; - // appearance - furball.nbcolors = source->appearance.colors.size(); - if(furball.nbcolors>MAX_COLORS) - furball.nbcolors = MAX_COLORS; - for(uint32_t i = 0; i < furball.nbcolors; i++) - { - furball.color[i] = source->appearance.colors[i]; - } - - //likes. FIXME: where do they fit in now? The soul? - /* - DfVector likes(d->p, temp + offs.creature_likes_offset); - furball.numLikes = likes.getSize(); - for(uint32_t i = 0;iread(temp2,sizeof(t_like),(uint8_t *) &furball.likes[i]); - } - */ - /* - if(d->Ft_soul) - { - uint32_t soul = p->readDWord(addr_cr + offs.default_soul_offset); - furball.has_default_soul = false; - - if(soul) - { - furball.has_default_soul = true; - // get first soul's skills - DfVector skills(soul + offs.soul_skills_vector_offset); - furball.defaultSoul.numSkills = skills.size(); - - for (uint32_t i = 0; i < furball.defaultSoul.numSkills;i++) - { - uint32_t temp2 = skills[i]; - // a byte: this gives us 256 skills maximum. - furball.defaultSoul.skills[i].id = p->readByte (temp2); - furball.defaultSoul.skills[i].rating = - p->readByte (temp2 + offsetof(t_skill, rating)); - furball.defaultSoul.skills[i].experience = - p->readWord (temp2 + offsetof(t_skill, experience)); - } - - // mental attributes are part of the soul - p->read(soul + offs.soul_mental_offset, - sizeof(t_attrib) * NUM_CREATURE_MENTAL_ATTRIBUTES, - (uint8_t *)&furball.defaultSoul.analytical_ability); - - // traits as well - p->read(soul + offs.soul_traits_offset, - sizeof (uint16_t) * NUM_CREATURE_TRAITS, - (uint8_t *) &furball.defaultSoul.traits); - } - } - */ - if(source->job.current_job == NULL) - { - furball.current_job.active = false; - } - else - { - furball.current_job.active = true; - furball.current_job.jobType = source->job.current_job->job_type; - furball.current_job.jobId = source->job.current_job->id; - } -} - -int32_t Units::FindIndexById(int32_t creature_id) -{ - return df::unit::binsearch_index(world->units.all, creature_id); -} -/* -bool Creatures::WriteLabors(const uint32_t index, uint8_t labors[NUM_CREATURE_LABORS]) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - - p->write(temp + d->creatures.labors_offset, NUM_CREATURE_LABORS, labors); - uint32_t pickup_equip; - p->readDWord(temp + d->creatures.pickup_equipment_bit, pickup_equip); - pickup_equip |= 1u; - p->writeDWord(temp + d->creatures.pickup_equipment_bit, pickup_equip); - return true; -} - -bool Creatures::WriteHappiness(const uint32_t index, const uint32_t happinessValue) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord (temp + d->creatures.happiness_offset, happinessValue); - return true; -} - -bool Creatures::WriteFlags(const uint32_t index, - const uint32_t flags1, - const uint32_t flags2) -{ - if(!d->Started || !d->Ft_basic) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord (temp + d->creatures.flags1_offset, flags1); - p->writeDWord (temp + d->creatures.flags2_offset, flags2); - return true; -} - -bool Creatures::WriteFlags(const uint32_t index, - const uint32_t flags1, - const uint32_t flags2, - const uint32_t flags3) -{ - if(!d->Started || !d->Ft_basic) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord (temp + d->creatures.flags1_offset, flags1); - p->writeDWord (temp + d->creatures.flags2_offset, flags2); - p->writeDWord (temp + d->creatures.flags3_offset, flags3); - return true; -} - -bool Creatures::WriteSkills(const uint32_t index, const t_soul &soul) -{ - if(!d->Started || !d->Ft_soul) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - uint32_t souloff = p->readDWord(temp + d->creatures.default_soul_offset); - - if(!souloff) - { - return false; - } - - DfVector skills(souloff + d->creatures.soul_skills_vector_offset); - - for (uint32_t i=0; iwriteByte(temp2 + offsetof(t_skill, rating), soul.skills[i].rating); - p->writeWord(temp2 + offsetof(t_skill, experience), soul.skills[i].experience); - } - - return true; -} - -bool Creatures::WriteAttributes(const uint32_t index, const t_creature &creature) -{ - if(!d->Started || !d->Ft_advanced || !d->Ft_soul) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - uint32_t souloff = p->readDWord(temp + d->creatures.default_soul_offset); - - if(!souloff) - { - return false; - } - - // physical attributes - p->write(temp + d->creatures.physical_offset, - sizeof(t_attrib) * NUM_CREATURE_PHYSICAL_ATTRIBUTES, - (uint8_t *)&creature.strength); - - // mental attributes are part of the soul - p->write(souloff + d->creatures.soul_mental_offset, - sizeof(t_attrib) * NUM_CREATURE_MENTAL_ATTRIBUTES, - (uint8_t *)&creature.defaultSoul.analytical_ability); - - return true; -} - -bool Creatures::WriteSex(const uint32_t index, const uint8_t sex) -{ - if(!d->Started || !d->Ft_basic ) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeByte (temp + d->creatures.sex_offset, sex); - - return true; -} - -bool Creatures::WriteTraits(const uint32_t index, const t_soul &soul) -{ - if(!d->Started || !d->Ft_soul) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - uint32_t souloff = p->readDWord(temp + d->creatures.default_soul_offset); - - if(!souloff) - { - return false; } - - p->write(souloff + d->creatures.soul_traits_offset, - sizeof (uint16_t) * NUM_CREATURE_TRAITS, - (uint8_t *) &soul.traits); - - return true; -} - -bool Creatures::WriteMood(const uint32_t index, const uint16_t mood) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeWord(temp + d->creatures.mood_offset, mood); - return true; -} - -bool Creatures::WriteMoodSkill(const uint32_t index, const uint16_t moodSkill) -{ - if(!d->Started || !d->Ft_advanced) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeWord(temp + d->creatures.mood_skill_offset, moodSkill); return true; } -bool Creatures::WriteJob(const t_creature * furball, std::vector const& mat) +int32_t Units::findIndexById(int32_t creature_id) { - if(!d->Inited || !d->Ft_job_materials) return false; - if(!furball->current_job.active) return false; - - unsigned int i; - Process * p = d->owner; - Private::t_offsets & off = d->creatures; - DfVector cmats(furball->current_job.occupationPtr + off.job_materials_vector); - - for(i=0;iwriteWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType); - p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].itemSubtype); - p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex); - p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index); - p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags); - } - return true; -} - -bool Creatures::WritePos(const uint32_t index, const t_creature &creature) -{ - if(!d->Started) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->write (temp + d->creatures.pos_offset, 3 * sizeof (uint16_t), (uint8_t *) & (creature.x)); - return true; -} - -bool Creatures::WriteCiv(const uint32_t index, const int32_t civ) -{ - if(!d->Started) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord(temp + d->creatures.civ_offset, civ); - return true; -} - -bool Creatures::WritePregnancy(const uint32_t index, const uint32_t pregTimer) -{ - if(!d->Started) return false; - - uint32_t temp = d->p_cre->at (index); - Process * p = d->owner; - p->writeDWord(temp + d->creatures.pregnancy_offset, pregTimer); - return true; -} -*/ -uint32_t Units::GetDwarfRaceIndex() -{ - return ui->race_id; -} - -int32_t Units::GetDwarfCivId() -{ - return ui->civ_id; -} -/* -bool Creatures::getCurrentCursorCreature(uint32_t & creature_index) -{ - if(!d->cursorWindowInited) return false; - Process * p = d->owner; - creature_index = p->readDWord(d->current_cursor_creature_offset); - return true; -} -*/ -/* -bool Creatures::ReadJob(const t_creature * furball, vector & mat) -{ - unsigned int i; - if(!d->Inited || !d->Ft_job_materials) return false; - if(!furball->current_job.active) return false; - - Process * p = d->owner; - Private::t_offsets & off = d->creatures; - DfVector cmats(furball->current_job.occupationPtr + off.job_materials_vector); - mat.resize(cmats.size()); - for(i=0;ireadWord(cmats[i] + off.job_material_itemtype_o); - mat[i].itemSubtype = p->readWord(cmats[i] + off.job_material_subtype_o); - mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o); - mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o); - mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o); - } - return true; -} -*/ -bool Units::ReadInventoryByIdx(const uint32_t index, std::vector & item) -{ - if(index >= world->units.all.size()) return false; - df::unit * temp = world->units.all[index]; - return ReadInventoryByPtr(temp, item); -} - -bool Units::ReadInventoryByPtr(const df::unit * unit, std::vector & items) -{ - if(!isValid()) return false; - if(!unit) return false; - items.clear(); - for (size_t i = 0; i < unit->inventory.size(); i++) - items.push_back(unit->inventory[i]->item); - return true; -} - -void Units::CopyNameTo(df::unit * creature, df::language_name * target) -{ - Translation::copyName(&creature->name, target); + return df::unit::binsearch_index(world->units.all, creature_id); } df::coord Units::getPosition(df::unit *unit) @@ -912,6 +527,12 @@ bool Units::isOwnRace(df::unit* unit) return unit->race == ui->race_id; } +bool Units::isVisible(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return Maps::isTileVisible(unit->pos); +} + // get race name by id or unit pointer string Units::getRaceNameById(int32_t id) { @@ -1085,10 +706,10 @@ double Units::getAge(df::unit *unit, bool true_age) return -1; double year_ticks = 403200.0; - double birth_time = unit->relations.birth_year + unit->relations.birth_time/year_ticks; + double birth_time = unit->birth_year + unit->birth_time/year_ticks; double cur_time = *cur_year + *cur_year_tick / year_ticks; - if (!true_age && unit->relations.curse_year >= 0) + if (!true_age && unit->curse_year >= 0) { if (auto identity = getIdentity(unit)) { @@ -1453,7 +1074,8 @@ int Units::computeMovementSpeed(df::unit *unit) // Activity state - if (unit->relations.draggee_id != -1) speed += 1000; + if (unit->relationship_ids[df::unit_relationship_type::Draggee] != -1) + speed += 1000; if (unit->flags1.bits.on_ground) speed += 2000; @@ -1522,7 +1144,7 @@ int Units::computeMovementSpeed(df::unit *unit) if (is_adventure) { auto player = vector_get(world->units.active, 0); - if (player && player->id == unit->relations.group_leader_id) + if (player && player->id == unit->relationship_ids[df::unit_relationship_type::GroupLeader]) speed = std::min(speed, computeMovementSpeed(player)); } @@ -1551,14 +1173,14 @@ float Units::computeSlowdownFactor(df::unit *unit) { if (!unit->flags1.bits.marauder && casteFlagSet(unit->race, unit->caste, caste_raw_flags::MEANDERER) && - !(unit->relations.following && isCitizen(unit)) && + !(unit->following && isCitizen(unit)) && linear_index(unit->inventory, &df::unit_inventory_item::mode, df::unit_inventory_item::Hauled) < 0) { coeff *= 4.0f; } - if (unit->relations.group_leader_id < 0 && + if (unit->relationship_ids[df::unit_relationship_type::GroupLeader] < 0 && unit->flags1.bits.active_invader && !unit->job.current_job && !unit->flags3.bits.no_meandering && unit->profession != profession::THIEF && unit->profession != profession::MASTER_THIEF && @@ -1829,6 +1451,24 @@ std::string Units::getSquadName(df::unit *unit) return Translation::TranslateName(&squad->name, true); } +df::activity_entry *Units::getMainSocialActivity(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->social_activities.empty()) + return nullptr; + + return df::activity_entry::find(unit->social_activities[unit->social_activities.size() - 1]); +} + +df::activity_event *Units::getMainSocialEvent(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + df::activity_entry *entry = getMainSocialActivity(unit); + if (!entry || entry->events.empty()) + return nullptr; + return entry->events[entry->events.size() - 1]; +} + bool Units::isMerchant(df::unit* unit) { CHECK_NULL_POINTER(unit); diff --git a/library/proto/tmp/.gitignore b/library/proto/tmp/.gitignore new file mode 100644 index 000000000..75feca5b1 --- /dev/null +++ b/library/proto/tmp/.gitignore @@ -0,0 +1 @@ +*.pb.* diff --git a/library/xml b/library/xml index c2b1a540b..c3025feb8 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c2b1a540b026555944f3ab60cef37dd6a4b2fe5c +Subproject commit c3025feb80c6f8e7441ea5dcf4f463a9cf89cbbd diff --git a/onLoad.init-example b/onLoad.init-example new file mode 100644 index 000000000..1d32f07c9 --- /dev/null +++ b/onLoad.init-example @@ -0,0 +1 @@ +repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 5a04bd591..445b600f7 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -4,14 +4,15 @@ cd "${PWD}" #thanks to Iriel for figuring this out OSREV=`uname -r | cut -d. -f1` if [ "$OSREV" -ge 11 ] ; then - export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs - export DYLD_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs + export DYLD_LIBRARY_PATH="./hack:./libs:./hack/libs" + export DYLD_FRAMEWORK_PATH="./hack:./libs:./hack/libs" else - export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs - export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs + export DYLD_FALLBACK_LIBRARY_PATH="./hack:./libs:./hack/libs" + export DYLD_FALLBACK_FRAMEWORK_PATH="./hack:./libs:./hack/libs" fi old_tty_settings=$(stty -g) DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" stty "$old_tty_settings" +tput sgr0 echo "" diff --git a/package/darwin/libstdc++.6.dylib.bz2 b/package/darwin/libstdc++.6.dylib.bz2 deleted file mode 100644 index a25b10b26..000000000 Binary files a/package/darwin/libstdc++.6.dylib.bz2 and /dev/null differ diff --git a/package/darwin/osx32-gcc7/.gitignore b/package/darwin/osx32-gcc7/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx32-gcc7/.gitignore @@ -0,0 +1 @@ +libstdc++* diff --git a/package/darwin/osx32/.gitignore b/package/darwin/osx32/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx32/.gitignore @@ -0,0 +1 @@ +libstdc++* diff --git a/package/darwin/osx64-gcc7/.gitignore b/package/darwin/osx64-gcc7/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx64-gcc7/.gitignore @@ -0,0 +1 @@ +libstdc++* diff --git a/package/darwin/osx64/.gitignore b/package/darwin/osx64/.gitignore new file mode 100644 index 000000000..e67764bf4 --- /dev/null +++ b/package/darwin/osx64/.gitignore @@ -0,0 +1 @@ +libstdc++* diff --git a/package/linux/dfhack b/package/linux/dfhack index 2b2d04465..011df22eb 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -59,10 +59,12 @@ fi # Now run -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack" +export LD_LIBRARY_PATH="./hack/libs:./hack:$LD_LIBRARY_PATH" PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}./hack/libdfhack.so" +setarch_arch=$(cat hack/dfhack_setarch.txt || printf i386) + case "$1" in -g | --gdb) shift @@ -74,27 +76,28 @@ case "$1" in ;; -h | --helgrind) shift - LD_PRELOAD="$PRELOAD_LIB" setarch i386 -R valgrind $DF_HELGRIND_OPTS --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress "$@" + LD_PRELOAD="$PRELOAD_LIB" setarch "$setarch_arch" -R valgrind $DF_HELGRIND_OPTS --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress "$@" ret=$? ;; -v | --valgrind) shift - LD_PRELOAD="$PRELOAD_LIB" setarch i386 -R valgrind $DF_VALGRIND_OPTS --log-file=valgrind.log ./libs/Dwarf_Fortress "$@" + LD_PRELOAD="$PRELOAD_LIB" setarch "$setarch_arch" -R valgrind $DF_VALGRIND_OPTS --log-file=valgrind.log ./libs/Dwarf_Fortress "$@" ret=$? ;; -c | --callgrind) shift - LD_PRELOAD="$PRELOAD_LIB" setarch i386 -R valgrind $DF_CALLGRIND_OPTS --tool=callgrind --separate-threads=yes --dump-instr=yes --instr-atstart=no --log-file=callgrind.log ./libs/Dwarf_Fortress "$@" + LD_PRELOAD="$PRELOAD_LIB" setarch "$setarch_arch" -R valgrind $DF_CALLGRIND_OPTS --tool=callgrind --separate-threads=yes --dump-instr=yes --instr-atstart=no --log-file=callgrind.log ./libs/Dwarf_Fortress "$@" ret=$? ;; *) - setarch i386 -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" + setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" ret=$? ;; esac # Restore previous terminal settings stty "$old_tty_settings" +tput sgr0 echo if [ -n "$DF_POST_CMD" ]; then diff --git a/package/windows/SDLreal.dll b/package/windows/SDLreal.dll deleted file mode 100644 index 3ce97a59d..000000000 Binary files a/package/windows/SDLreal.dll and /dev/null differ diff --git a/package/windows/win32/.gitignore b/package/windows/win32/.gitignore new file mode 100644 index 000000000..6a7461313 --- /dev/null +++ b/package/windows/win32/.gitignore @@ -0,0 +1 @@ +*.dll diff --git a/package/windows/win64/.gitignore b/package/windows/win64/.gitignore new file mode 100644 index 000000000..6a7461313 --- /dev/null +++ b/package/windows/win64/.gitignore @@ -0,0 +1 @@ +*.dll diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 3e51e6582..0bd2cd6c1 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "Core.h" #include "Console.h" diff --git a/plugins/Brushes.h b/plugins/Brushes.h index 99e9e4125..6095a03da 100644 --- a/plugins/Brushes.h +++ b/plugins/Brushes.h @@ -130,7 +130,7 @@ public: while (mc.testCoord(start)) { df::tiletype tt = mc.tiletypeAt(start); - if(DFHack::LowPassable(tt) || juststarted && DFHack::HighPassable(tt)) + if(DFHack::LowPassable(tt) || (juststarted && DFHack::HighPassable(tt))) { v.push_back(start); juststarted = false; @@ -152,10 +152,11 @@ public: class FloodBrush : public Brush { public: - FloodBrush(Core *c){c_ = c;}; + FloodBrush(DFHack::Core *c){c_ = c;}; ~FloodBrush(){}; coord_vec points(MapExtras::MapCache & mc, DFHack::DFCoord start) { + using namespace DFHack; coord_vec v; std::stack to_flood; @@ -198,19 +199,21 @@ public: return "flood"; } private: - void maybeFlood(DFCoord c, std::stack &to_flood, MapExtras::MapCache &mc) { + void maybeFlood(DFHack::DFCoord c, std::stack &to_flood, + MapExtras::MapCache &mc) { if (mc.testCoord(c)) { to_flood.push(c); } } - Core *c_; + DFHack::Core *c_; }; -command_result parseRectangle(color_ostream & out, +DFHack::command_result parseRectangle(DFHack::color_ostream & out, vector & input, int start, int end, int & width, int & height, int & zLevels, bool hasConsole = true) { + using namespace DFHack; int newWidth = 0, newHeight = 0, newZLevels = 0; if (end > start + 1) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index cc0de019f..e8e1b6bc8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -44,16 +44,33 @@ FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}") STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}") +STRING(REPLACE "/proto/" "/proto/tmp/" PROJECT_PROTO_TMP_FILES "${PROJECT_PROTO_SRCS};${PROJECT_PROTO_HDRS}") +SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + PROPERTIES GENERATED TRUE) + +# Force a re-gen if any *.pb.* files are missing +# (only runs when cmake is run, but better than nothing) +FOREACH(file IN LISTS PROJECT_PROTO_SRCS PROJECT_PROTO_HDRS) + IF(NOT EXISTS ${file}) + # MESSAGE("Resetting generate_proto because '${file}' is missing") + FILE(REMOVE ${PROJECT_PROTO_TMP_FILES}) + BREAK() + ENDIF() +ENDFOREACH() ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} + OUTPUT ${PROJECT_PROTO_TMP_FILES} COMMAND protoc-bin -I=${dfhack_SOURCE_DIR}/library/proto/ -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ - --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ + --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ ${PROJECT_PROTOS} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl + ${PROJECT_PROTO_TMP_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ + COMMENT "Generating plugin protobufs" DEPENDS protoc-bin ${PROJECT_PROTOS} ) -add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS}) +ADD_CUSTOM_TARGET(generate_proto DEPENDS ${PROJECT_PROTO_TMP_FILES}) SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) @@ -65,20 +82,19 @@ OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON) if (BUILD_SUPPORTED) DFHACK_PLUGIN(3dveins 3dveins.cpp) DFHACK_PLUGIN(add-spatter add-spatter.cpp) -# DFHACK_PLUGIN(advtools advtools.cpp) + #DFHACK_PLUGIN(advtools advtools.cpp) DFHACK_PLUGIN(autochop autochop.cpp) DFHACK_PLUGIN(autodump autodump.cpp) - DFHACK_PLUGIN(autogems autogems.cpp) + DFHACK_PLUGIN(autogems autogems.cpp LINK_LIBRARIES jsoncpp) DFHACK_PLUGIN(autohauler autohauler.cpp) DFHACK_PLUGIN(autolabor autolabor.cpp) DFHACK_PLUGIN(automaterial automaterial.cpp) DFHACK_PLUGIN(automelt automelt.cpp) DFHACK_PLUGIN(autotrade autotrade.cpp) - DFHACK_PLUGIN(blueprint blueprint.cpp) + DFHACK_PLUGIN(blueprint blueprint.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(buildingplan buildingplan.cpp LINK_LIBRARIES buildingplan-lib) - DFHACK_PLUGIN(catsplosion catsplosion.cpp) DFHACK_PLUGIN(changeitem changeitem.cpp) DFHACK_PLUGIN(changelayer changelayer.cpp) DFHACK_PLUGIN(changevein changevein.cpp) @@ -93,7 +109,9 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) + DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) + add_subdirectory(embark-assistant) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) @@ -105,25 +123,29 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib) + DFHACK_PLUGIN(generated-creature-renamer generated-creature-renamer.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(hotkeys hotkeys.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) DFHACK_PLUGIN(jobutils jobutils.cpp) + add_subdirectory(labormanager) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) + DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) - #DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp) + DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(plants plants.cpp) DFHACK_PLUGIN(probe probe.cpp) DFHACK_PLUGIN(prospector prospector.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(regrass regrass.cpp) - DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp PROTOBUFS RemoteFortressReader) + add_subdirectory(remotefortressreader) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) DFHACK_PLUGIN(resume resume.cpp) @@ -139,9 +161,9 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) + DFHACK_PLUGIN(title-folder title-folder.cpp) DFHACK_PLUGIN(title-version title-version.cpp) DFHACK_PLUGIN(trackstop trackstop.cpp) -# DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp) add_subdirectory(tweak) DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index c801570db..905752219 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -1,8 +1,20 @@ IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall") - SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -m32 -std=c++0x") - SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32") + add_definitions(-DLINUX_BUILD) + SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall") + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -std=c++0x") + SET(CMAKE_C_FLAGS "-fvisibility=hidden") + IF(DFHACK_BUILD_64) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -mno-avx") + ELSE() + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") + ENDIF() + IF(NOT APPLE) + # Linux: Check for unresolved symbols at link time + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-z,defs") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-z,defs") + ENDIF() ENDIF() include_directories("${dfhack_SOURCE_DIR}/library/include") @@ -66,41 +78,79 @@ MACRO(DFHACK_PLUGIN) CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS}) CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS}) - SET(PLUGIN_PROTOCPP) + SET(PLUGIN_PROTOS) FOREACH(pbuf ${PLUGIN_PROTOBUFS}) - SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) - SET(PLUGIN_PROTOCPP ${PLUGIN_PROTOCPP} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) + LIST(APPEND PLUGIN_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.proto) ENDFOREACH() - # Tell CMake the source won't be available until build time. - SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES GENERATED 1) + LIST(LENGTH PLUGIN_PROTOS NUM_PROTO) + IF(NUM_PROTO) + STRING(REPLACE ".proto" ".pb.cc" PLUGIN_PROTO_SRCS "${PLUGIN_PROTOS}") + STRING(REPLACE ".proto" ".pb.h" PLUGIN_PROTO_HDRS "${PLUGIN_PROTOS}") + STRING(REPLACE "/proto/" "/proto/tmp/" PLUGIN_PROTO_TMP_FILES "${PLUGIN_PROTO_SRCS};${PLUGIN_PROTO_HDRS}") + SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTO_SRCS} ${PLUGIN_PROTO_HDRS} PROPERTIES GENERATED TRUE) + + # Force a re-gen if any *.pb.* files are missing + # (only runs when cmake is run, but better than nothing) + FOREACH(file IN LISTS PLUGIN_PROTO_SRCS PLUGIN_PROTO_HDRS) + IF(NOT EXISTS ${file}) + # MESSAGE("Resetting generate_proto_${PLUGIN_NAME} because '${file}' is missing") + FILE(REMOVE ${PLUGIN_PROTO_TMP_FILES}) + BREAK() + ENDIF() + ENDFOREACH() + + ADD_CUSTOM_COMMAND( + OUTPUT ${PLUGIN_PROTO_TMP_FILES} + COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ + --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/tmp/ + ${PLUGIN_PROTOS} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/depends/copy-if-different.pl + ${PLUGIN_PROTO_TMP_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ + COMMENT "Generating plugin ${PLUGIN_NAME} protobufs" + DEPENDS protoc-bin ${PLUGIN_PROTOS} + ) - ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES}) - IDE_FOLDER(${PLUGIN_NAME} "Plugins") + IF(UNIX) + SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTO_SRCS} PROPERTIES COMPILE_FLAGS "-Wno-misleading-indentation") + ENDIF() - ADD_DEPENDENCIES(${PLUGIN_NAME} dfhack-version) + ADD_CUSTOM_TARGET(generate_proto_${PLUGIN_NAME} DEPENDS ${PLUGIN_PROTO_TMP_FILES}) - # Make sure the source is generated before the executable builds. - ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto) + # Merge headers into sources + SET_SOURCE_FILES_PROPERTIES( ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE ) + LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_HDRS}) + LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_SRCS}) - LIST(LENGTH PLUGIN_PROTOBUFS NUM_PROTO) - IF(NUM_PROTO) - TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite dfhack-version ${PLUGIN_LINK_LIBRARIES}) IF(UNIX) - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "-include Export.h") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} -include Export.h") ELSE() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} /FI\"Export.h\"") ENDIF() + ENDIF() + + ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES}) + IDE_FOLDER(${PLUGIN_NAME} "Plugins") + + IF(NUM_PROTO) + ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto_${PLUGIN_NAME}) + TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite dfhack-version ${PLUGIN_LINK_LIBRARIES}) ELSE() TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) ENDIF() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}") + ADD_DEPENDENCIES(${PLUGIN_NAME} dfhack-version) + + # Make sure the source is generated before the executable builds. + ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto) + IF(UNIX) - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_GCC}") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} ${PLUGIN_COMPILE_FLAGS_GCC}") ELSE() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_MSVC}") + SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} ${PLUGIN_COMPILE_FLAGS_MSVC}") ENDIF() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}") IF(APPLE) SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "") diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 45d636d7c..3d2d89dd1 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -249,7 +249,7 @@ struct product_hook : improvement_product { std::vector *in_reag, std::vector *in_items, int32_t quantity, df::job_skill skill, - df::historical_entity *entity, df::world_site *site) + df::historical_entity *entity, int32_t unk, df::world_site *site, void* unk2) ) { if (auto product = products[this]) { @@ -295,7 +295,7 @@ struct product_hook : improvement_product { return; } - INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, site); + INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, unk, site, unk2); } }; @@ -356,7 +356,7 @@ static bool find_reactions(color_ostream &out) reactions.clear(); products.clear(); - auto &rlist = world->raws.reactions; + auto &rlist = df::reaction::get_vector(); for (size_t i = 0; i < rlist.size(); i++) { diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 77cc02934..50868560d 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -17,6 +17,7 @@ #include "df/item.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/unit_relationship_type.h" #include "df/map_block.h" #include "df/nemesis_record.h" #include "df/historical_figure.h" @@ -139,7 +140,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) switch (ui_advmode->menu) { case Travel: - case Sleep: + // was also Sleep, now equivalent revert = true; break; default: @@ -218,7 +219,7 @@ df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) out.print("Returned into the body of %s.\n", name.c_str()); } - real_nemesis->unit->relations.group_leader_id = -1; + real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; in_transient_swap = false; } @@ -237,7 +238,7 @@ void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_ // Update follow new_nemesis->group_leader_id = -1; - new_nemesis->unit->relations.group_leader_id = -1; + new_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; for (unsigned i = 0; i < cvec.size(); i++) { @@ -247,7 +248,7 @@ void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_ nm->group_leader_id = new_nemesis->id; if (nm->unit) - nm->unit->relations.group_leader_id = new_nemesis->unit_id; + nm->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; } } @@ -721,7 +722,7 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par // Make the player unit follow around to avoid bad consequences // if it is unloaded before the transient swap is reverted. - real_nemesis->unit->relations.group_leader_id = new_nemesis->unit_id; + real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; } return CR_OK; diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 73a3da24e..858989a97 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -10,24 +10,27 @@ #include "DataDefs.h" #include "TileTypes.h" -#include "df/world.h" -#include "df/map_block.h" -#include "df/tile_dig_designation.h" -#include "df/plant_raw.h" -#include "df/plant.h" -#include "df/ui.h" #include "df/burrow.h" -#include "df/item_flags.h" #include "df/item.h" +#include "df/item_flags.h" #include "df/items_other_id.h" +#include "df/job.h" +#include "df/map_block.h" +#include "df/material.h" +#include "df/plant.h" +#include "df/plant_raw.h" +#include "df/tile_dig_designation.h" +#include "df/ui.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" -#include "modules/Screen.h" -#include "modules/Maps.h" #include "modules/Burrows.h" -#include "modules/World.h" -#include "modules/MapCache.h" +#include "modules/Designations.h" #include "modules/Gui.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "modules/World.h" #include @@ -46,6 +49,26 @@ static bool autochop_enabled = false; static int min_logs, max_logs; static const int LOG_CAP_MAX = 99999; static bool wait_for_threshold; +struct Skip { + bool fruit_trees; + bool food_trees; + bool cook_trees; + operator int() { + return (fruit_trees ? 1 : 0) | + (food_trees ? 2 : 0) | + (cook_trees ? 4 : 0); + } + Skip &operator= (int in) { + // set all fields to false if they haven't been set in this save yet + if (in < 0) + in = 0; + fruit_trees = (in & 1); + food_trees = (in & 2); + cook_trees = (in & 4); + return *this; + } +}; +static Skip skip; static PersistentDataItem config_autochop; @@ -177,6 +200,7 @@ static void save_config() config_autochop.ival(1) = min_logs; config_autochop.ival(2) = max_logs; config_autochop.ival(3) = wait_for_threshold; + config_autochop.ival(4) = skip; } static void initialize() @@ -186,6 +210,7 @@ static void initialize() min_logs = 80; max_logs = 100; wait_for_threshold = false; + skip = 0; config_autochop = World::GetPersistentData("autochop/config"); if (config_autochop.isValid()) @@ -195,6 +220,7 @@ static void initialize() min_logs = config_autochop.ival(1); max_logs = config_autochop.ival(2); wait_for_threshold = config_autochop.ival(3); + skip = config_autochop.ival(4); } else { @@ -204,62 +230,115 @@ static void initialize() } } -static int do_chop_designation(bool chop, bool count_only) +static bool skip_plant(const df::plant * plant, bool *restricted) +{ + if (restricted) + *restricted = false; + + // Skip all non-trees immediately. + if (plant->flags.bits.is_shrub) + return true; + + // Skip plants with invalid tile. + df::map_block *cur = Maps::getTileBlock(plant->pos); + if (!cur) + return true; + + int x = plant->pos.x % 16; + int y = plant->pos.y % 16; + + // Skip all unrevealed plants. + if (cur->designation[x][y].bits.hidden) + return true; + + df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); + if (material != tiletype_material::TREE) + return true; + + const df::plant_raw *plant_raw = df::plant_raw::find(plant->material); + + // Skip fruit trees if set. + if (skip.fruit_trees && plant_raw->material_defs.type_drink != -1) + { + if (restricted) + *restricted = true; + return true; + } + + if (skip.food_trees || skip.cook_trees) + { + for (df::material * mat : plant_raw->material) + { + if (skip.food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW)) + { + if (restricted) + *restricted = true; + return true; + } + + if (skip.cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED)) + { + if (restricted) + *restricted = true; + return true; + } + } + } + + return false; +} + +static int do_chop_designation(bool chop, bool count_only, int *skipped = nullptr) { int count = 0; + if (skipped) + { + *skipped = 0; + } for (size_t i = 0; i < world->plants.all.size(); i++) { const df::plant *plant = world->plants.all[i]; - df::map_block *cur = Maps::getTileBlock(plant->pos); - if (!cur) - continue; - int x = plant->pos.x % 16; - int y = plant->pos.y % 16; - - if (plant->flags.bits.is_shrub) - continue; - if (cur->designation[x][y].bits.hidden) - continue; - df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); - if (material != tiletype_material::TREE) + bool restricted = false; + if (skip_plant(plant, &restricted)) + { + if (restricted && skipped) + { + ++*skipped; + } continue; + } if (!count_only && !watchedBurrows.isValidPos(plant->pos)) continue; - bool dirty = false; - if (chop && cur->designation[x][y].bits.dig == tile_dig_designation::No) + if (chop && !Designations::isPlantMarked(plant)) { if (count_only) { - ++count; + if (Designations::canMarkPlant(plant)) + count++; } else { - cur->designation[x][y].bits.dig = tile_dig_designation::Default; - dirty = true; + if (Designations::markPlant(plant)) + count++; } } - if (!chop && cur->designation[x][y].bits.dig == tile_dig_designation::Default) + if (!chop && Designations::isPlantMarked(plant)) { if (count_only) { - ++count; + if (Designations::canUnmarkPlant(plant)) + count++; } else { - cur->designation[x][y].bits.dig = tile_dig_designation::No; - dirty = true; + if (Designations::unmarkPlant(plant)) + count++; } } - - if (dirty) - { - cur->flags.bits.designated = true; - ++count; - } } return count; @@ -370,7 +449,11 @@ static void do_autochop() class ViewscreenAutochop : public dfhack_viewscreen { public: - ViewscreenAutochop() + ViewscreenAutochop(): + selected_column(0), + current_log_count(0), + marked_tree_count(0), + skipped_tree_count(0) { edit_mode = EDIT_NONE; burrows_column.multiselect = true; @@ -390,10 +473,12 @@ public: auto last_selected_index = burrows_column.highlighted_index; burrows_column.clear(); - for (auto iter = ui->burrows.list.begin(); iter != ui->burrows.list.end(); iter++) + for (df::burrow *burrow : ui->burrows.list) { - df::burrow* burrow = *iter; - auto elem = ListEntry(burrow->name, burrow); + string name = burrow->name; + if (name.empty()) + name = "Burrow " + int_to_string(burrow->id + 1); + auto elem = ListEntry(name, burrow); elem.selected = watchedBurrows.isBurrowWatched(burrow); burrows_column.add(elem); } @@ -402,7 +487,7 @@ public: burrows_column.filterDisplay(); current_log_count = get_log_count(); - marked_tree_count = do_chop_designation(false, true); + marked_tree_count = do_chop_designation(false, true, &skipped_tree_count); } void change_min_logs(int delta) @@ -502,13 +587,21 @@ public: { int count = do_chop_designation(true, false); message = "Trees marked for chop: " + int_to_string(count); - marked_tree_count = do_chop_designation(false, true); + marked_tree_count = do_chop_designation(false, true, &skipped_tree_count); + if (skipped_tree_count) + { + message += ", skipped: " + int_to_string(skipped_tree_count); + } } else if (input->count(interface_key::CUSTOM_U)) { int count = do_chop_designation(false, false); message = "Trees unmarked: " + int_to_string(count); - marked_tree_count = do_chop_designation(false, true); + marked_tree_count = do_chop_designation(false, true, &skipped_tree_count); + if (skipped_tree_count) + { + message += ", skipped: " + int_to_string(skipped_tree_count); + } } else if (input->count(interface_key::CUSTOM_N)) { @@ -555,6 +648,18 @@ public: { change_max_logs(10); } + else if (input->count(interface_key::CUSTOM_F)) + { + skip.fruit_trees = !skip.fruit_trees; + } + else if (input->count(interface_key::CUSTOM_E)) + { + skip.food_trees = !skip.food_trees; + } + else if (input->count(interface_key::CUSTOM_C)) + { + skip.cook_trees = !skip.cook_trees; + } else if (enabler->tracking_on && enabler->mouse_lbut) { if (burrows_column.setHighlightByMouse()) @@ -590,6 +695,7 @@ public: if (burrows_column.getSelectedElems().size() > 0) { OutputString(COLOR_GREEN, x, y, "Will chop in selected burrows", true, left_margin); + ++y; } else { @@ -598,13 +704,14 @@ public: } ++y; - OutputToggleString(x, y, "Autochop", "a", autochop_enabled, true, left_margin); - OutputHotkeyString(x, y, "Designate Now", "d", true, left_margin); - OutputHotkeyString(x, y, "Undesignate Now", "u", true, left_margin); + + using namespace df::enums::interface_key; + OutputToggleString(x, y, "Autochop", CUSTOM_A, autochop_enabled, true, left_margin); + OutputHotkeyString(x, y, "Designate Now", CUSTOM_D, true, left_margin); + OutputHotkeyString(x, y, "Undesignate Now", CUSTOM_U, true, left_margin); OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin); if (autochop_enabled) { - using namespace df::enums::interface_key; const struct { const char *caption; int count; @@ -615,7 +722,7 @@ public: {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}}, {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}} }; - for (size_t i = 0; i < sizeof(rows)/sizeof(rows[0]); ++i) + for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); ++i) { auto row = rows[i]; OutputHotkeyString(x, y, row.caption, row.key); @@ -629,13 +736,16 @@ public: if (edit_mode == EDIT_NONE) { x = std::max(x, prev_x + 10); - for (size_t j = 0; j < sizeof(row.skeys)/sizeof(row.skeys[0]); ++j) + for (size_t j = 0; j < sizeof(row.skeys) / sizeof(row.skeys[0]); ++j) OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j])); OutputString(COLOR_WHITE, x, y, ": Step"); } OutputString(COLOR_WHITE, x, y, "", true, left_margin); } OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); + OutputToggleString(x, y, "Skip Fruit Trees", CUSTOM_F, skip.fruit_trees, true, left_margin); + OutputToggleString(x, y, "Skip Edible Product Trees", CUSTOM_E, skip.food_trees, true, left_margin); + OutputToggleString(x, y, "Skip Cookable Product Trees", CUSTOM_C, skip.cook_trees, true, left_margin); } ++y; @@ -660,6 +770,7 @@ private: int selected_column; int current_log_count; int marked_tree_count; + int skipped_tree_count; MapExtras::MapCache mcache; string message; enum { EDIT_NONE, EDIT_MIN, EDIT_MAX } edit_mode; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 3fbe1ae77..b70c7f83c 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -181,6 +181,9 @@ struct dump_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index 9bdfcb02c..ee255846d 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -4,9 +4,12 @@ * For best effect, include "enable autogems" in your dfhack.init configuration. */ +#include + #include "uicommon.h" #include "modules/Buildings.h" +#include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/Job.h" #include "modules/World.h" @@ -19,6 +22,8 @@ #include "df/job_item.h" #include "df/viewscreen_dwarfmodest.h" +#include "jsoncpp-ex.h" + #define CONFIG_KEY "autogems/config" #define DELTA_TICKS 1200 #define MAX_WORKSHOP_JOBS 10 @@ -50,6 +55,7 @@ const char *usage = ( "While this option is enabled, jobs will be created in Jeweler's Workshops\n" "to cut any accessible rough gems.\n" ); +std::set blacklist; void add_task(mat_index gem_type, df::building_workshopst *workshop) { // Create a single task in the specified workshop. @@ -121,6 +127,7 @@ bool valid_gem(df::item* item) { if (item->flags.bits.construction) return false; if (item->flags.bits.garbage_collect) return false; if (item->flags.bits.in_building) return false; + if (blacklist.count(item->getMaterialIndex())) return false; return true; } @@ -134,7 +141,7 @@ void create_jobs() { for (auto w = workshops->begin(); w != workshops->end(); ++w) { auto workshop = virtual_cast(*w); - auto links = workshop->links.take_from_pile; + auto links = workshop->profile.links.take_from_pile; if (workshop->construction_stage < 3) { // Construction in progress. @@ -245,6 +252,8 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { running = !running; return true; + } else if (input->count(interface_key::CUSTOM_SHIFT_G)) { + Core::getInstance().setHotkeyCmd("gui/autogems"); } return false; @@ -269,7 +278,11 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { } if (pen.valid()) { - OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), + interface_key::CUSTOM_G, false, x, COLOR_WHITE, COLOR_LIGHTRED); + x += running ? 5 : 2; + OutputHotkeyString(x, y, "Opts", interface_key::CUSTOM_SHIFT_G, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); } } } @@ -278,6 +291,44 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render); +bool read_config(color_ostream &out) { + std::string path = "data/save/" + World::ReadWorldFolder() + "/autogems.json"; + if (!Filesystem::isfile(path)) { + // no need to require the config file to exist + return true; + } + + std::ifstream f(path); + Json::Value config; + try { + if (!f.good() || !(f >> config)) { + out.printerr("autogems: failed to read autogems.json\n"); + return false; + } + } + catch (Json::Exception &e) { + out.printerr("autogems: failed to read autogems.json: %s\n", e.what()); + return false; + } + + if (config["blacklist"].isArray()) { + blacklist.clear(); + for (int i = 0; i < int(config["blacklist"].size()); i++) { + Json::Value item = config["blacklist"][i]; + if (item.isInt()) { + blacklist.insert(mat_index(item.asInt())); + } + else { + out.printerr("autogems: illegal item at position %i in blacklist\n", i); + } + } + } + return true; +} + +command_result cmd_reload_config(color_ostream &out, std::vector&) { + return read_config(out) ? CR_OK : CR_FAILURE; +} DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_MAP_LOADED) { @@ -286,6 +337,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan auto config = World::GetPersistentData(CONFIG_KEY); running = config.isValid() && !config.ival(0); last_frame_count = world->frame_counter; + read_config(out); } } else if (event == DFHack::SC_MAP_UNLOADED) { running = false; @@ -305,10 +357,20 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { } running = enabled && World::isFortressMode(); + if (running) { + read_config(out); + } return CR_OK; } DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + "autogems-reload", + "Reload autogems config file", + cmd_reload_config, + false, + "Reload autogems config file" + )); return CR_OK; } diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index d7b233195..c43ec110e 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -397,7 +397,16 @@ static const dwarf_state dwarf_states[] = { BUSY /* PushTrackVehicle */, BUSY /* PlaceTrackVehicle */, BUSY /* StoreItemInVehicle */, - BUSY /* GeldAnimal */ + BUSY /* GeldAnimal */, + BUSY /* MakeFigurine */, + BUSY /* MakeAmulet */, + BUSY /* MakeScepter */, + BUSY /* MakeCrown */, + BUSY /* MakeRing */, + BUSY /* MakeEarring */, + BUSY /* MakeBracelet */, + BUSY /* MakeGem */, + BUSY /* PutItemOnDisplay */, }; // Mode assigned to labors. Either it's a hauling job, or it's not. @@ -615,7 +624,7 @@ static void init_state() df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autohauler/labors/")).c_str()); // Ensure that the labor is defined in the existing list - if (labor >= 0 && labor <= labor_infos.size()) + if (labor >= 0 && size_t(labor) < labor_infos.size()) { // Link the labor treatment with the associated persistent data item labor_infos[labor].set_config(*p); @@ -626,7 +635,7 @@ static void init_state() } // Add default labors for those not in save - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { // Determine if the labor is already present. If so, exit the for loop if (labor_infos[i].config.isValid()) @@ -797,7 +806,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // Scan the world and look for any citizens in the player's civilization. // Add these to the list of dwarves. // xxx Does it need to be ++i? - for (int i = 0; i < world->units.active.size(); ++i) + for (size_t i = 0; i < world->units.active.size(); ++i) { df::unit* cre = world->units.active[i]; if (Units::isCitizen(cre)) @@ -886,7 +895,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else { int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) dwarf_info[dwarf].state = dwarf_states[job]; else { @@ -951,7 +960,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) continue; // For every dwarf... - for(int dwarf = 0; dwarf < dwarfs.size(); dwarf++) + for(size_t dwarf = 0; dwarf < dwarfs.size(); dwarf++) { if (!Units::isValidLabor(dwarfs[dwarf], labor)) continue; @@ -1129,7 +1138,7 @@ command_result autohauler (color_ostream &out, std::vector & param return CR_FAILURE; } - for (int i = 0; i < labor_infos.size(); i++) + for (size_t i = 0; i < labor_infos.size(); i++) { reset_labor((df::unit_labor) i); } diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index b8c89215e..936ce4022 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -373,7 +373,8 @@ static const dwarf_state dwarf_states[] = { BUSY /* MakeRing */, BUSY /* MakeEarring */, BUSY /* MakeBracelet */, - BUSY /* MakeGem */ + BUSY /* MakeGem */, + BUSY /* PutItemOnDisplay */, }; struct labor_info @@ -605,7 +606,7 @@ static void init_state() { string key = p->key(); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); - if (labor >= 0 && labor <= labor_infos.size()) + if (labor >= 0 && size_t(labor) < labor_infos.size()) { labor_infos[labor].config = *p; labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; @@ -614,7 +615,7 @@ static void init_state() } // Add default labors for those not in save - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { if (labor_infos[i].config.isValid()) continue; @@ -959,7 +960,7 @@ static void assign_labor(unit_labor::unit_labor labor, * Military and children/nobles will not have labors assigned. * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. */ - for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) + for (size_t i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) { int dwarf = candidates[i]; @@ -1047,7 +1048,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) bool has_fishery = false; bool trader_requested = false; - for (int i = 0; i < world->buildings.all.size(); ++i) + for (size_t i = 0; i < world->buildings.all.size(); ++i) { df::building *build = world->buildings.all[i]; auto type = build->getType(); @@ -1073,7 +1074,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) } } - for (int i = 0; i < world->units.active.size(); ++i) + for (size_t i = 0; i < world->units.active.size(); ++i) { df::unit* cre = world->units.active[i]; if (Units::isCitizen(cre)) @@ -1104,7 +1105,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); if(hf!=NULL) //can be NULL. E.g. script created citizens - for (int i = 0; i < hf->entity_links.size(); i++) + for (size_t i = 0; i < hf->entity_links.size(); i++) { df::histfig_entity_link* hfelink = hf->entity_links.at(i); if (hfelink->getType() == df::histfig_entity_link_type::POSITION) @@ -1139,7 +1140,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // identify dwarfs who are needed for meetings and mark them for exclusion - for (int i = 0; i < ui->activities.size(); ++i) + for (size_t i = 0; i < ui->activities.size(); ++i) { df::activity_info *act = ui->activities[i]; if (!act) continue; @@ -1229,7 +1230,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else { int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) dwarf_info[dwarf].state = dwarf_states[job]; else { @@ -1311,6 +1312,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) continue; dwarfs[dwarf]->status.labors[labor] = false; } + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) + { + num_haulers--; + } continue; } @@ -1336,7 +1341,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (int i = 0; i < num_haulers; i++) { - assert(i < hauler_ids.size()); + assert(size_t(i) < hauler_ids.size()); int dwarf = hauler_ids[i]; @@ -1352,7 +1357,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); } - for (int i = num_haulers; i < hauler_ids.size(); i++) + for (size_t i = num_haulers; i < hauler_ids.size(); i++) { assert(i < hauler_ids.size()); @@ -1512,7 +1517,7 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_FAILURE; } - for (int i = 0; i < labor_infos.size(); i++) + for (size_t i = 0; i < labor_infos.size(); i++) { reset_labor((df::unit_labor) i); } diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 60ccdd52a..40dc2bb48 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -14,6 +14,7 @@ // DF data structure definition headers #include "DataDefs.h" #include "MiscUtils.h" +#include "TileTypes.h" #include "df/build_req_choice_genst.h" #include "df/build_req_choice_specst.h" #include "df/construction_type.h" @@ -25,6 +26,7 @@ #include "df/job.h" #include "df/world.h" #include "df/building_constructionst.h" +#include "df/job_item.h" #include "modules/Gui.h" #include "modules/Screen.h" @@ -34,8 +36,7 @@ #include "modules/Maps.h" #include "modules/MapCache.h" -#include "TileTypes.h" -#include "df/job_item.h" +#include "uicommon.h" using namespace std; using std::map; @@ -68,38 +69,12 @@ struct MaterialDescriptor } }; - -static command_result automaterial_cmd(color_ostream &out, vector & parameters) -{ - return CR_OK; -} - DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } -void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - if (newline) - { - ++y; - x = left_margin; - } - else - x += text.length(); -} - -void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE) -{ - OutputString(10, x, y, hotkey); - string display(": "); - display.append(text); - OutputString(color, x, y, display, newline, left_margin); -} - -void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) +void AMOutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) { OutputHotkeyString(x, y, text, hotkey); OutputString(COLOR_WHITE, x, y, ": "); @@ -109,16 +84,7 @@ void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bo OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin); } -static string int_to_string(int i) -{ - return static_cast( &(ostringstream() << i))->str(); -} - //START UI Functions -struct coord32_t -{ - int32_t x, y, z; -}; static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST; static coord32_t box_first, box_second; @@ -668,7 +634,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest x = x - vport.x + 1; y = y - vport.y + 1; - OutputString(COLOR_GREEN, x, y, "X"); + OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */); } else if (show_box_selection && box_select_mode == SELECT_SECOND) { @@ -688,7 +654,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest int32_t x = xB - vport.x + 1; int32_t y = yB - vport.y + 1; - OutputString(color, x, y, "X"); + OutputString(color, x, y, "X", false, 0, 0, true /* map */); } } } @@ -698,7 +664,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { int32_t x = it->pos.x - vport.x + 1; int32_t y = it->pos.y - vport.y + 1; - OutputString(COLOR_GREEN, x, y, "X"); + OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */); } } } @@ -896,8 +862,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest void move_cursor(df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); } void move_cursor(coord32_t &pos) @@ -1114,7 +1079,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { - OutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin); + AMOutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin); if (box_select_mode == SELECT_MATERIALS) { @@ -1127,16 +1092,16 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - OutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); - OutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); + AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); + AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); ++y; - OutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); + AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); if (box_select_enabled) { - OutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); + AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - OutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); + AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); } ++y; if (box_select_enabled) @@ -1153,6 +1118,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest break; case SELECT_SECOND: + { OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin); int32_t curr_x, curr_y, curr_z; @@ -1165,7 +1131,12 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest int cx = box_first.x; int cy = box_first.y; - OutputString(COLOR_BROWN, cx, cy, "X"); + OutputString(COLOR_BROWN, cx, cy, "X", false, 0, 0, true /* map */); + break; + } + + default: + break; } OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index b4c93e15f..c01981603 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -194,6 +194,9 @@ struct melt_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index a645d579e..ca3f456ec 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -134,33 +134,9 @@ static TradeDepotInfo depot_info; * Item Manipulation */ -static bool check_mandates(df::item *item) -{ - for (auto it = world->mandates.begin(); it != world->mandates.end(); it++) - { - auto mandate = *it; - - if (mandate->mode != 0) - continue; - - if (item->getType() != mandate->item_type || - (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) - continue; - - if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) - continue; - - if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) - continue; - - return false; - } - - return true; -} - static bool is_valid_item(df::item *item) { + // Similar to Items::canTrade() with a few checks changed for (size_t i = 0; i < item->general_refs.size(); i++) { df::general_ref *ref = item->general_refs[i]; @@ -192,7 +168,7 @@ static bool is_valid_item(df::item *item) } } - if (!check_mandates(item)) + if (!Items::checkMandates(item)) return false; return true; @@ -236,9 +212,9 @@ static void mark_all_in_stockpiles(vector &stockpiles) bool mandates_ok = true; vector contained_items; Items::getContainedItems(item, &contained_items); - for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++) + for (df::item *cit : contained_items) { - if (!check_mandates(*cit)) + if (!Items::checkMandates(cit)) { mandates_ok = false; break; @@ -389,6 +365,9 @@ struct trade_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index badc094c9..9742b16e7 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -6,6 +6,7 @@ #include #include +#include "LuaTools.h" #include "modules/Buildings.h" #include "modules/Gui.h" @@ -106,9 +107,9 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) { if (! b) return " "; - bool at_nw_corner = x == b->x1 && y == b->y1; - bool at_se_corner = x == b->x2 && y == b->y2; - bool at_center = x == b->centerx && y == b->centery; + bool at_nw_corner = int32_t(x) == b->x1 && int32_t(y) == b->y1; + bool at_se_corner = int32_t(x) == b->x2 && int32_t(y) == b->y2; + bool at_center = int32_t(x) == b->centerx && int32_t(y) == b->centery; pair size = get_building_size(b); stringstream out;// = stringstream(); switch(b->getType()) @@ -227,7 +228,10 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) return "wy"; case workshop_type::Dyers: return "wd"; + case workshop_type::Kennels: + return "k"; case workshop_type::Custom: + case workshop_type::Tool: //can't do anything with custom workshop return "`"; } @@ -261,6 +265,8 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) case building_type::Construction: switch (((df::building_constructionst*) b)->type) { + case construction_type::NONE: + return "`"; case construction_type::Fortification: return "CF"; case construction_type::Wall: @@ -482,7 +488,7 @@ string get_tile_place(uint32_t x, uint32_t y, df::building* b) { if (! b || b->getType() != building_type::Stockpile) return " "; - if (b->x1 != x || b->y1 != y) + if (b->x1 != int32_t(x) || b->y1 != int32_t(y)) return "`"; pair size = get_building_size(b); df::building_stockpilest* sp = (df::building_stockpilest*) b; @@ -682,3 +688,44 @@ command_result blueprint(color_ostream &out, vector ¶meters) option |= QUERY; return do_transform(start, end, parameters[3], option); } + +static int create(lua_State *L, uint32_t options) { + df::coord start, end; + + lua_settop(L, 3); + Lua::CheckDFAssign(L, &start, 1); + if (!start.isValid()) + luaL_argerror(L, 1, "invalid start position"); + Lua::CheckDFAssign(L, &end, 2); + if (!end.isValid()) + luaL_argerror(L, 2, "invalid end position"); + string filename(lua_tostring(L, 3)); + + lua_pushboolean(L, do_transform(start, end, filename, options)); + return 1; + +} + +static int dig(lua_State *L) { + return create(L, DIG); +} + +static int build(lua_State *L) { + return create(L, BUILD); +} + +static int place(lua_State *L) { + return create(L, PLACE); +} + +static int query(lua_State *L) { + return create(L, QUERY); +} + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(dig), + DFHACK_LUA_COMMAND(build), + DFHACK_LUA_COMMAND(place), + DFHACK_LUA_COMMAND(query), + DFHACK_LUA_END +}; diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index 483082c67..17390cd5a 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -287,7 +287,7 @@ struct work_hook : df::building_workshopst{ } int w=db->x2-db->x1+1; std::vector &cur_frame=def->frames[frame]; - for(int i=0;i=0) { @@ -391,7 +391,9 @@ static int addBuilding(lua_State* L) int y=lua_tonumber(L,-1); lua_pop(L,1); - newDefinition.connections.can_connect.push_back(-1);//TODO add this too... + df::machine_conn_modes modes; + modes.whole = -1; + newDefinition.connections.can_connect.push_back(modes);//TODO add this too... newDefinition.connections.tiles.push_back(df::coord(x,y,0)); lua_pop(L,1); diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index 93efd60f6..f6023d2d7 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -17,7 +17,7 @@ void enable_quickfort_fn(pair& pair) { pair.secon * Material Choice Screen */ -static std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } +std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) { @@ -131,7 +131,7 @@ void ItemFilter::clear() materials.clear(); } -static DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } +DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) { @@ -386,7 +386,7 @@ void RoomMonitor::reset(color_ostream &out) } -static void delete_item_fn(df::job_item *x) { delete x; } +void delete_item_fn(df::job_item *x) { delete x; } // START Planning @@ -654,3 +654,12 @@ void Planner::cycleDefaultQuality(df::building_type type) if (*quality == item_quality::Artifact) (*quality) = item_quality::Ordinary; } + +map planmode_enabled, saved_planmodes; + +bool show_debugging = false; +bool show_help = false; + +Planner planner; + +RoomMonitor roomMonitor; diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 7ec0c41d4..9720d5421 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -65,7 +65,7 @@ struct MaterialDescriptor #define MAX_MATERIAL 21 #define SIDEBAR_WIDTH 30 -static bool canReserveRoom(df::building *building) +static inline bool canReserveRoom(df::building *building) { if (!building) return false; @@ -76,7 +76,7 @@ static bool canReserveRoom(df::building *building) return building->is_room; } -static std::vector getUniqueNoblePositions(df::unit *unit) +static inline std::vector getUniqueNoblePositions(df::unit *unit) { std::vector np; Units::getNoblePositions(&np, unit); @@ -92,19 +92,19 @@ static std::vector getUniqueNoblePositions(df::unit *unit) return np; } -static void delete_item_fn(df::job_item *x); +void delete_item_fn(df::job_item *x); -static MaterialInfo &material_info_identity_fn(MaterialInfo &m); +MaterialInfo &material_info_identity_fn(MaterialInfo &m); -static map planmode_enabled, saved_planmodes; +extern map planmode_enabled, saved_planmodes; void enable_quickfort_fn(pair& pair); void debug(const std::string &msg); -static std::string material_to_string_fn(MaterialInfo m); +std::string material_to_string_fn(MaterialInfo m); -static bool show_debugging = false; -static bool show_help = false; +extern bool show_debugging; +extern bool show_help; struct ItemFilter { @@ -114,7 +114,9 @@ struct ItemFilter bool decorated_only; ItemFilter() : min_quality(df::item_quality::Ordinary), decorated_only(false), valid(true) - { } + { + clear(); // mat_mask is not cleared by default (see issue #1047) + } bool matchesMask(DFHack::MaterialInfo &mat); @@ -385,7 +387,7 @@ class Planner public: bool in_dummmy_screen; - Planner() : quickfort_mode(false), in_dummmy_screen(false) { } + Planner() : in_dummmy_screen(false), quickfort_mode(false) { } bool isPlanableBuilding(const df::building_type type) const { @@ -489,8 +491,8 @@ private: } }; -static Planner planner; +extern Planner planner; -static RoomMonitor roomMonitor; +extern RoomMonitor roomMonitor; #endif diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 6f833ba6d..37bb069a4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -105,8 +105,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest planmode_enabled[type] = !planmode_enabled[type]; if (!planmode_enabled[type]) { - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); planner.in_dummmy_screen = false; } return true; @@ -138,8 +137,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest { if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) { - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); if (planner.inQuickFortMode()) { planner.in_dummmy_screen = true; @@ -188,11 +186,13 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (isInNobleRoomQueryMode()) { + if (Gui::inRenameBuilding()) + return false; auto np = getNoblePositionOfSelectedBuildingOwner(); df::interface_key last_token = get_string_key(input); if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) { - int selection = last_token - interface_key::STRING_A048; + size_t selection = last_token - interface_key::STRING_A048; if (np.size() < selection) return false; roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); @@ -315,7 +315,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest int y = 24; OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); - for (int i = 0; i < np.size() && i < 9; i++) + for (size_t i = 0; i < np.size() && i < 9; i++) { bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id) == np[i].position->code); diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 87adbe734..69325331e 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -547,8 +547,8 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) { CHECK_NULL_POINTER(target); - df::tile_designation mask(0); - df::tile_designation value(0); + df::tile_designation mask; + df::tile_designation value; if (name == "ABOVE_GROUND") mask.bits.subterranean = true; diff --git a/plugins/catsplosion.cpp b/plugins/catsplosion.cpp deleted file mode 100644 index eee39a996..000000000 --- a/plugins/catsplosion.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Catsplosion -// By Zhentar , Further modified by dark_rabite, peterix, belal -// This work of evil makes animals pregnant -// and due within 2 in-game hours... - -#include -#include -#include -#include -#include // for rand() -#include // for std::transform -#include -#include -#include -using namespace std; - -#include "DFHack.h" -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "DataDefs.h" -#include -#include -#include - -using namespace DFHack; - -DFHACK_PLUGIN("catsplosion"); -REQUIRE_GLOBAL(world); - -command_result catsplosion (color_ostream &out, std::vector & parameters); - -// Mandatory init function. If you have some global state, create it here. -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - // Fill the command list with your commands. - commands.push_back(PluginCommand( - "catsplosion", "Make cats just /multiply/.", - catsplosion, false, - " Makes cats abnormally abundant, if you provide some base population ;)\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result catsplosion (color_ostream &out, std::vector & parameters) -{ - if (!Core::getInstance().isWorldLoaded()) - { - out.printerr("World not loaded.\n"); - return CR_FAILURE; - } - bool list_only = false; - list s_creatures; - if (parameters.size()) - { - for (size_t i = 0; i < parameters.size(); i++) - { - if (parameters[i] == "list") - { - list_only = true; - } - else - { - s_creatures.push_back(parameters[i]); - } - } - } - else - { - s_creatures.push_back("CAT"); - } - // make the creature list unique ... with cats. they are always unique - s_creatures.unique(); - // SUSPEND THE CORE! ::Evil laugh:: - CoreSuspender susp; - - uint32_t numCreatures; - if(!(numCreatures = Units::getNumCreatures())) - { - out.printerr("Can't get any creatures.\n"); - return CR_FAILURE; - } - - int totalcount=0; - int totalchanged=0; - int totalcreated=0; - string sextype; - - int maxlength = 0; - map > male_counts; - map > female_counts; - - // classify - for(uint32_t i =0;i < numCreatures;i++) - { - df::unit * creature = Units::GetCreature(i); - df::creature_raw *raw = world->raws.creatures.all[creature->race]; - if(Units::isFemale(creature)) - { - female_counts[raw->creature_id].push_back(creature); - male_counts[raw->creature_id].size(); - } - else // male, other, etc. - { - male_counts[raw->creature_id].push_back(creature); - female_counts[raw->creature_id].size(); //auto initialize the females as well - } - } - - if (list_only) - { - out.print("Type Male # Female #\n"); - for (auto it1 = male_counts.begin(); it1!=male_counts.end(); it1++) - { - out.print("%22s %6d %8d\n", it1->first.c_str(), it1->second.size(), female_counts[it1->first].size()); - } - return CR_OK; - } - - // process - for (list::iterator it = s_creatures.begin(); it != s_creatures.end(); ++it) - { - std::string clinput = *it; - std::transform(clinput.begin(), clinput.end(), clinput.begin(), ::toupper); - vector &females = female_counts[clinput]; - uint32_t sz_fem = females.size(); - totalcount += sz_fem; - for(uint32_t i = 0; i < sz_fem; i++)// max 1 pregnancy - { - df::unit * female = females[i]; - // accelerate - if(female->relations.pregnancy_timer != 0) - { - female->relations.pregnancy_timer = rand() % 100 + 1; - totalchanged++; - } - else if(!female->relations.pregnancy_genes) - { - df::unit_genes *preg = new df::unit_genes; - preg->appearance = female->appearance.genes.appearance; - preg->colors = female->appearance.genes.colors; - female->relations.pregnancy_genes = preg; - female->relations.pregnancy_timer = rand() % 100 + 1; - female->relations.pregnancy_caste = 1; - totalcreated ++; - } - } - } - if(totalchanged) - out.print("%d pregnancies accelerated.\n", totalchanged); - if(totalcreated) - out.print("%d pregnancies created.\n", totalcreated); - if (!totalcount) - { - out.printerr("No creatures matched.\n"); - return CR_FAILURE; - } - out.print("Total creatures checked: %d\n", totalcount); - return CR_OK; -} diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 64e932609..cf7d4fdbc 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -1,25 +1,24 @@ // changelayer plugin // allows changing the material type of geological layers -// some headers required for a plugin. Nothing special, just the basics. +#include "Console.h" #include "Core.h" -#include -#include -#include - -// DF data structure definition headers #include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "TileTypes.h" + +#include "modules/Gui.h" +#include "modules/MapCache.h" #include "modules/Maps.h" #include "modules/Materials.h" -#include "modules/MapCache.h" -#include "modules/Gui.h" - -#include "TileTypes.h" +// DF data structure definition headers +#include "df/region_map_entry.h" +#include "df/world.h" #include "df/world_data.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" -#include "df/region_map_entry.h" using namespace DFHack; using namespace df::enums; @@ -228,7 +227,7 @@ command_result changelayer (color_ostream &out, std::vector & para { if(verbose) out << "---Biome: " << i; - if(!all_biomes && i!=biome) + if(!all_biomes && uint32_t(i)!=biome) { if(verbose) out << "-skipping" << endl; @@ -258,7 +257,7 @@ command_result changelayer (color_ostream &out, std::vector & para out << "geoindex: " << geoindex << endl; bool skip = false; - for(int g=0; gdisplay_frames; gps->display_frames=0; @@ -172,7 +173,7 @@ void viewscreen_commandpromptst::render() if(cursor_pos < (dim.x - 10)) { Screen::paintString(Screen::Pen(' ', 7, 0), 10,0 , entry); - if (entry.size() > dim.x - 10) + if (int16_t(entry.size()) > dim.x - 10) Screen::paintTile(Screen::Pen('\032', 7, 0), dim.x - 1, 0); if (cursor != " ") Screen::paintString(Screen::Pen(' ', 10, 0), 10 + cursor_pos, 0, cursor); @@ -243,7 +244,7 @@ void viewscreen_commandpromptst::feed(std::set *events) entry.erase(cursor_pos - 1, 1); cursor_pos--; } - if(cursor_pos > entry.size()) + if(size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); continue; } @@ -260,7 +261,7 @@ void viewscreen_commandpromptst::feed(std::set *events) if(events->count(interface_key::CURSOR_RIGHT)) { cursor_pos++; - if (cursor_pos > entry.size()) + if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); } else if(events->count(interface_key::CURSOR_LEFT)) @@ -294,10 +295,10 @@ void viewscreen_commandpromptst::feed(std::set *events) } else if(events->count(interface_key::CURSOR_DOWN)) { - if (history_idx < command_history.size() - 1) + if (size_t(history_idx) < command_history.size() - 1) { history_idx++; - if (history_idx >= command_history.size()) + if (size_t(history_idx) >= command_history.size()) history_idx = command_history.size() - 1; entry = get_entry(); cursor_pos = entry.size(); @@ -314,6 +315,7 @@ command_result show_prompt(color_ostream &out, std::vector & param if (Gui::getCurFocus() == "dfhack/commandprompt") { Screen::dismiss(Gui::getCurViewscreen(true)); + return CR_OK; } std::string params; for(size_t i=0;i ikey_set; command_result df_confirm (color_ostream &out, vector & parameters); struct conf_wrapper; -static std::map confirmations; +static map confirmations; string active_id; -std::queue cmds; +queue cmds; template inline bool in_vector (std::vector &vec, FT item) @@ -58,6 +60,26 @@ string char_replace (string s, char a, char b) bool set_conf_state (string name, bool state); +class confirmation_base { +public: + enum cstate { INACTIVE, ACTIVE, SELECTED }; + virtual string get_id() = 0; + virtual bool set_state(cstate) = 0; + + static bool set_state(string id, cstate state) + { + if (active && active->get_id() == id) + { + active->set_state(state); + return true; + } + return false; + } +protected: + static confirmation_base *active; +}; +confirmation_base *confirmation_base::active = nullptr; + struct conf_wrapper { private: bool enabled; @@ -74,9 +96,9 @@ public: bool apply (bool state) { if (state == enabled) return true; - for (auto h = hooks.begin(); h != hooks.end(); ++h) + for (auto hook : hooks) { - if (!(**h).apply(state)) + if (!hook->apply(state)) return false; } enabled = state; @@ -88,8 +110,8 @@ public: namespace trade { static bool goods_selected (const std::vector &selected) { - for (auto it = selected.begin(); it != selected.end(); ++it) - if (*it) + for (char c : selected) + if (c) return true; return false; } @@ -112,11 +134,10 @@ namespace trade { { // check to see if item is in a container // (if the container is not selected, it will be detected separately) - std::vector &refs = items[i]->general_refs; bool in_container = false; - for (auto it = refs.begin(); it != refs.end(); ++it) + for (auto ref : items[i]->general_refs) { - if (virtual_cast(*it)) + if (virtual_cast(ref)) { in_container = true; break; @@ -154,7 +175,7 @@ namespace conf_lua { if (out) { delete out; - out = NULL; + out = nullptr; } lua_close(l_state); } @@ -176,31 +197,24 @@ namespace conf_lua { { Lua::Push(l_state, val); } - template - void table_set (lua_State *L, KeyType k, ValueType v) - { - Lua::Push(L, k); - Lua::Push(L, v); - lua_settable(L, -3); - } namespace api { int get_ids (lua_State *L) { lua_newtable(L); - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - table_set(L, it->first, true); + for (auto item : confirmations) + Lua::TableInsert(L, item.first, true); return 1; } int get_conf_data (lua_State *L) { lua_newtable(L); int i = 1; - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + for (auto item : confirmations) { Lua::Push(L, i++); lua_newtable(L); - table_set(L, "id", it->first); - table_set(L, "enabled", it->second->is_enabled()); + Lua::TableInsert(L, "id", item.first); + Lua::TableInsert(L, "enabled", item.second->is_enabled()); lua_settable(L, -3); } return 1; @@ -240,29 +254,42 @@ void show_options() } template -class confirmation { +class confirmation : public confirmation_base { public: - enum cstate { INACTIVE, ACTIVE, SELECTED }; typedef T screen_type; screen_type *screen; - void set_state (cstate s) + + bool set_state (cstate s) override { + if (confirmation_base::active && confirmation_base::active != this) + { + // Stop this confirmation from appearing over another one + return false; + } + state = s; - if (s == INACTIVE) + if (s == INACTIVE) { active_id = ""; - else + confirmation_base::active = nullptr; + } + else { active_id = get_id(); + confirmation_base::active = this; + } + return true; } bool feed (ikey_set *input) { if (state == INACTIVE) { - for (auto it = input->begin(); it != input->end(); ++it) + for (df::interface_key key : *input) { - if (intercept_key(*it)) + if (intercept_key(key)) { - last_key = *it; - set_state(ACTIVE); - return true; + if (set_state(ACTIVE)) + { + last_key = key; + return true; + } } } return false; @@ -297,8 +324,8 @@ public: { split_string(&lines, get_message(), "\n"); size_t max_length = 40; - for (auto it = lines.begin(); it != lines.end(); ++it) - max_length = std::max(max_length, it->size()); + for (string line : lines) + max_length = std::max(max_length, line.size()); int width = max_length + 4; int height = lines.size() + 4; int x1 = (gps->dimx / 2) - (width / 2); @@ -348,7 +375,7 @@ public: set_state(INACTIVE); } } - virtual string get_id() = 0; + virtual string get_id() override = 0; #define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id()); bool intercept_key (df::interface_key key) { @@ -390,15 +417,12 @@ protected: }; template -int conf_register(confirmation *c, ...) +int conf_register(confirmation *c, const vector &hooks) { conf_wrapper *w = new conf_wrapper(); confirmations[c->get_id()] = w; - va_list args; - va_start(args, c); - while (VMethodInterposeLinkBase *hook = va_arg(args, VMethodInterposeLinkBase*)) + for (auto hook : hooks) w->add_hook(hook); - va_end(args); return 0; } @@ -426,35 +450,35 @@ struct cls##_hooks : cls::screen_type { \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); \ -static int conf_register_##cls = conf_register(&cls##_instance, \ +static int conf_register_##cls = conf_register(&cls##_instance, {\ &INTERPOSE_HOOK(cls##_hooks, feed), \ &INTERPOSE_HOOK(cls##_hooks, render), \ &INTERPOSE_HOOK(cls##_hooks, key_conflict), \ - NULL); +}); -#define DEFINE_CONFIRMATION(cls, screen, prio) \ +#define DEFINE_CONFIRMATION(cls, screen) \ class confirmation_##cls : public confirmation { \ virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ }; \ - IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, prio); + IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0); /* This section defines stubs for all confirmation dialogs, with methods implemented in plugins/lua/confirm.lua. IDs (used in the "confirm enable/disable" command, by Lua, and in the docs) are obtained by replacing '_' with '-' in the first argument to DEFINE_CONFIRMATION */ -DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst, -1); -DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst, 0); -DEFINE_CONFIRMATION(haul_delete, viewscreen_dwarfmodest, 0); -DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, 0); -DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst, 0); -DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst, 0); -DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest, 0); -DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest, 0); -DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst, 0); +DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst); +DEFINE_CONFIRMATION(haul_delete, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst); +DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst); +DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest); +DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { @@ -476,9 +500,9 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) { if (is_enabled != enable) { - for (auto c = confirmations.begin(); c != confirmations.end(); ++c) + for (auto c : confirmations) { - if (!c->second->apply(enable)) + if (!c.second->apply(enable)) return CR_FAILURE; } is_enabled = enable; @@ -495,6 +519,13 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) if (plugin_enable(out, false) != CR_OK) return CR_FAILURE; conf_lua::cleanup(); + + for (auto item : confirmations) + { + delete item.second; + } + confirmations.clear(); + return CR_OK; } @@ -511,14 +542,21 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) bool set_conf_state (string name, bool state) { bool found = false; - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + for (auto it : confirmations) { - if (it->first == name) + if (it.first == name) { found = true; - it->second->apply(state); + it.second->apply(state); } } + + if (state == false) + { + // dismiss the confirmation too + confirmation_base::set_state(name, confirmation_base::INACTIVE); + } + return found; } @@ -535,23 +573,23 @@ command_result df_confirm (color_ostream &out, vector & parameters) if (parameters.empty() || in_vector(parameters, "help") || in_vector(parameters, "status")) { out << "Available options: \n"; - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - out.print(" %20s: %s\n", it->first.c_str(), it->second->is_enabled() ? "enabled" : "disabled"); + for (auto it : confirmations) + out.print(" %20s: %s\n", it.first.c_str(), it.second->is_enabled() ? "enabled" : "disabled"); return CR_OK; } - for (auto it = parameters.begin(); it != parameters.end(); ++it) + for (string param : parameters) { - if (*it == "enable") + if (param == "enable") state = true; - else if (*it == "disable") + else if (param == "disable") state = false; - else if (*it == "all") + else if (param == "all") { - for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - it->second->apply(state); + for (auto it : confirmations) + it.second->apply(state); } else - enable_conf(out, *it, state); + enable_conf(out, param, state); } return CR_OK; } diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 156176bbe..70794e38c 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -86,38 +86,35 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it building = df::building::find(dest_building); prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, job_skill::NONE, - df::historical_entity::find(unit->civ_id), - (World::isFortressMode()) ? df::world_site::find(ui->site_id) : NULL); + df::historical_entity::find(unit->civ_id), 0, + (World::isFortressMode()) ? df::world_site::find(ui->site_id) : NULL, 0); if (!out_items.size()) return false; // if we asked to make shoes and we got twice as many as we asked, then we're okay // otherwise, make a second set because shoes are normally made in pairs - if (is_shoes && out_items.size() == prod->count * 2) + if (is_shoes && out_items.size() == size_t(prod->count * 2)) is_shoes = false; MapExtras::MapCache mc; for (size_t i = 0; i < out_items.size(); i++) { - bool on_ground = true; if (container) { - on_ground = false; out_items[i]->flags.bits.removed = 1; if (!Items::moveToContainer(mc, out_items[i], container)) out_items[i]->moveToGround(container->pos.x, container->pos.y, container->pos.z); } - if (building) + else if (building) { - on_ground = false; out_items[i]->flags.bits.removed = 1; if (!Items::moveToBuilding(mc, out_items[i], (df::building_actual *)building, 0)) out_items[i]->moveToGround(building->centerx, building->centery, building->z); } - if (on_ground) - out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); - if (move_to_cursor) + else if (move_to_cursor) out_items[i]->moveToGround(cursor->x, cursor->y, cursor->z); + else + out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); if (is_gloves) { // if the reaction creates gloves without handedness, then create 2 sets (left and right) diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index 82404b288..e006bd17c 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -68,62 +68,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } - -// code for setting nicknames is copypasta from rename.cpp -// will not work in all cases, some vampires don't like to get nicks - -static void set_nickname(df::language_name *name, std::string nick) -{ - if (!name->has_name) - { - *name = df::language_name(); - - name->language = 0; - name->has_name = true; - } - - name->nickname = nick; -} - -void setUnitNickname(df::unit *unit, const std::string &nick) -{ - // There are >=3 copies of the name, and the one - // in the unit is not the authoritative one. - // This is the reason why military units often - // lose nicknames set from Dwarf Therapist. - set_nickname(&unit->name, nick); - - if (unit->status.current_soul) - set_nickname(&unit->status.current_soul->name, nick); - - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - if (figure) - { - set_nickname(&figure->name, nick); - - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) - { - auto identity = df::identity::find(figure->info->reputation->cur_identity); - - if (identity) - { - auto id_hfig = df::historical_figure::find(identity->histfig_id); - - if (id_hfig) - { - // Even DF doesn't do this bit, because it's apparently - // only used for demons masquerading as gods, so you - // can't ever change their nickname in-game. - set_nickname(&id_hfig->name, nick); - } - else - set_nickname(&identity->name, nick); - } - } - } -} - std::string determineCurse(df::unit * unit) { string cursetype = "unknown"; @@ -229,7 +173,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) } // non-cursed creatures have curse_year == -1 - if(unit->relations.curse_year != -1) + if(unit->curse_year != -1) { cursecount++; @@ -237,7 +181,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) if(giveNick) { - setUnitNickname(unit, cursetype); //"CURSED"); + Units::setNickname(unit, cursetype); //"CURSED"); } if(giveDetails) @@ -268,8 +212,8 @@ command_result cursecheck (color_ostream &out, vector & parameters) } out.print("born in %d, cursed in %d to be a %s. (%s%s%s)\n", - unit->relations.birth_year, - unit->relations.curse_year, + unit->birth_year, + unit->curse_year, cursetype.c_str(), // technically most cursed creatures are undead, // therefore output 'active' if they are not completely dead diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index f0171dabc..fd7ceb3d7 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -9,6 +9,9 @@ #include "modules/Maps.h" #include "TileTypes.h" +#include "df/map_block.h" +#include "df/world.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 1f3ff6f75..4fb4b5cf5 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -3,7 +3,6 @@ DFHACK_PLUGIN(vectors vectors.cpp) endif() ADD_DEFINITIONS(-DDEV_PLUGIN) -#DFHACK_PLUGIN(autolabor2 autolabor2.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(color-dfhack-text color-dfhack-text.cpp) DFHACK_PLUGIN(counters counters.cpp) @@ -11,7 +10,7 @@ DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) DFHACK_PLUGIN(kittens kittens.cpp) -DFHACK_PLUGIN(memview memview.cpp) +DFHACK_PLUGIN(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(notes notes.cpp) DFHACK_PLUGIN(onceExample onceExample.cpp) diff --git a/plugins/devel/autolabor2.cpp b/plugins/devel/autolabor2.cpp deleted file mode 100644 index 4897832d4..000000000 --- a/plugins/devel/autolabor2.cpp +++ /dev/null @@ -1,2585 +0,0 @@ -/* -* Autolabor 2.0 module for dfhack -* -* */ - - -#include "Core.h" -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "modules/Units.h" -#include "modules/World.h" -#include "modules/Maps.h" -#include "modules/MapCache.h" -#include "modules/Items.h" - -// DF data structure definition headers -#include "DataDefs.h" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using std::string; -using std::endl; -using namespace DFHack; -using namespace df::enums; -using df::global::ui; -using df::global::world; - -#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) - -DFHACK_PLUGIN_IS_ENABLED(enable_autolabor); - -static bool print_debug = 0; - -static std::vector state_count(5); - -static PersistentDataItem config; - -enum ConfigFlags { - CF_ENABLED = 1, - CF_ALLOW_FISHING = 2, - CF_ALLOW_HUNTING = 4, -}; - - -// Here go all the command declarations... -// mostly to allow having the mandatory stuff on top of the file and commands on the bottom -command_result autolabor (color_ostream &out, std::vector & parameters); - -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - autolabor2.plug.so or autolabor2.plug.dll in this case -DFHACK_PLUGIN("autolabor2"); - -static void generate_labor_to_skill_map(); - -enum dwarf_state { - // Ready for a new task - IDLE, - - // Busy with a useful task - BUSY, - - // In the military, can't work - MILITARY, - - // Child or noble, can't work - CHILD, - - // Doing something that precludes working, may be busy for a while - OTHER -}; - -const int NUM_STATE = 5; - -static const char *state_names[] = { - "IDLE", - "BUSY", - "MILITARY", - "CHILD", - "OTHER", -}; - -static const dwarf_state dwarf_states[] = { - BUSY /* CarveFortification */, - BUSY /* DetailWall */, - BUSY /* DetailFloor */, - BUSY /* Dig */, - BUSY /* CarveUpwardStaircase */, - BUSY /* CarveDownwardStaircase */, - BUSY /* CarveUpDownStaircase */, - BUSY /* CarveRamp */, - BUSY /* DigChannel */, - BUSY /* FellTree */, - BUSY /* GatherPlants */, - BUSY /* RemoveConstruction */, - BUSY /* CollectWebs */, - BUSY /* BringItemToDepot */, - BUSY /* BringItemToShop */, - OTHER /* Eat */, - OTHER /* GetProvisions */, - OTHER /* Drink */, - OTHER /* Drink2 */, - OTHER /* FillWaterskin */, - OTHER /* FillWaterskin2 */, - OTHER /* Sleep */, - BUSY /* CollectSand */, - BUSY /* Fish */, - BUSY /* Hunt */, - OTHER /* HuntVermin */, - BUSY /* Kidnap */, - BUSY /* BeatCriminal */, - BUSY /* StartingFistFight */, - BUSY /* CollectTaxes */, - BUSY /* GuardTaxCollector */, - BUSY /* CatchLiveLandAnimal */, - BUSY /* CatchLiveFish */, - BUSY /* ReturnKill */, - BUSY /* CheckChest */, - BUSY /* StoreOwnedItem */, - BUSY /* PlaceItemInTomb */, - BUSY /* StoreItemInStockpile */, - BUSY /* StoreItemInBag */, - BUSY /* StoreItemInHospital */, - BUSY /* StoreItemInChest */, - BUSY /* StoreItemInCabinet */, - BUSY /* StoreWeapon */, - BUSY /* StoreArmor */, - BUSY /* StoreItemInBarrel */, - BUSY /* StoreItemInBin */, - BUSY /* SeekArtifact */, - BUSY /* SeekInfant */, - OTHER /* AttendParty */, - OTHER /* GoShopping */, - OTHER /* GoShopping2 */, - BUSY /* Clean */, - OTHER /* Rest */, - BUSY /* PickupEquipment */, - BUSY /* DumpItem */, - OTHER /* StrangeMoodCrafter */, - OTHER /* StrangeMoodJeweller */, - OTHER /* StrangeMoodForge */, - OTHER /* StrangeMoodMagmaForge */, - OTHER /* StrangeMoodBrooding */, - OTHER /* StrangeMoodFell */, - OTHER /* StrangeMoodCarpenter */, - OTHER /* StrangeMoodMason */, - OTHER /* StrangeMoodBowyer */, - OTHER /* StrangeMoodTanner */, - OTHER /* StrangeMoodWeaver */, - OTHER /* StrangeMoodGlassmaker */, - OTHER /* StrangeMoodMechanics */, - BUSY /* ConstructBuilding */, - BUSY /* ConstructDoor */, - BUSY /* ConstructFloodgate */, - BUSY /* ConstructBed */, - BUSY /* ConstructThrone */, - BUSY /* ConstructCoffin */, - BUSY /* ConstructTable */, - BUSY /* ConstructChest */, - BUSY /* ConstructBin */, - BUSY /* ConstructArmorStand */, - BUSY /* ConstructWeaponRack */, - BUSY /* ConstructCabinet */, - BUSY /* ConstructStatue */, - BUSY /* ConstructBlocks */, - BUSY /* MakeRawGlass */, - BUSY /* MakeCrafts */, - BUSY /* MintCoins */, - BUSY /* CutGems */, - BUSY /* CutGlass */, - BUSY /* EncrustWithGems */, - BUSY /* EncrustWithGlass */, - BUSY /* DestroyBuilding */, - BUSY /* SmeltOre */, - BUSY /* MeltMetalObject */, - BUSY /* ExtractMetalStrands */, - BUSY /* PlantSeeds */, - BUSY /* HarvestPlants */, - BUSY /* TrainHuntingAnimal */, - BUSY /* TrainWarAnimal */, - BUSY /* MakeWeapon */, - BUSY /* ForgeAnvil */, - BUSY /* ConstructCatapultParts */, - BUSY /* ConstructBallistaParts */, - BUSY /* MakeArmor */, - BUSY /* MakeHelm */, - BUSY /* MakePants */, - BUSY /* StudWith */, - BUSY /* ButcherAnimal */, - BUSY /* PrepareRawFish */, - BUSY /* MillPlants */, - BUSY /* BaitTrap */, - BUSY /* MilkCreature */, - BUSY /* MakeCheese */, - BUSY /* ProcessPlants */, - BUSY /* ProcessPlantsBag */, - BUSY /* ProcessPlantsVial */, - BUSY /* ProcessPlantsBarrel */, - BUSY /* PrepareMeal */, - BUSY /* WeaveCloth */, - BUSY /* MakeGloves */, - BUSY /* MakeShoes */, - BUSY /* MakeShield */, - BUSY /* MakeCage */, - BUSY /* MakeChain */, - BUSY /* MakeFlask */, - BUSY /* MakeGoblet */, - BUSY /* MakeInstrument */, - BUSY /* MakeToy */, - BUSY /* MakeAnimalTrap */, - BUSY /* MakeBarrel */, - BUSY /* MakeBucket */, - BUSY /* MakeWindow */, - BUSY /* MakeTotem */, - BUSY /* MakeAmmo */, - BUSY /* DecorateWith */, - BUSY /* MakeBackpack */, - BUSY /* MakeQuiver */, - BUSY /* MakeBallistaArrowHead */, - BUSY /* AssembleSiegeAmmo */, - BUSY /* LoadCatapult */, - BUSY /* LoadBallista */, - BUSY /* FireCatapult */, - BUSY /* FireBallista */, - BUSY /* ConstructMechanisms */, - BUSY /* MakeTrapComponent */, - BUSY /* LoadCageTrap */, - BUSY /* LoadStoneTrap */, - BUSY /* LoadWeaponTrap */, - BUSY /* CleanTrap */, - BUSY /* CastSpell */, - BUSY /* LinkBuildingToTrigger */, - BUSY /* PullLever */, - BUSY /* BrewDrink */, - BUSY /* ExtractFromPlants */, - BUSY /* ExtractFromRawFish */, - BUSY /* ExtractFromLandAnimal */, - BUSY /* TameVermin */, - BUSY /* TameAnimal */, - BUSY /* ChainAnimal */, - BUSY /* UnchainAnimal */, - BUSY /* UnchainPet */, - BUSY /* ReleaseLargeCreature */, - BUSY /* ReleasePet */, - BUSY /* ReleaseSmallCreature */, - BUSY /* HandleSmallCreature */, - BUSY /* HandleLargeCreature */, - BUSY /* CageLargeCreature */, - BUSY /* CageSmallCreature */, - BUSY /* RecoverWounded */, - BUSY /* DiagnosePatient */, - BUSY /* ImmobilizeBreak */, - BUSY /* DressWound */, - BUSY /* CleanPatient */, - BUSY /* Surgery */, - BUSY /* Suture */, - BUSY /* SetBone */, - BUSY /* PlaceInTraction */, - BUSY /* DrainAquarium */, - BUSY /* FillAquarium */, - BUSY /* FillPond */, - BUSY /* GiveWater */, - BUSY /* GiveFood */, - BUSY /* GiveWater2 */, - BUSY /* GiveFood2 */, - BUSY /* RecoverPet */, - BUSY /* PitLargeAnimal */, - BUSY /* PitSmallAnimal */, - BUSY /* SlaughterAnimal */, - BUSY /* MakeCharcoal */, - BUSY /* MakeAsh */, - BUSY /* MakeLye */, - BUSY /* MakePotashFromLye */, - BUSY /* FertilizeField */, - BUSY /* MakePotashFromAsh */, - BUSY /* DyeThread */, - BUSY /* DyeCloth */, - BUSY /* SewImage */, - BUSY /* MakePipeSection */, - BUSY /* OperatePump */, - OTHER /* ManageWorkOrders */, - OTHER /* UpdateStockpileRecords */, - OTHER /* TradeAtDepot */, - BUSY /* ConstructHatchCover */, - BUSY /* ConstructGrate */, - BUSY /* RemoveStairs */, - BUSY /* ConstructQuern */, - BUSY /* ConstructMillstone */, - BUSY /* ConstructSplint */, - BUSY /* ConstructCrutch */, - BUSY /* ConstructTractionBench */, - BUSY /* CleanSelf */, - BUSY /* BringCrutch */, - BUSY /* ApplyCast */, - BUSY /* CustomReaction */, - BUSY /* ConstructSlab */, - BUSY /* EngraveSlab */, - BUSY /* ShearCreature */, - BUSY /* SpinThread */, - BUSY /* PenLargeAnimal */, - BUSY /* PenSmallAnimal */, - BUSY /* MakeTool */, - BUSY /* CollectClay */, - BUSY /* InstallColonyInHive */, - BUSY /* CollectHiveProducts */, - OTHER /* CauseTrouble */, - OTHER /* DrinkBlood */, - OTHER /* ReportCrime */, - OTHER /* ExecuteCriminal */, - BUSY /* TrainAnimal */, - BUSY /* CarveTrack */, - BUSY /* PushTrackVehicle */, - BUSY /* PlaceTrackVehicle */, - BUSY /* StoreItemInVehicle */, - BUSY /* GeldAnimal */, - BUSY /* MakeFigurine */, - BUSY /* MakeAmulet */, - BUSY /* MakeScepter */, - BUSY /* MakeCrown */, - BUSY /* MakeRing */, - BUSY /* MakeEarring */, - BUSY /* MakeBracelet */, - BUSY /* MakeGem */ -}; - -struct labor_info -{ - PersistentDataItem config; - - int active_dwarfs; - int idle_dwarfs; - int busy_dwarfs; - - int priority() { return config.ival(1); } - void set_priority(int priority) { config.ival(1) = priority; } - - int maximum_dwarfs() { return config.ival(2); } - void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } - - int time_since_last_assigned() - { - return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); - } - void mark_assigned() { - config.ival(3) = (* df::global::cur_year); - config.ival(4) = (* df::global::cur_year_tick); - } - -}; - -enum tools_enum { - TOOL_NONE, TOOL_PICK, TOOL_AXE, TOOL_CROSSBOW, - TOOLS_MAX -}; - - -struct labor_default -{ - int priority; - int maximum_dwarfs; - tools_enum tool; -}; - -static std::vector labor_infos; - -static const struct labor_default default_labor_infos[] = { - /* MINE */ {200, 0, TOOL_PICK}, - /* HAUL_STONE */ {100, 0, TOOL_NONE}, - /* HAUL_WOOD */ {100, 0, TOOL_NONE}, - /* HAUL_BODY */ {200, 0, TOOL_NONE}, - /* HAUL_FOOD */ {300, 0, TOOL_NONE}, - /* HAUL_REFUSE */ {100, 0, TOOL_NONE}, - /* HAUL_ITEM */ {100, 0, TOOL_NONE}, - /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, - /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, - /* CLEAN */ {200, 0, TOOL_NONE}, - /* CUTWOOD */ {200, 0, TOOL_AXE}, - /* CARPENTER */ {200, 0, TOOL_NONE}, - /* DETAIL */ {200, 0, TOOL_NONE}, - /* MASON */ {200, 0, TOOL_NONE}, - /* ARCHITECT */ {400, 0, TOOL_NONE}, - /* ANIMALTRAIN */ {200, 0, TOOL_NONE}, - /* ANIMALCARE */ {200, 0, TOOL_NONE}, - /* DIAGNOSE */ {1000, 0, TOOL_NONE}, - /* SURGERY */ {1000, 0, TOOL_NONE}, - /* BONE_SETTING */ {1000, 0, TOOL_NONE}, - /* SUTURING */ {1000, 0, TOOL_NONE}, - /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, - /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, - /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, - /* BUTCHER */ {200, 0, TOOL_NONE}, - /* TRAPPER */ {200, 0, TOOL_NONE}, - /* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, - /* LEATHER */ {200, 0, TOOL_NONE}, - /* TANNER */ {200, 0, TOOL_NONE}, - /* BREWER */ {200, 0, TOOL_NONE}, - /* ALCHEMIST */ {200, 0, TOOL_NONE}, - /* SOAP_MAKER */ {200, 0, TOOL_NONE}, - /* WEAVER */ {200, 0, TOOL_NONE}, - /* CLOTHESMAKER */ {200, 0, TOOL_NONE}, - /* MILLER */ {200, 0, TOOL_NONE}, - /* PROCESS_PLANT */ {200, 0, TOOL_NONE}, - /* MAKE_CHEESE */ {200, 0, TOOL_NONE}, - /* MILK */ {200, 0, TOOL_NONE}, - /* COOK */ {200, 0, TOOL_NONE}, - /* PLANT */ {200, 0, TOOL_NONE}, - /* HERBALIST */ {200, 0, TOOL_NONE}, - /* FISH */ {100, 0, TOOL_NONE}, - /* CLEAN_FISH */ {200, 0, TOOL_NONE}, - /* DISSECT_FISH */ {200, 0, TOOL_NONE}, - /* HUNT */ {100, 0, TOOL_CROSSBOW}, - /* SMELT */ {200, 0, TOOL_NONE}, - /* FORGE_WEAPON */ {200, 0, TOOL_NONE}, - /* FORGE_ARMOR */ {200, 0, TOOL_NONE}, - /* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, - /* METAL_CRAFT */ {200, 0, TOOL_NONE}, - /* CUT_GEM */ {200, 0, TOOL_NONE}, - /* ENCRUST_GEM */ {200, 0, TOOL_NONE}, - /* WOOD_CRAFT */ {200, 0, TOOL_NONE}, - /* STONE_CRAFT */ {200, 0, TOOL_NONE}, - /* BONE_CARVE */ {200, 0, TOOL_NONE}, - /* GLASSMAKER */ {200, 0, TOOL_NONE}, - /* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, - /* SIEGECRAFT */ {200, 0, TOOL_NONE}, - /* SIEGEOPERATE */ {200, 0, TOOL_NONE}, - /* BOWYER */ {200, 0, TOOL_NONE}, - /* MECHANIC */ {200, 0, TOOL_NONE}, - /* POTASH_MAKING */ {200, 0, TOOL_NONE}, - /* LYE_MAKING */ {200, 0, TOOL_NONE}, - /* DYER */ {200, 0, TOOL_NONE}, - /* BURN_WOOD */ {200, 0, TOOL_NONE}, - /* OPERATE_PUMP */ {200, 0, TOOL_NONE}, - /* SHEARER */ {200, 0, TOOL_NONE}, - /* SPINNER */ {200, 0, TOOL_NONE}, - /* POTTERY */ {200, 0, TOOL_NONE}, - /* GLAZING */ {200, 0, TOOL_NONE}, - /* PRESSING */ {200, 0, TOOL_NONE}, - /* BEEKEEPING */ {200, 0, TOOL_NONE}, - /* WAX_WORKING */ {200, 0, TOOL_NONE}, - /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE} -}; - -struct dwarf_info_t -{ - df::unit* dwarf; - dwarf_state state; - - bool clear_all; - - bool has_tool[TOOLS_MAX]; - - int high_skill; - - bool has_children; - bool armed; - - df::unit_labor using_labor; - - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), - state(OTHER), high_skill(0), has_children(false), armed(false) - { - for (int e = TOOL_NONE; e < TOOLS_MAX; e++) - has_tool[e] = false; - } - - void set_labor(df::unit_labor labor) - { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) - { - dwarf->status.labors[labor] = true; - } - } - - void clear_labor(df::unit_labor labor) - { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) - { - dwarf->status.labors[labor] = false; - } - } - -}; - -/* -* Here starts all the complicated stuff to try to deduce labors from jobs. -* This is all way more complicated than it really ought to be, but I have -* not found a way to make it simpler. -*/ - -static df::unit_labor hauling_labor_map[] = -{ - df::unit_labor::HAUL_ITEM, /* BAR */ - df::unit_labor::HAUL_STONE, /* SMALLGEM */ - df::unit_labor::HAUL_ITEM, /* BLOCKS */ - df::unit_labor::HAUL_STONE, /* ROUGH */ - df::unit_labor::HAUL_STONE, /* BOULDER */ - df::unit_labor::HAUL_WOOD, /* WOOD */ - df::unit_labor::HAUL_FURNITURE, /* DOOR */ - df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ - df::unit_labor::HAUL_FURNITURE, /* BED */ - df::unit_labor::HAUL_FURNITURE, /* CHAIR */ - df::unit_labor::HAUL_ITEM, /* CHAIN */ - df::unit_labor::HAUL_ITEM, /* FLASK */ - df::unit_labor::HAUL_ITEM, /* GOBLET */ - df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ - df::unit_labor::HAUL_ITEM, /* TOY */ - df::unit_labor::HAUL_FURNITURE, /* WINDOW */ - df::unit_labor::HAUL_ANIMAL, /* CAGE */ - df::unit_labor::HAUL_ITEM, /* BARREL */ - df::unit_labor::HAUL_ITEM, /* BUCKET */ - df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ - df::unit_labor::HAUL_FURNITURE, /* TABLE */ - df::unit_labor::HAUL_FURNITURE, /* COFFIN */ - df::unit_labor::HAUL_FURNITURE, /* STATUE */ - df::unit_labor::HAUL_REFUSE, /* CORPSE */ - df::unit_labor::HAUL_ITEM, /* WEAPON */ - df::unit_labor::HAUL_ITEM, /* ARMOR */ - df::unit_labor::HAUL_ITEM, /* SHOES */ - df::unit_labor::HAUL_ITEM, /* SHIELD */ - df::unit_labor::HAUL_ITEM, /* HELM */ - df::unit_labor::HAUL_ITEM, /* GLOVES */ - df::unit_labor::HAUL_FURNITURE, /* BOX */ - df::unit_labor::HAUL_ITEM, /* BIN */ - df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ - df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ - df::unit_labor::HAUL_FURNITURE, /* CABINET */ - df::unit_labor::HAUL_ITEM, /* FIGURINE */ - df::unit_labor::HAUL_ITEM, /* AMULET */ - df::unit_labor::HAUL_ITEM, /* SCEPTER */ - df::unit_labor::HAUL_ITEM, /* AMMO */ - df::unit_labor::HAUL_ITEM, /* CROWN */ - df::unit_labor::HAUL_ITEM, /* RING */ - df::unit_labor::HAUL_ITEM, /* EARRING */ - df::unit_labor::HAUL_ITEM, /* BRACELET */ - df::unit_labor::HAUL_ITEM, /* GEM */ - df::unit_labor::HAUL_FURNITURE, /* ANVIL */ - df::unit_labor::HAUL_REFUSE, /* CORPSEPIECE */ - df::unit_labor::HAUL_REFUSE, /* REMAINS */ - df::unit_labor::HAUL_FOOD, /* MEAT */ - df::unit_labor::HAUL_FOOD, /* FISH */ - df::unit_labor::HAUL_FOOD, /* FISH_RAW */ - df::unit_labor::HAUL_REFUSE, /* VERMIN */ - df::unit_labor::HAUL_ITEM, /* PET */ - df::unit_labor::HAUL_FOOD, /* SEEDS */ - df::unit_labor::HAUL_FOOD, /* PLANT */ - df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ - df::unit_labor::HAUL_FOOD, /* LEAVES */ - df::unit_labor::HAUL_ITEM, /* THREAD */ - df::unit_labor::HAUL_ITEM, /* CLOTH */ - df::unit_labor::HAUL_ITEM, /* TOTEM */ - df::unit_labor::HAUL_ITEM, /* PANTS */ - df::unit_labor::HAUL_ITEM, /* BACKPACK */ - df::unit_labor::HAUL_ITEM, /* QUIVER */ - df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ - df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ - df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ - df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ - df::unit_labor::HAUL_FOOD, /* DRINK */ - df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ - df::unit_labor::HAUL_FOOD, /* CHEESE */ - df::unit_labor::HAUL_FOOD, /* FOOD */ - df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ - df::unit_labor::HAUL_ITEM, /* COIN */ - df::unit_labor::HAUL_FOOD, /* GLOB */ - df::unit_labor::HAUL_STONE, /* ROCK */ - df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ - df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ - df::unit_labor::HAUL_FURNITURE, /* GRATE */ - df::unit_labor::HAUL_FURNITURE, /* QUERN */ - df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ - df::unit_labor::HAUL_ITEM, /* SPLINT */ - df::unit_labor::HAUL_ITEM, /* CRUTCH */ - df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ - df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ - df::unit_labor::HAUL_ITEM, /* TOOL */ - df::unit_labor::HAUL_FURNITURE, /* SLAB */ - df::unit_labor::HAUL_FOOD, /* EGG */ - df::unit_labor::HAUL_ITEM, /* BOOK */ -}; - -static df::unit_labor workshop_build_labor[] = -{ - /* Carpenters */ df::unit_labor::CARPENTER, - /* Farmers */ df::unit_labor::PROCESS_PLANT, - /* Masons */ df::unit_labor::MASON, - /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, - /* Jewelers */ df::unit_labor::CUT_GEM, - /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, - /* MagmaForge */ df::unit_labor::METAL_CRAFT, - /* Bowyers */ df::unit_labor::BOWYER, - /* Mechanics */ df::unit_labor::MECHANIC, - /* Siege */ df::unit_labor::SIEGECRAFT, - /* Butchers */ df::unit_labor::BUTCHER, - /* Leatherworks */ df::unit_labor::LEATHER, - /* Tanners */ df::unit_labor::TANNER, - /* Clothiers */ df::unit_labor::CLOTHESMAKER, - /* Fishery */ df::unit_labor::CLEAN_FISH, - /* Still */ df::unit_labor::BREWER, - /* Loom */ df::unit_labor::WEAVER, - /* Quern */ df::unit_labor::MILLER, - /* Kennels */ df::unit_labor::ANIMALTRAIN, - /* Kitchen */ df::unit_labor::COOK, - /* Ashery */ df::unit_labor::LYE_MAKING, - /* Dyers */ df::unit_labor::DYER, - /* Millstone */ df::unit_labor::MILLER, - /* Custom */ df::unit_labor::NONE, - /* Tool */ df::unit_labor::NONE -}; - -static df::building* get_building_from_job(df::job* j) -{ - for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) - { - if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) - { - int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; - df::building* bld = binsearch_in_vector(world->buildings.all, id); - return bld; - } - } - return 0; -} - -static df::unit_labor construction_build_labor (df::item* i) -{ - MaterialInfo matinfo; - if (i && matinfo.decode(i)) - { - if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) - return df::unit_labor::METAL_CRAFT; - if (matinfo.material->flags.is_set(df::material_flags::WOOD)) - return df::unit_labor::CARPENTER; - } - return df::unit_labor::MASON; -} - -color_ostream* debug_stream; - -void debug (char* fmt, ...) -{ - if (debug_stream) - { - va_list args; - va_start(args,fmt); - debug_stream->vprint(fmt, args); - va_end(args); - } -} - - -class JobLaborMapper { - -private: - class jlfunc - { - public: - virtual df::unit_labor get_labor(df::job* j) = 0; - }; - - class jlfunc_const : public jlfunc - { - private: - df::unit_labor labor; - public: - df::unit_labor get_labor(df::job* j) - { - return labor; - } - jlfunc_const(df::unit_labor l) : labor(l) {}; - }; - - class jlfunc_hauling : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - df::item* item = 0; - if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) - return (df::unit_labor) j->item_subtype; - - for (auto i = j->items.begin(); i != j->items.end(); i++) - { - if ((*i)->role == 7) - { - item = (*i)->item; - break; - } - } - - if (item && item->flags.bits.container) - { - for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) - { - if ((*a)->getType() == df::general_ref_type::CONTAINS_ITEM) - { - int item_id = ((df::general_ref_contains_itemst *) (*a))->item_id; - item = binsearch_in_vector(world->items.all, item_id); - break; - } - } - } - - df::unit_labor l = item ? hauling_labor_map[item->getType()] : df::unit_labor::HAUL_ITEM; - if (item && l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) - l = df::unit_labor::HAUL_BODY; - return l; - } - jlfunc_hauling() {}; - }; - - class jlfunc_construct_bld : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - if (j->flags.bits.item_lost) - return df::unit_labor::NONE; - - df::building* bld = get_building_from_job (j); - switch (bld->getType()) - { - case df::building_type::Hive: - return df::unit_labor::BEEKEEPING; - case df::building_type::Workshop: - { - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->design && !ws->design->flags.bits.designed) - return df::unit_labor::ARCHITECT; - if (ws->type == df::workshop_type::Custom) - { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - } - break; - case df::building_type::Furnace: - case df::building_type::TradeDepot: - case df::building_type::Construction: - case df::building_type::Bridge: - case df::building_type::ArcheryTarget: - case df::building_type::WaterWheel: - case df::building_type::RoadPaved: - case df::building_type::Well: - case df::building_type::ScrewPump: - case df::building_type::Wagon: - case df::building_type::Shop: - case df::building_type::Support: - case df::building_type::Windmill: - { - df::building_actual* b = (df::building_actual*) bld; - if (b->design && !b->design->flags.bits.designed) - return df::unit_labor::ARCHITECT; - return construction_build_labor(j->items[0]->item); - } - break; - case df::building_type::FarmPlot: - return df::unit_labor::PLANT; - case df::building_type::Chair: - case df::building_type::Bed: - case df::building_type::Table: - case df::building_type::Coffin: - case df::building_type::Door: - case df::building_type::Floodgate: - case df::building_type::Box: - case df::building_type::Weaponrack: - case df::building_type::Armorstand: - case df::building_type::Cabinet: - case df::building_type::Statue: - case df::building_type::WindowGlass: - case df::building_type::WindowGem: - case df::building_type::Cage: - case df::building_type::NestBox: - case df::building_type::TractionBench: - case df::building_type::Slab: - case df::building_type::Chain: - case df::building_type::GrateFloor: - case df::building_type::Hatch: - case df::building_type::BarsFloor: - case df::building_type::BarsVertical: - case df::building_type::GrateWall: - return df::unit_labor::HAUL_FURNITURE; - case df::building_type::Trap: - case df::building_type::GearAssembly: - case df::building_type::AxleHorizontal: - case df::building_type::AxleVertical: - case df::building_type::Rollers: - return df::unit_labor::MECHANIC; - case df::building_type::AnimalTrap: - return df::unit_labor::TRAPPER; - case df::building_type::Civzone: - case df::building_type::Nest: - case df::building_type::RoadDirt: - case df::building_type::Stockpile: - case df::building_type::Weapon: - return df::unit_labor::NONE; - case df::building_type::SiegeEngine: - return df::unit_labor::SIEGECRAFT; - } - - debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", - ENUM_KEY_STR(building_type, bld->getType()).c_str()); - - return df::unit_labor::NONE; - } - jlfunc_construct_bld() {} - }; - - class jlfunc_destroy_bld : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job (j); - df::building_type type = bld->getType(); - - switch (bld->getType()) - { - case df::building_type::Hive: - return df::unit_labor::BEEKEEPING; - case df::building_type::Workshop: - { - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->type == df::workshop_type::Custom) - { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - } - break; - case df::building_type::Furnace: - case df::building_type::TradeDepot: - case df::building_type::Construction: - case df::building_type::Wagon: - case df::building_type::Bridge: - case df::building_type::ScrewPump: - case df::building_type::ArcheryTarget: - case df::building_type::RoadPaved: - case df::building_type::Shop: - case df::building_type::Support: - case df::building_type::WaterWheel: - case df::building_type::Well: - case df::building_type::Windmill: - { - df::building_actual* b = (df::building_actual*) bld; - return construction_build_labor(b->contained_items[0]->item); - } - break; - case df::building_type::FarmPlot: - return df::unit_labor::PLANT; - case df::building_type::Trap: - case df::building_type::AxleHorizontal: - case df::building_type::AxleVertical: - case df::building_type::GearAssembly: - case df::building_type::Rollers: - return df::unit_labor::MECHANIC; - case df::building_type::Chair: - case df::building_type::Bed: - case df::building_type::Table: - case df::building_type::Coffin: - case df::building_type::Door: - case df::building_type::Floodgate: - case df::building_type::Box: - case df::building_type::Weaponrack: - case df::building_type::Armorstand: - case df::building_type::Cabinet: - case df::building_type::Statue: - case df::building_type::WindowGlass: - case df::building_type::WindowGem: - case df::building_type::Cage: - case df::building_type::NestBox: - case df::building_type::TractionBench: - case df::building_type::Slab: - case df::building_type::Chain: - case df::building_type::Hatch: - case df::building_type::BarsFloor: - case df::building_type::BarsVertical: - case df::building_type::GrateFloor: - case df::building_type::GrateWall: - return df::unit_labor::HAUL_FURNITURE; - case df::building_type::AnimalTrap: - return df::unit_labor::TRAPPER; - case df::building_type::Civzone: - case df::building_type::Nest: - case df::building_type::RoadDirt: - case df::building_type::Stockpile: - case df::building_type::Weapon: - return df::unit_labor::NONE; - case df::building_type::SiegeEngine: - return df::unit_labor::SIEGECRAFT; - } - - debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", - ENUM_KEY_STR(building_type, bld->getType()).c_str()); - - return df::unit_labor::NONE; - } - jlfunc_destroy_bld() {} - }; - - class jlfunc_make : public jlfunc - { - private: - df::unit_labor metaltype; - public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job(j); - if (bld->getType() == df::building_type::Workshop) - { - df::workshop_type type = ((df::building_workshopst*)(bld))->type; - switch (type) - { - case df::workshop_type::Craftsdwarfs: - { - df::item_type jobitem = j->job_items[0]->item_type; - switch (jobitem) - { - case df::item_type::BOULDER: - return df::unit_labor::STONE_CRAFT; - case df::item_type::NONE: - if (j->material_category.bits.bone || - j->material_category.bits.horn || - j->material_category.bits.tooth) - return df::unit_labor::BONE_CARVE; - else - { - debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); - return df::unit_labor::NONE; - } - case df::item_type::WOOD: - return df::unit_labor::WOOD_CRAFT; - default: - debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", - ENUM_KEY_STR(item_type, jobitem).c_str()); - return df::unit_labor::NONE; - } - } - case df::workshop_type::Masons: - return df::unit_labor::MASON; - case df::workshop_type::Carpenters: - return df::unit_labor::CARPENTER; - case df::workshop_type::Leatherworks: - return df::unit_labor::LEATHER; - case df::workshop_type::Clothiers: - return df::unit_labor::CLOTHESMAKER; - case df::workshop_type::MagmaForge: - case df::workshop_type::MetalsmithsForge: - return metaltype; - default: - debug ("AUTOLABOR: Cannot deduce labor for make job, workshop type %s\n", - ENUM_KEY_STR(workshop_type, type).c_str()); - return df::unit_labor::NONE; - } - } - else if (bld->getType() == df::building_type::Furnace) - { - df::furnace_type type = ((df::building_furnacest*)(bld))->type; - switch (type) - { - case df::furnace_type::MagmaGlassFurnace: - case df::furnace_type::GlassFurnace: - return df::unit_labor::GLASSMAKER; - default: - debug ("AUTOLABOR: Cannot deduce labor for make job, furnace type %s\n", - ENUM_KEY_STR(furnace_type, type).c_str()); - return df::unit_labor::NONE; - } - } - - debug ("AUTOLABOR: Cannot deduce labor for make job, building type %s\n", - ENUM_KEY_STR(building_type, bld->getType()).c_str()); - - return df::unit_labor::NONE; - } - - jlfunc_make (df::unit_labor mt) : metaltype(mt) {} - }; - - class jlfunc_custom : public jlfunc - { - public: - df::unit_labor get_labor(df::job* j) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); - return labor; - } - } - return df::unit_labor::NONE; - } - jlfunc_custom() {} - }; - - map jlf_cache; - - jlfunc* jlf_const(df::unit_labor l) { - jlfunc* jlf; - if (jlf_cache.count(l) == 0) - { - jlf = new jlfunc_const(l); - jlf_cache[l] = jlf; - } - else - jlf = jlf_cache[l]; - - return jlf; - } -private: - std::map job_to_labor_table; - -public: - ~JobLaborMapper() - { - std::set log; - - for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) - { - if (!log.count(i->second)) - { - log.insert(i->second); - delete i->second; - } - i->second = 0; - } - - FOR_ENUM_ITEMS (job_type, j) - { - if (j < 0) - continue; - - jlfunc* p = job_to_labor_table[j]; - if (!log.count(p)) - { - log.insert(p); - delete p; - } - job_to_labor_table[j] = 0; - } - } - - JobLaborMapper() - { - jlfunc* jlf_hauling = new jlfunc_hauling(); - jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); - jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); - jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); - jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); - - jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); - - job_to_labor_table[df::job_type::CarveFortification] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::DetailWall] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::DetailFloor] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::Dig] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveUpwardStaircase] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveDownwardStaircase] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveUpDownStaircase] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::CarveRamp] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); - job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); - job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); - job_to_labor_table[df::job_type::BringItemToDepot] = jlf_no_labor; - job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; - job_to_labor_table[df::job_type::Eat] = jlf_no_labor; - job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; - job_to_labor_table[df::job_type::Drink] = jlf_no_labor; - job_to_labor_table[df::job_type::Drink2] = jlf_no_labor; - job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; - job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; - job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::HAUL_ITEM); - job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); - job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); - job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; - job_to_labor_table[df::job_type::Kidnap] = jlf_no_labor; - job_to_labor_table[df::job_type::BeatCriminal] = jlf_no_labor; - job_to_labor_table[df::job_type::StartingFistFight] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectTaxes] = jlf_no_labor; - job_to_labor_table[df::job_type::GuardTaxCollector] = jlf_no_labor; - job_to_labor_table[df::job_type::CatchLiveLandAnimal] = jlf_const(df::unit_labor::HUNT); - job_to_labor_table[df::job_type::CatchLiveFish] = jlf_const(df::unit_labor::FISH); - job_to_labor_table[df::job_type::ReturnKill] = jlf_no_labor; - job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; - job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; - job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); - job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInChest] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInCabinet] = jlf_hauling; - job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; - job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_const(df::unit_labor::HAUL_ITEM); - job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; - job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; - job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; - job_to_labor_table[df::job_type::GoShopping] = jlf_no_labor; - job_to_labor_table[df::job_type::GoShopping2] = jlf_no_labor; - job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); - job_to_labor_table[df::job_type::Rest] = jlf_no_labor; - job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; - job_to_labor_table[df::job_type::DumpItem] = jlf_const(df::unit_labor::HAUL_REFUSE); - job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodMagmaForge] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodBrooding] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodFell] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodCarpenter] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodMason] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodBowyer] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodTanner] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodWeaver] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodGlassmaker] = jlf_no_labor; - job_to_labor_table[df::job_type::StrangeMoodMechanics] = jlf_no_labor; - job_to_labor_table[df::job_type::ConstructBuilding] = new jlfunc_construct_bld(); - job_to_labor_table[df::job_type::ConstructDoor] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructFloodgate] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructBed] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructThrone] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCoffin] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructTable] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructChest] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructBin] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructArmorStand] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructWeaponRack] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCabinet] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); - job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; - job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); - job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); - job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); - job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); - job_to_labor_table[df::job_type::EncrustWithGlass] = jlf_const(df::unit_labor::ENCRUST_GEM); - job_to_labor_table[df::job_type::DestroyBuilding] = new jlfunc_destroy_bld(); - job_to_labor_table[df::job_type::SmeltOre] = jlf_const(df::unit_labor::SMELT); - job_to_labor_table[df::job_type::MeltMetalObject] = jlf_const(df::unit_labor::SMELT); - job_to_labor_table[df::job_type::ExtractMetalStrands] = jlf_const(df::unit_labor::EXTRACT_STRAND); - job_to_labor_table[df::job_type::PlantSeeds] = jlf_const(df::unit_labor::PLANT); - job_to_labor_table[df::job_type::HarvestPlants] = jlf_const(df::unit_labor::PLANT); - job_to_labor_table[df::job_type::TrainHuntingAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); - job_to_labor_table[df::job_type::TrainWarAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); - job_to_labor_table[df::job_type::MakeWeapon] = jlf_make_weapon; - job_to_labor_table[df::job_type::ForgeAnvil] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCatapultParts] = jlf_const(df::unit_labor::SIEGECRAFT); - job_to_labor_table[df::job_type::ConstructBallistaParts] = jlf_const(df::unit_labor::SIEGECRAFT); - job_to_labor_table[df::job_type::MakeArmor] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeHelm] = jlf_make_armor; - job_to_labor_table[df::job_type::MakePants] = jlf_make_armor; - job_to_labor_table[df::job_type::StudWith] = jlf_make_object; - job_to_labor_table[df::job_type::ButcherAnimal] = jlf_const(df::unit_labor::BUTCHER); - job_to_labor_table[df::job_type::PrepareRawFish] = jlf_const(df::unit_labor::CLEAN_FISH); - job_to_labor_table[df::job_type::MillPlants] = jlf_const(df::unit_labor::MILLER); - job_to_labor_table[df::job_type::BaitTrap] = jlf_const(df::unit_labor::TRAPPER); - job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); - job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); - job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsBag] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); - job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); - job_to_labor_table[df::job_type::WeaveCloth] = jlf_const(df::unit_labor::WEAVER); - job_to_labor_table[df::job_type::MakeGloves] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeShoes] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeShield] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeCage] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeChain] = jlf_make_object; - job_to_labor_table[df::job_type::MakeFlask] = jlf_make_object; - job_to_labor_table[df::job_type::MakeGoblet] = jlf_make_object; - job_to_labor_table[df::job_type::MakeToy] = jlf_make_object; - job_to_labor_table[df::job_type::MakeAnimalTrap] = jlf_const(df::unit_labor::TRAPPER); - job_to_labor_table[df::job_type::MakeBarrel] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeBucket] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeWindow] = jlf_make_furniture; - job_to_labor_table[df::job_type::MakeTotem] = jlf_const(df::unit_labor::BONE_CARVE); - job_to_labor_table[df::job_type::MakeAmmo] = jlf_make_weapon; - job_to_labor_table[df::job_type::DecorateWith] = jlf_make_object; - job_to_labor_table[df::job_type::MakeBackpack] = jlf_make_object; - job_to_labor_table[df::job_type::MakeQuiver] = jlf_make_armor; - job_to_labor_table[df::job_type::MakeBallistaArrowHead] = jlf_make_weapon; - job_to_labor_table[df::job_type::AssembleSiegeAmmo] = jlf_const(df::unit_labor::SIEGECRAFT); - job_to_labor_table[df::job_type::LoadCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::LoadBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); - job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); - job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; - job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; - job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; - job_to_labor_table[df::job_type::BrewDrink] = jlf_const(df::unit_labor::BREWER) ; - job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; - job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; - job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; - job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; - job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; - job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::UnchainAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; - job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::CageLargeCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; - job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); - job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; - job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING) ; - job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; - job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; - job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY) ; - job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); - job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING) ; - job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING) ; - job_to_labor_table[df::job_type::DrainAquarium] = jlf_no_labor; - job_to_labor_table[df::job_type::FillAquarium] = jlf_no_labor; - job_to_labor_table[df::job_type::FillPond] = jlf_no_labor; - job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; - job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; - job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; - job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; - job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; - job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); - job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); - job_to_labor_table[df::job_type::MakeAsh] = jlf_const(df::unit_labor::BURN_WOOD); - job_to_labor_table[df::job_type::MakeLye] = jlf_const(df::unit_labor::LYE_MAKING); - job_to_labor_table[df::job_type::MakePotashFromLye] = jlf_const(df::unit_labor::POTASH_MAKING); - job_to_labor_table[df::job_type::FertilizeField] = jlf_const(df::unit_labor::PLANT); - job_to_labor_table[df::job_type::MakePotashFromAsh] = jlf_const(df::unit_labor::POTASH_MAKING); - job_to_labor_table[df::job_type::DyeThread] = jlf_const(df::unit_labor::DYER); - job_to_labor_table[df::job_type::DyeCloth] = jlf_const(df::unit_labor::DYER); - job_to_labor_table[df::job_type::SewImage] = jlf_make_object; - job_to_labor_table[df::job_type::MakePipeSection] = jlf_make_furniture; - job_to_labor_table[df::job_type::OperatePump] = jlf_const(df::unit_labor::OPERATE_PUMP); - job_to_labor_table[df::job_type::ManageWorkOrders] = jlf_no_labor; - job_to_labor_table[df::job_type::UpdateStockpileRecords] = jlf_no_labor; - job_to_labor_table[df::job_type::TradeAtDepot] = jlf_no_labor; - job_to_labor_table[df::job_type::ConstructHatchCover] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; - job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); - job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); - job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; - job_to_labor_table[df::job_type::BringCrutch] = jlf_const(df::unit_labor::BONE_SETTING); - job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); - job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); - job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; - job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); - job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); - job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; - job_to_labor_table[df::job_type::MakeTool] = jlf_make_furniture; - job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); - job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); - job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); - job_to_labor_table[df::job_type::CauseTrouble] = jlf_no_labor; - job_to_labor_table[df::job_type::DrinkBlood] = jlf_no_labor; - job_to_labor_table[df::job_type::ReportCrime] = jlf_no_labor; - job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; - job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); - job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); - job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); - job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); - job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; - job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; - job_to_labor_table[df::job_type::MakeScepter] = jlf_make_object; - job_to_labor_table[df::job_type::MakeCrown] = jlf_make_object; - job_to_labor_table[df::job_type::MakeRing] = jlf_make_object; - job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; - job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; - job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; - }; - - df::unit_labor find_job_labor(df::job* j) - { - if (j->job_type == df::job_type::CustomReaction) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - return ENUM_ATTR(job_skill, labor, skill); - } - } - return df::unit_labor::NONE; - } - - - df::unit_labor labor; - labor = job_to_labor_table[j->job_type]->get_labor(j); - - return labor; - } -}; - -/* End of labor deducer */ - -static JobLaborMapper* labor_mapper = 0; - -static bool isOptionEnabled(unsigned flag) -{ - return config.isValid() && (config.ival(0) & flag) != 0; -} - -static void setOptionEnabled(ConfigFlags flag, bool on) -{ - if (!config.isValid()) - return; - - if (on) - config.ival(0) |= flag; - else - config.ival(0) &= ~flag; -} - -static void cleanup_state() -{ - enable_autolabor = false; - labor_infos.clear(); -} - -static void reset_labor(df::unit_labor labor) -{ - labor_infos[labor].set_priority(default_labor_infos[labor].priority); - labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); -} - -static void init_state() -{ - config = World::GetPersistentData("autolabor/2.0/config"); - if (config.isValid() && config.ival(0) == -1) - config.ival(0) = 0; - - enable_autolabor = isOptionEnabled(CF_ENABLED); - - if (!enable_autolabor) - return; - - // Load labors from save - labor_infos.resize(ARRAY_COUNT(default_labor_infos)); - - std::vector items; - World::GetPersistentData(&items, "autolabor/2.0/labors/", true); - - for (auto p = items.begin(); p != items.end(); p++) - { - string key = p->key(); - df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/2.0/labors/")).c_str()); - if (labor >= 0 && labor <= labor_infos.size()) - { - labor_infos[labor].config = *p; - labor_infos[labor].active_dwarfs = 0; - } - } - - // Add default labors for those not in save - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { - if (labor_infos[i].config.isValid()) - continue; - - std::stringstream name; - name << "autolabor/2.0/labors/" << i; - - labor_infos[i].config = World::AddPersistentData(name.str()); - labor_infos[i].mark_assigned(); - labor_infos[i].active_dwarfs = 0; - reset_labor((df::unit_labor) i); - } - -} - -static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; - -static void generate_labor_to_skill_map() -{ - // Generate labor -> skill mapping - - for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) - labor_to_skill[i] = job_skill::NONE; - - FOR_ENUM_ITEMS(job_skill, skill) - { - int labor = ENUM_ATTR(job_skill, labor, skill); - if (labor != unit_labor::NONE) - { - /* - assert(labor >= 0); - assert(labor < ARRAY_COUNT(labor_to_skill)); - */ - - labor_to_skill[labor] = skill; - } - } - -} - - -static void enable_plugin(color_ostream &out) -{ - if (!config.isValid()) - { - config = World::AddPersistentData("autolabor/2.0/config"); - config.ival(0) = 0; - } - - setOptionEnabled(CF_ENABLED, true); - enable_autolabor = true; - out << "Enabling the plugin." << endl; - - cleanup_state(); - init_state(); -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - // initialize labor infos table from default table - if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) - return CR_FAILURE; - - // Fill the command list with your commands. - commands.push_back(PluginCommand( - "autolabor2", "Automatically manage dwarf labors.", - autolabor, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " autolabor2 enable\n" - " autolabor2 disable\n" - " Enables or disables the plugin.\n" - " autolabor2 max \n" - " Set max number of dwarves assigned to a labor.\n" - " autolabor2 max none\n" - " Unrestrict the number of dwarves assigned to a labor.\n" - " autolabor2 priority \n" - " Change the assignment priority of a labor (default is 100)\n" - " autolabor2 reset \n" - " Return a labor to the default handling.\n" - " autolabor2 reset-all\n" - " Return all labors to the default handling.\n" - " autolabor2 list\n" - " List current status of all labors.\n" - " autolabor2 status\n" - " Show basic status information.\n" - "Function:\n" - " When enabled, autolabor periodically checks your dwarves and enables or\n" - " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" - " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled. Do not try to run both autolabor and autolabor2 at\n" - " the same time." - )); - - generate_labor_to_skill_map(); - - labor_mapper = new JobLaborMapper(); - - init_state(); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - cleanup_state(); - - delete labor_mapper; - - return CR_OK; -} - -class AutoLaborManager { - color_ostream& out; - -public: - AutoLaborManager(color_ostream& o) : out(o) - { - dwarf_info.clear(); - } - - ~AutoLaborManager() - { - for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) - { - delete (*d); - } - } - - dwarf_info_t* add_dwarf(df::unit* u) - { - dwarf_info_t* dwarf = new dwarf_info_t(u); - dwarf_info.push_back(dwarf); - return dwarf; - } - - -private: - bool has_butchers; - bool has_fishery; - bool trader_requested; - - int dig_count; - int tree_count; - int plant_count; - int detail_count; - - int tool_count[TOOLS_MAX]; - bool reequip_needed[TOOLS_MAX]; - - int cnt_recover_wounded; - int cnt_diagnosis; - int cnt_immobilize; - int cnt_dressing; - int cnt_cleaning; - int cnt_surgery; - int cnt_suture; - int cnt_setting; - int cnt_traction; - int cnt_crutch; - - int need_food_water; - - std::map labor_needed; - std::map labor_outside; - std::vector dwarf_info; - std::list available_dwarfs; - -private: - void scan_buildings() - { - for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) - { - df::building *build = *b; - auto type = build->getType(); - if (building_type::Workshop == type) - { - df::workshop_type subType = (df::workshop_type)build->getSubtype(); - if (workshop_type::Butchers == subType) - has_butchers = true; - if (workshop_type::Fishery == subType) - has_fishery = true; - } - else if (building_type::TradeDepot == type) - { - df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->trade_flags.bits.trader_requested; - if (print_debug) - { - if (trader_requested) - out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); - else - out.print("Trade depot found but trader is not requested.\n"); - } - } - } - } - - void count_map_designations() - { - dig_count = 0; - tree_count = 0; - plant_count = 0; - detail_count = 0; - - for (int i = 0; i < world->map.map_blocks.size(); ++i) - { - df::map_block* bl = world->map.map_blocks[i]; - - if (!bl->flags.bits.designated) - continue; - - for (int x = 0; x < 16; x++) - for (int y = 0; y < 16; y++) - { - if (bl->designation[x][y].bits.hidden) - { - df::coord p = bl->map_pos; - df::coord c(p.x, p.y, p.z-1); - if (Maps::getTileDesignation(c)->bits.hidden) - continue; - } - - df::tile_dig_designation dig = bl->designation[x][y].bits.dig; - if (dig != df::enums::tile_dig_designation::No) - { - df::tiletype tt = bl->tiletype[x][y]; - df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); - switch (tts) - { - case df::enums::tiletype_shape::TREE: - tree_count++; break; - case df::enums::tiletype_shape::SHRUB: - plant_count++; break; - default: - dig_count++; break; - } - } - if (bl->designation[x][y].bits.smooth != 0) - detail_count++; - } - } - - if (print_debug) - out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); - - } - - void count_tools() - { - for (int e = TOOL_NONE; e < TOOLS_MAX; e++) - tool_count[e] = 0; - - df::item_flags bad_flags; - bad_flags.whole = 0; - -#define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); -#undef F - - auto& v = world->items.all; - for (auto i = v.begin(); i != v.end(); i++) - { - df::item* item = *i; - - if (item->flags.bits.dump) - labor_needed[df::unit_labor::HAUL_REFUSE]++; - - if (item->flags.whole & bad_flags.whole) - continue; - - if (!item->isWeapon()) - continue; - - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - df::job_skill weaponsk2 = (df::job_skill) weapondef->skill_ranged; - if (weaponsk == df::job_skill::AXE) - tool_count[TOOL_AXE]++; - else if (weaponsk == df::job_skill::MINING) - tool_count[TOOL_PICK]++; - else if (weaponsk2 = df::job_skill::CROSSBOW) - tool_count[TOOL_CROSSBOW]++; - } - - } - - void collect_job_list() - { - labor_needed.clear(); - - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) - { - df::job* j = jll->item; - if (!j) - continue; - - if (j->flags.bits.suspend || j->flags.bits.item_lost) - continue; - - int worker = -1; - int bld = -1; - - for (int r = 0; r < j->general_refs.size(); ++r) - { - if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; - if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) - bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; - } - - if (bld != -1) - { - df::building* b = binsearch_in_vector(world->buildings.all, bld); - int fjid = -1; - for (int jn = 0; jn < b->jobs.size(); jn++) - { - if (b->jobs[jn]->flags.bits.suspend) - continue; - fjid = b->jobs[jn]->id; - break; - } - // check if this job is the first nonsuspended job on this building; if not, ignore it - // (except for farms) - if (fjid != j->id && b->getType() != df::building_type::FarmPlot) { - continue; - } - - } - - df::unit_labor labor = labor_mapper->find_job_labor (j); - - if (labor != df::unit_labor::NONE) - { - labor_needed[labor]++; - - if (worker != -1) - labor_infos[labor].mark_assigned(); - - if (j->pos.isValid()) - { - df::tile_designation* d = Maps::getTileDesignation(j->pos); - if (d->bits.outside) - labor_outside[labor] = true; - } - } - } - - } - - void collect_dwarf_list() - { - - for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) - { - df::unit* cre = *u; - - if (Units::isCitizen(cre)) - { - dwarf_info_t* dwarf = add_dwarf(cre); - - df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); - for (int i = 0; i < hf->entity_links.size(); i++) - { - df::histfig_entity_link* hfelink = hf->entity_links.at(i); - if (hfelink->getType() == df::histfig_entity_link_type::POSITION) - { - df::histfig_entity_link_positionst *epos = - (df::histfig_entity_link_positionst*) hfelink; - df::historical_entity* entity = df::historical_entity::find(epos->entity_id); - if (!entity) - continue; - df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); - if (!assignment) - continue; - df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); - if (!position) - continue; - - if (position->responsibilities[df::entity_position_responsibility::TRADE]) - if (trader_requested) - dwarf->clear_all = true; - } - - } - - // identify dwarfs who are needed for meetings and mark them for exclusion - - for (int i = 0; i < ui->activities.size(); ++i) - { - df::activity_info *act = ui->activities[i]; - if (!act) continue; - bool p1 = act->unit_actor == dwarf->dwarf; - bool p2 = act->unit_noble == dwarf->dwarf; - - if (p1 || p2) - { - dwarf->clear_all = true; - if (print_debug) - out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); - break; - } - } - - // check to see if dwarf has minor children - - for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2) - { - if ((*u2)->relations.mother_id == dwarf->dwarf->id && - !(*u2)->flags1.bits.dead && - ((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY)) - { - dwarf->has_children = true; - if (print_debug) - out.print("Dwarf %s has minor children\n", dwarf->dwarf->name.first_name.c_str()); - break; - } - } - - // Find the activity state for each dwarf - - bool is_on_break = false; - dwarf_state state = OTHER; - - for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; - } - - if (dwarf->dwarf->profession == profession::BABY || - dwarf->dwarf->profession == profession::CHILD || - dwarf->dwarf->profession == profession::DRUNK) - { - state = CHILD; - } - else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) - state = MILITARY; - else if (dwarf->dwarf->job.current_job == NULL) - { - if (is_on_break) - state = OTHER; - else if (dwarf->dwarf->burrows.size() > 0) - state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy - else if (dwarf->dwarf->status2.limbs_grasp_count == 0) - { - state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors - dwarf->clear_all = true; - if (print_debug) - out.print ("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); - } - else - { - state = IDLE; - } - } - else - { - df::job_type job = dwarf->dwarf->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) - state = dwarf_states[job]; - else - { - out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); - state = OTHER; - } - if (state == BUSY) - { - df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); - if (labor != df::unit_labor::NONE) - { - dwarf->using_labor = labor; - - if (!dwarf->dwarf->status.labors[labor] && print_debug) - { - out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", - dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, - ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); - } - } - } - } - - dwarf->state = state; - - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - if (dwarf->dwarf->status.labors[l]) - if (state == IDLE) - labor_infos[l].idle_dwarfs++; - else if (state == BUSY) - labor_infos[l].busy_dwarfs++; - } - - - if (print_debug) - out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); - - // determine if dwarf has medical needs - if (dwarf->dwarf->health) - { - if (dwarf->dwarf->health->flags.bits.needs_recovery) - cnt_recover_wounded++; - if (dwarf->dwarf->health->flags.bits.rq_diagnosis) - cnt_diagnosis++; - if (dwarf->dwarf->health->flags.bits.rq_immobilize) - cnt_immobilize++; - if (dwarf->dwarf->health->flags.bits.rq_dressing) - cnt_dressing++; - if (dwarf->dwarf->health->flags.bits.rq_cleaning) - cnt_cleaning++; - if (dwarf->dwarf->health->flags.bits.rq_surgery) - cnt_surgery++; - if (dwarf->dwarf->health->flags.bits.rq_suture) - cnt_suture++; - if (dwarf->dwarf->health->flags.bits.rq_setting) - cnt_setting++; - if (dwarf->dwarf->health->flags.bits.rq_traction) - cnt_traction++; - if (dwarf->dwarf->health->flags.bits.rq_crutch) - cnt_crutch++; - } - - if (dwarf->dwarf->counters2.hunger_timer > 60000 || dwarf->dwarf->counters2.thirst_timer > 40000) - need_food_water++; - - // find dwarf's highest effective skill - - int high_skill = 0; - - FOR_ENUM_ITEMS (unit_labor, labor) - { - if (labor == df::unit_labor::NONE) - continue; - - df::job_skill skill = labor_to_skill[labor]; - if (skill != df::job_skill::NONE) - { - int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); - high_skill = std::max(high_skill, skill_level); - } - } - - dwarf->high_skill = high_skill; - // check if dwarf has an axe, pick, or crossbow - - for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) - { - df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; - if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) - { - dwarf->armed = true; - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; - if (weaponsk == df::job_skill::AXE) - { - dwarf->has_tool[TOOL_AXE] = true; - if (state != IDLE) - tool_count[TOOL_AXE]--; - } - else if (weaponsk == df::job_skill::MINING) - { - dwarf->has_tool[TOOL_PICK] = 1; - if (state != IDLE) - tool_count[TOOL_PICK]--; - } - else if (rangesk == df::job_skill::CROSSBOW) - { - dwarf->has_tool[TOOL_CROSSBOW] = 1; - if (state != IDLE) - tool_count[TOOL_CROSSBOW]--; - } - } - } - - // clear labors of dwarfs with clear_all set - - if (dwarf->clear_all) - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - dwarf->clear_labor(labor); - } - } - else if (state == IDLE || state == BUSY) - available_dwarfs.push_back(dwarf); - - } - - } - } - -public: - void process() - { - dwarf_info.clear(); - - dig_count = tree_count = plant_count = detail_count = 0; - cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = - cnt_setting = cnt_traction = cnt_crutch = 0; - need_food_water = 0; - - for (int e = 0; e < TOOLS_MAX; e++) - tool_count[e] = 0; - - trader_requested = false; - - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - - labor_infos[l].active_dwarfs = labor_infos[l].busy_dwarfs = labor_infos[l].idle_dwarfs = 0; - } - - // scan for specific buildings of interest - - scan_buildings(); - - // count number of squares designated for dig, wood cutting, detailing, and plant harvesting - - count_map_designations(); - - // collect current job list - - collect_job_list(); - - // count number of picks and axes available for use - - count_tools(); - - // collect list of dwarfs - - collect_dwarf_list(); - - // add job entries for designation-related jobs - - labor_needed[df::unit_labor::MINE] += std::min(tool_count[TOOL_PICK], dig_count); - labor_needed[df::unit_labor::CUTWOOD] += std::min(tool_count[TOOL_AXE], tree_count); - labor_needed[df::unit_labor::DETAIL] += detail_count; - labor_needed[df::unit_labor::HERBALIST] += plant_count; - - // add job entries for health care - - labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; - labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; - labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; - labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; - labor_needed[df::unit_labor::SURGERY] += cnt_surgery; - labor_needed[df::unit_labor::SUTURING] += cnt_suture; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; - labor_needed[df::unit_labor::BONE_SETTING] += cnt_crutch; - - labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; - - // add entries for hauling jobs - - labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; - labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; - labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; - labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[4]; - labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; - labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; - labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; - labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; - labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; - - // add entries for vehicle hauling - - for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) - if ((*v)->route_id != -1) - labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; - - // add fishing & hunting - - labor_needed[df::unit_labor::FISH] = - (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) ? 1 : 0; - - labor_needed[df::unit_labor::HUNT] = - (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) ? 1 : 0; - - /* add animal trainers */ - for (auto a = df::global::ui->equipment.training_assignments.begin(); - a != df::global::ui->equipment.training_assignments.end(); - a++) - { - labor_needed[df::unit_labor::ANIMALTRAIN]++; - // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. - } - - /* adjust for over/under */ - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL) - continue; - if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) - { - int clawback = labor_infos[l].busy_dwarfs; - if (clawback == 0 && labor_needed[l] > 0) - clawback = 1; - - if (print_debug) - out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), - clawback, - labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); - labor_needed[l] = clawback; - } - } - - if (print_debug) - { - for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) - { - out.print ("labor_needed [%s] = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, - labor_outside[i->first], labor_infos[i->first].idle_dwarfs); - } - } - - priority_queue> pq; - - for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) - { - df::unit_labor l = i->first; - if (labor_infos[l].maximum_dwarfs() > 0 && - i->second > labor_infos[l].maximum_dwarfs()) - i->second = labor_infos[l].maximum_dwarfs(); - if (i->second > 0) - { - int priority = labor_infos[l].priority(); - priority += labor_infos[l].time_since_last_assigned()/12; - pq.push(make_pair(priority, l)); - } - } - - if (print_debug) - out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); - - std::map to_assign; - - to_assign.clear(); - - int av = available_dwarfs.size(); - - while (!pq.empty() && av > 0) - { - df::unit_labor labor = pq.top().second; - int priority = pq.top().first; - to_assign[labor]++; - pq.pop(); - av--; - - if (print_debug) - out.print("Will assign: %s priority %d (%d)\n", ENUM_KEY_STR(unit_labor, labor).c_str(), priority, to_assign[labor]); - - if (--labor_needed[labor] > 0) - { - priority /= 2; - pq.push(make_pair(priority, labor)); - } - - } - - int canary = (1 << df::unit_labor::HAUL_STONE) | - (1 << df::unit_labor::HAUL_WOOD) | - (1 << df::unit_labor::HAUL_BODY) | - (1 << df::unit_labor::HAUL_FOOD) | - (1 << df::unit_labor::HAUL_REFUSE) | - (1 << df::unit_labor::HAUL_ITEM) | - (1 << df::unit_labor::HAUL_FURNITURE) | - (1 << df::unit_labor::HAUL_ANIMAL); - - while (!available_dwarfs.empty()) - { - std::list::iterator bestdwarf = available_dwarfs.begin(); - - int best_score = INT_MIN; - df::unit_labor best_labor = df::unit_labor::CLEAN; - - for (auto j = to_assign.begin(); j != to_assign.end(); j++) - { - if (j->second <= 0) - continue; - - df::unit_labor labor = j->first; - df::job_skill skill = labor_to_skill[labor]; - - for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) - { - dwarf_info_t* d = (*k); - int skill_level = 0; - int xp = 0; - if (skill != df::job_skill::NONE) - { - skill_level = Units::getEffectiveSkill(d->dwarf, skill); - xp = Units::getExperience(d->dwarf, skill, false); - } - int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); - if (d->dwarf->status.labors[labor]) - if (labor == df::unit_labor::OPERATE_PUMP) - score += 50000; - else - score += 1000; - if (default_labor_infos[labor].tool != TOOL_NONE && - d->has_tool[default_labor_infos[labor].tool]) - score += 5000; - if (d->has_children && labor_outside[labor]) - score -= 15000; - if (d->armed && labor_outside[labor]) - score += 5000; - if (d->state == BUSY) - if (d->using_labor == labor) - score += 7500; - else - score -= 7500; - if (score > best_score) - { - bestdwarf = k; - best_score = score; - best_labor = labor; - } - } - } - - if (print_debug) - out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); - - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - - if (l == best_labor) - { - (*bestdwarf)->set_labor(l); - tools_enum t = default_labor_infos[l].tool; - if (t != TOOL_NONE) - { - tool_count[t]--; - if (!(*bestdwarf)->has_tool[t]) - (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; - } - } - else if ((*bestdwarf)->state == IDLE) - (*bestdwarf)->clear_labor(l); - } - - if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) - canary &= ~(1 << best_labor); - - if (best_labor != df::unit_labor::NONE) - { - labor_infos[best_labor].active_dwarfs++; - to_assign[best_labor]--; - } - - available_dwarfs.erase(bestdwarf); - } - - if (canary != 0) - { - dwarf_info_t* d = dwarf_info.front(); - FOR_ENUM_ITEMS (unit_labor, l) - { - if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL && - canary & (1 << l)) - d->set_labor(l); - } - if (print_debug) - out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); - } - - /* set reequip on any dwarfs who are carrying tools needed by others */ - - for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) - { - FOR_ENUM_ITEMS (unit_labor, l) - { - tools_enum t = default_labor_infos[l].tool; - if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) - { - tool_count[t]++; - (*d)->dwarf->military.pickup_flags.bits.update = 1; - } - } - } - - *df::global::process_jobs = true; - - print_debug = 0; - - } - -}; - - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - cleanup_state(); - init_state(); - break; - case SC_MAP_UNLOADED: - cleanup_state(); - break; - default: - break; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - static int step_count = 0; - // check run conditions - if(!world || !world->map.block_index || !enable_autolabor) - { - // give up if we shouldn't be running' - return CR_OK; - } - - if (++step_count < 60) - return CR_OK; - step_count = 0; - - debug_stream = &out; - AutoLaborManager alm(out); - alm.process(); - - return CR_OK; -} - -void print_labor (df::unit_labor labor, color_ostream &out) -{ - string labor_name = ENUM_KEY_STR(unit_labor, labor); - out << labor_name << ": "; - for (int i = 0; i < 20 - (int)labor_name.length(); i++) - out << ' '; - out << "priority " << labor_infos[labor].priority() - << ", maximum " << labor_infos[labor].maximum_dwarfs() - << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs (" - << labor_infos[labor].busy_dwarfs << " busy, " - << labor_infos[labor].idle_dwarfs << " idle)" - << endl; -} - -df::unit_labor lookup_labor_by_name (std::string& name) -{ - df::unit_labor labor = df::unit_labor::NONE; - - FOR_ENUM_ITEMS(unit_labor, test_labor) - { - if (name == ENUM_KEY_STR(unit_labor, test_labor)) - labor = test_labor; - } - - return labor; -} - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) -{ - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } - - if (enable && !enable_autolabor) - { - enable_plugin(out); - } - else if(!enable && enable_autolabor) - { - enable_autolabor = false; - setOptionEnabled(CF_ENABLED, false); - - out << "Autolabor is disabled." << endl; - } - - return CR_OK; -} - -command_result autolabor (color_ostream &out, std::vector & parameters) -{ - CoreSuspender suspend; - - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } - - if (parameters.size() == 1 && - (parameters[0] == "enable" || parameters[0] == "disable")) - { - bool enable = (parameters[0] == "enable"); - return plugin_enable(out, enable); - } - else if (parameters.size() == 3 && - (parameters[0] == "max" || parameters[0] == "priority")) - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - df::unit_labor labor = lookup_labor_by_name(parameters[1]); - - if (labor == df::unit_labor::NONE) - { - out.printerr("Could not find labor %s.\n", parameters[0].c_str()); - return CR_WRONG_USAGE; - } - - int v; - - if (parameters[2] == "none") - v = 0; - else - v = atoi (parameters[2].c_str()); - - if (parameters[0] == "max") - labor_infos[labor].set_maximum_dwarfs(v); - else if (parameters[0] == "priority") - labor_infos[labor].set_priority(v); - - print_labor(labor, out); - return CR_OK; - } - else if (parameters.size() == 2 && parameters[0] == "reset") - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - df::unit_labor labor = lookup_labor_by_name(parameters[1]); - - if (labor == df::unit_labor::NONE) - { - out.printerr("Could not find labor %s.\n", parameters[0].c_str()); - return CR_WRONG_USAGE; - } - reset_labor(labor); - print_labor(labor, out); - return CR_OK; - } - else if (parameters.size() == 1 && (parameters[0] == "allow-fishing" || parameters[0] == "forbid-fishing")) - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - setOptionEnabled(CF_ALLOW_FISHING, (parameters[0] == "allow-fishing")); - return CR_OK; - } - else if (parameters.size() == 1 && (parameters[0] == "allow-hunting" || parameters[0] == "forbid-hunting")) - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - setOptionEnabled(CF_ALLOW_HUNTING, (parameters[0] == "allow-hunting")); - return CR_OK; - } - else if (parameters.size() == 1 && parameters[0] == "reset-all") - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - for (int i = 0; i < labor_infos.size(); i++) - { - reset_labor((df::unit_labor) i); - } - out << "All labors reset." << endl; - return CR_OK; - } - else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - bool need_comma = 0; - for (int i = 0; i < NUM_STATE; i++) - { - if (state_count[i] == 0) - continue; - if (need_comma) - out << ", "; - out << state_count[i] << ' ' << state_names[i]; - need_comma = 1; - } - out << endl; - - if (parameters[0] == "list") - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - print_labor(labor, out); - } - } - - return CR_OK; - } - else if (parameters.size() == 1 && parameters[0] == "debug") - { - if (!enable_autolabor) - { - out << "Error: The plugin is not enabled." << endl; - return CR_FAILURE; - } - - print_debug = 1; - - return CR_OK; - } - else - { - out.print("Automatically assigns labors to dwarves.\n" - "Activate with 'autolabor enable', deactivate with 'autolabor disable'.\n" - "Current state: %s.\n", enable_autolabor ? "enabled" : "disabled"); - - return CR_OK; - } -} - diff --git a/plugins/devel/color-dfhack-text.cpp b/plugins/devel/color-dfhack-text.cpp index eb5e9c659..c80c3cd4d 100644 --- a/plugins/devel/color-dfhack-text.cpp +++ b/plugins/devel/color-dfhack-text.cpp @@ -16,12 +16,12 @@ REQUIRE_GLOBAL(gps); struct { bool flicker; uint8_t color; - short tick; + uint8_t tick; } config; -void color_text_tile(const Screen::Pen &pen, int x, int y, bool map); +bool color_text_tile(const Screen::Pen &pen, int x, int y, bool map); GUI_HOOK_CALLBACK(Screen::Hooks::set_tile, color_text_hook, color_text_tile); -void color_text_tile(const Screen::Pen &pen, int x, int y, bool map) +bool color_text_tile(const Screen::Pen &pen, int x, int y, bool map) { Screen::Pen pen2 = pen; uint8_t color = config.flicker ? config.tick % 8 : config.color; @@ -40,24 +40,24 @@ void color_text_tile(const Screen::Pen &pen, int x, int y, bool map) return color_text_hook.next()(pen2, x, y, map); } -void aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map); +bool aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map); GUI_HOOK_CALLBACK(Screen::Hooks::set_tile, aaaaa_set_tile_hook, aaaaa_set_tile); -void aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map) +bool aaaaa_set_tile(const Screen::Pen &pen, int x, int y, bool map) { Screen::Pen pen2 = pen; if ((pen.ch >= 'A' && pen.ch <= 'Z') || (pen.ch >= '0' && pen.ch <= '9')) pen2.ch = 'A'; else if (pen.ch >= 'a' && pen.ch <= 'z') pen2.ch = 'a'; - aaaaa_set_tile_hook.next()(pen2, x, y, map); + return aaaaa_set_tile_hook.next()(pen2, x, y, map); } -void shift_set_tile(const Screen::Pen &pen, int x, int y, bool map); +bool shift_set_tile(const Screen::Pen &pen, int x, int y, bool map); GUI_HOOK_CALLBACK(Screen::Hooks::set_tile, shift_set_tile_hook, shift_set_tile); -void shift_set_tile(const Screen::Pen &pen, int x, int y, bool map) +bool shift_set_tile(const Screen::Pen &pen, int x, int y, bool map) { x = (x + 1) % gps->dimx; - shift_set_tile_hook.next()(pen, x, y, map); + return shift_set_tile_hook.next()(pen, x, y, map); } DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) diff --git a/plugins/devel/dumpmats.cpp b/plugins/devel/dumpmats.cpp index e50771a9f..23c7be4e1 100644 --- a/plugins/devel/dumpmats.cpp +++ b/plugins/devel/dumpmats.cpp @@ -59,10 +59,10 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) { def_color[matter_state::Liquid] = solid_color; def_color[matter_state::Gas] = solid_color; - out.print("\t[STATE_COLOR:ALL:%s]\n", world->raws.language.colors[solid_color]->id.c_str()); + out.print("\t[STATE_COLOR:ALL:%s]\n", world->raws.descriptors.colors[solid_color]->id.c_str()); } else - out.print("\t[STATE_COLOR:ALL_SOLID:%s]\n", world->raws.language.colors[solid_color]->id.c_str()); + out.print("\t[STATE_COLOR:ALL_SOLID:%s]\n", world->raws.descriptors.colors[solid_color]->id.c_str()); } string solid_name = mat->state_name[matter_state::Solid]; @@ -141,7 +141,7 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) FOR_ENUM_ITEMS(matter_state, state) { if (mat->state_color[state] != -1 && mat->state_color[state] != def_color[state]) - out.print("\t[STATE_COLOR:%s:%s]\n", state_names[state], world->raws.language.colors[mat->state_color[state]]->id.c_str()); + out.print("\t[STATE_COLOR:%s:%s]\n", state_names[state], world->raws.descriptors.colors[mat->state_color[state]]->id.c_str()); if (mat->state_name[state] == mat->state_adj[state]) { if (mat->state_name[state].size() && mat->state_name[state] != def_name[state] || mat->state_adj[state].size() && mat->state_adj[state] != def_adj[state]) @@ -241,7 +241,7 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) if (mat->hardens_with_water.mat_type != -1) out.print("\t[HARDENS_WITH_WATER:%s:%s%s%s]\n", mat->hardens_with_water.str[0].c_str(), mat->hardens_with_water.str[1].c_str(), mat->hardens_with_water.str[2].size() ? ":" : "", mat->hardens_with_water.str[2].c_str()); if (mat->powder_dye != -1) - out.print("\t[POWDER_DYE:%s]\n", world->raws.language.colors[mat->powder_dye]->id.c_str()); + out.print("\t[POWDER_DYE:%s]\n", world->raws.descriptors.colors[mat->powder_dye]->id.c_str()); if (mat->soap_level != -0) out.print("\t[SOAP_LEVEL:%o]\n", mat->soap_level); @@ -262,4 +262,4 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector DFhackCExport command_result plugin_shutdown ( Core * c ) { return CR_OK; -} \ No newline at end of file +} diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 35c5253b4..d64969c91 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -128,26 +128,26 @@ void jobCompleted(color_ostream& out, void* job) { } void timePassed(color_ostream& out, void* ptr) { - out.print("Time: %d\n", (int32_t)(ptr)); + out.print("Time: %d\n", (intptr_t)(ptr)); } void unitDeath(color_ostream& out, void* ptr) { - out.print("Death: %d\n", (int32_t)(ptr)); + out.print("Death: %d\n", (intptr_t)(ptr)); } void itemCreate(color_ostream& out, void* ptr) { - int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (int32_t)ptr); + int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (intptr_t)ptr); if ( item_index == -1 ) { out.print("%s, %d: Error.\n", __FILE__, __LINE__); } df::item* item = df::global::world->items.all[item_index]; df::item_type type = item->getType(); df::coord pos = item->pos; - out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); + out.print("Item created: %d, %s, at (%d,%d,%d)\n", (intptr_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); } void building(color_ostream& out, void* ptr) { - out.print("Building created/destroyed: %d\n", (int32_t)ptr); + out.print("Building created/destroyed: %d\n", (intptr_t)ptr); } void construction(color_ostream& out, void* ptr) { @@ -168,7 +168,7 @@ void syndrome(color_ostream& out, void* ptr) { } void invasion(color_ostream& out, void* ptr) { - out.print("New invasion! %d\n", (int32_t)ptr); + out.print("New invasion! %d\n", (intptr_t)ptr); } void unitAttack(color_ostream& out, void* ptr) { diff --git a/plugins/devel/frozen.cpp b/plugins/devel/frozen.cpp index 338cc37e8..ed5192073 100644 --- a/plugins/devel/frozen.cpp +++ b/plugins/devel/frozen.cpp @@ -1,11 +1,15 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" -#include "DataDefs.h" #include "modules/Maps.h" +#include "df/block_square_event_frozen_liquidst.h" +#include "df/map_block.h" +#include "df/world.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index 6317a0a39..77da0851e 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -1,16 +1,19 @@ -#include "Core.h" +#include +#include + #include "Console.h" +#include "Core.h" #include "Export.h" -#include "PluginManager.h" #include "MiscUtils.h" -#include -#include -#include "modules/Maps.h" +#include "PluginManager.h" + +#include "modules/Gui.h" #include "modules/Items.h" -#include -#include -#include -#include +#include "modules/Maps.h" + +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/devel/memutils.cpp b/plugins/devel/memutils.cpp new file mode 100644 index 000000000..5674f2c9c --- /dev/null +++ b/plugins/devel/memutils.cpp @@ -0,0 +1,71 @@ +#include + +#include "Core.h" +#include "LuaTools.h" +#include "LuaWrapper.h" + +using namespace DFHack; +using namespace DFHack::LuaWrapper; +using namespace std; + +namespace memutils { + static lua_State *state; + static color_ostream_proxy *out; + + struct initializer { + Lua::StackUnwinder *unwinder; + initializer() + { + if (!out) + out = new color_ostream_proxy(Core::getInstance().getConsole()); + if (!state) + state = Lua::Open(*out); + unwinder = new Lua::StackUnwinder(state); + } + ~initializer() + { + delete unwinder; + } + }; + + struct cleaner { + ~cleaner() + { + if (state) + { + lua_close(state); + state = NULL; + } + if (out) + { + delete out; + out = NULL; + } + } + }; + + static cleaner g_cleaner; + + void *lua_expr_to_addr(const char *expr) + { + initializer init; + Lua::PushModulePublic(*out, state, "utils", "df_expr_to_ref"); + lua_pushstring(state, expr); + if (!Lua::SafeCall(*out, state, 1, 1)) + { + out->printerr("Failed to evaluate %s\n", expr); + return NULL; + } + + Lua::PushModulePublic(*out, state, "utils", "addressof"); + lua_swap(state); + if (!Lua::SafeCall(*out, state, 1, 1) || !lua_isinteger(state, -1)) + { + out->printerr("Failed to get address: %s\n", expr); + return NULL; + } + + auto addr = uintptr_t(lua_tointeger(state, -1)); + return (void*)addr; + } +} diff --git a/plugins/devel/memutils.h b/plugins/devel/memutils.h new file mode 100644 index 000000000..b94d02a9a --- /dev/null +++ b/plugins/devel/memutils.h @@ -0,0 +1,3 @@ +namespace memutils { + void *lua_expr_to_addr(const char *expr); +} diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index 07158babc..d84a3b578 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -8,6 +8,8 @@ #include #include +#include "memutils.h" + using std::vector; using std::string; using namespace DFHack; @@ -51,7 +53,14 @@ size_t convert(const std::string& p,bool ishex=false) conv>>ret; return ret; } -bool isAddr(uint32_t *trg,vector & ranges) +bool isAddr(void *trg, vector &ranges) +{ + for (auto &r : ranges) + if (r.isInRange(trg)) + return true; + return false; +} +bool isAddrAt(uintptr_t *trg, vector &ranges) { if(trg[0]%4==0) for(size_t i=0;i & parameters) { mymutex->lock(); Core::getInstance().p->getMemRanges(memdata.ranges); - memdata.addr=(void *)convert(parameters[0],true); + if (parameters.empty()) + { + memdata.addr = 0; + } + else if (toLower(parameters[0].substr(0, 2)) == "0x") + { + memdata.addr = (void *)convert(parameters[0],true); + } + else + { + memdata.addr = memutils::lua_expr_to_addr(parameters[0].c_str()); + } + if(memdata.addr==0) { Deinit(); @@ -163,7 +195,9 @@ command_result memview (color_ostream &out, vector & parameters) is_enabled = true; memdata.state=STATE_ON; } - if(parameters.size()>1) + if (vector_get(parameters, 1, string("a")).substr(0, 1) == "a") + memdata.len = detect_size(memdata.addr); + else if (parameters.size()>1) memdata.len=convert(parameters[1]); else memdata.len=20*16; diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp index f4f423515..d29e97558 100644 --- a/plugins/devel/nestboxes.cpp +++ b/plugins/devel/nestboxes.cpp @@ -48,7 +48,7 @@ static void eggscan(color_ostream &out) if (nb->claimed_by != -1) { df::unit* u = df::unit::find(nb->claimed_by); - if (u && u->relations.pregnancy_timer > 0) + if (u && u->pregnancy_timer > 0) fertile = true; } for (int j = 1; j < nb->contained_items.size(); j++) diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp index 71da40600..082966f87 100644 --- a/plugins/devel/rprobe.cpp +++ b/plugins/devel/rprobe.cpp @@ -153,7 +153,7 @@ command_result rprobe (color_ostream &out, vector & parameters) int c = sizeof(*rd) / sizeof(int32_t); for (int j = 0; j < c; j++) { if (j % 8 == 0) - out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": "; + out << endl << setfill('0') << setw(8) << hex << (intptr_t)(rd+j) << ": "; out << " " << setfill('0') << setw(8) << hex << p[j]; } out << setfill(' ') << setw(0) << dec << endl; diff --git a/plugins/devel/tilesieve.cpp b/plugins/devel/tilesieve.cpp index 5c82eabe0..4cef423f6 100644 --- a/plugins/devel/tilesieve.cpp +++ b/plugins/devel/tilesieve.cpp @@ -9,6 +9,7 @@ // DF data structure definition headers #include "DataDefs.h" #include "modules/Maps.h" +#include "df/map_block.h" #include "df/world.h" #include "TileTypes.h" diff --git a/plugins/devel/vectors.cpp b/plugins/devel/vectors.cpp index c7e81dfe7..717f4bdae 100644 --- a/plugins/devel/vectors.cpp +++ b/plugins/devel/vectors.cpp @@ -50,11 +50,13 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static bool hexOrDec(string &str, uint32_t &value) +static bool hexOrDec(string &str, uintptr_t &value) { - if (str.find("0x") == 0 && sscanf(str.c_str(), "%x", &value) == 1) + std::stringstream ss; + ss << str; + if (str.find("0x") == 0 && (ss >> std::hex >> value)) return true; - else if (sscanf(str.c_str(), "%u", &value) == 1) + else if (ss >> std::dec >> value) return true; return false; @@ -69,7 +71,7 @@ static bool mightBeVec(vector &ranges, // Vector length might not be a multiple of 4 if, for example, // it's a vector of uint8_t or uint16_t. However, the actual memory // allocated to the vector should be 4 byte aligned. - if (((int)vec->start % 4 != 0) || ((int)vec->alloc_end % 4 != 0)) + if (((intptr_t)vec->start % 4 != 0) || ((intptr_t)vec->alloc_end % 4 != 0)) return false; for (size_t i = 0; i < ranges.size(); i++) @@ -128,10 +130,10 @@ static void vectorsUsage(color_ostream &con) } static void printVec(color_ostream &con, const char* msg, t_vecTriplet *vec, - uint32_t start, uint32_t pos, std::vector &ranges) + uintptr_t start, uintptr_t pos, std::vector &ranges) { - uint32_t length = (int)vec->end - (int)vec->start; - uint32_t offset = pos - start; + uintptr_t length = (intptr_t)vec->end - (intptr_t)vec->start; + uintptr_t offset = pos - start; con.print("%8s offset %06p, addr %010p, start %010p, length %u", msg, offset, pos, vec->start, length); @@ -158,7 +160,7 @@ command_result df_vectors (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t start = 0, bytes = 0; + uintptr_t start = 0, bytes = 0; if (!hexOrDec(parameters[0], start)) { @@ -172,7 +174,7 @@ command_result df_vectors (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t end = start + bytes; + uintptr_t end = start + bytes; // 4 byte alignment. while (start % 4 != 0) @@ -200,10 +202,10 @@ command_result df_vectors (color_ostream &con, vector & parameters) { con.print("Scanning %u bytes would read past end of memory " "range.\n", bytes); - uint32_t diff = end - (int)range.end; + size_t diff = end - (intptr_t)range.end; con.print("Cutting bytes down by %u.\n", diff); - end = (uint32_t) range.end; + end = (uintptr_t) range.end; } startInRange = true; break; @@ -215,11 +217,11 @@ command_result df_vectors (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t pos = start; + uintptr_t pos = start; - const uint32_t ptr_size = sizeof(void*); + const size_t ptr_size = sizeof(void*); - for (uint32_t pos = start; pos < end; pos += ptr_size) + for (uintptr_t pos = start; pos < end; pos += ptr_size) { // Is it an embeded vector? if (pos <= ( end - sizeof(t_vecTriplet) )) @@ -238,7 +240,7 @@ command_result df_vectors (color_ostream &con, vector & parameters) // Is it a vector pointer? if (pos <= (end - ptr_size)) { - uint32_t ptr = * ( (uint32_t*) pos); + uintptr_t ptr = * ( (uintptr_t*) pos); if (inAnyRange(ranges, (void *) ptr)) { @@ -275,7 +277,7 @@ command_result df_clearvec (color_ostream &con, vector & parameters) return CR_FAILURE; } - uint32_t start = 0, bytes = 0; + uintptr_t start = 0, bytes = 0; if (!hexOrDec(parameters[0], start)) { @@ -301,7 +303,7 @@ command_result df_clearvec (color_ostream &con, vector & parameters) for (size_t i = 0; i < parameters.size(); i++) { std::string addr_str = parameters[i]; - uint32_t addr; + uintptr_t addr; if (!hexOrDec(addr_str, addr)) { con << "'" << addr_str << "' not a number." << std::endl; @@ -329,7 +331,7 @@ command_result df_clearvec (color_ostream &con, vector & parameters) else { // Is it a pointer to a vector? - addr = * ( (uint32_t*) addr); + addr = * ( (uintptr_t*) addr); vec = (t_vecTriplet*) addr; if (inAnyRange(ranges, (void *) addr) && mightBeVec(ranges, vec)) diff --git a/plugins/devel/zoom.cpp b/plugins/devel/zoom.cpp index 3ee0566e8..d302d4900 100644 --- a/plugins/devel/zoom.cpp +++ b/plugins/devel/zoom.cpp @@ -77,5 +77,6 @@ command_result df_gzoom (color_ostream &out, std::vector & paramete Gui::setCursorCoords(x,y,z); } Gui::setViewCoords(x,y,z); + return CR_OK; } diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 5404f2f2d..83ca2ef2d 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -1,16 +1,23 @@ +#include +#include +#include +#include +#include +#include + #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Maps.h" +#include "uicommon.h" + #include "modules/Gui.h" #include "modules/MapCache.h" +#include "modules/Maps.h" #include "modules/Materials.h" -#include -#include -#include -#include -#include + +#include "df/ui_sidebar_menus.h" + using std::vector; using std::string; using std::stack; @@ -27,6 +34,7 @@ command_result digcircle (color_ostream &out, vector & parameters); command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); +REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -97,6 +105,7 @@ enum circle_what bool dig (MapExtras::MapCache & MCache, circle_what what, df::tile_dig_designation type, + int32_t priority, int32_t x, int32_t y, int32_t z, int x_max, int y_max ) @@ -175,20 +184,21 @@ bool dig (MapExtras::MapCache & MCache, break; } std::cerr << "allowing tt" << (int)tt << "\n"; - MCache.setDesignationAt(at,des); + MCache.setDesignationAt(at,des,priority); return true; }; bool lineX (MapExtras::MapCache & MCache, circle_what what, df::tile_dig_designation type, + int32_t priority, int32_t y1, int32_t y2, int32_t x, int32_t z, int x_max, int y_max ) { for(int32_t y = y1; y <= y2; y++) { - dig(MCache, what, type,x,y,z, x_max, y_max); + dig(MCache, what, type, priority, x,y,z, x_max, y_max); } return true; }; @@ -196,17 +206,55 @@ bool lineX (MapExtras::MapCache & MCache, bool lineY (MapExtras::MapCache & MCache, circle_what what, df::tile_dig_designation type, + int32_t priority, int32_t x1, int32_t x2, int32_t y, int32_t z, int x_max, int y_max ) { for(int32_t x = x1; x <= x2; x++) { - dig(MCache, what, type,x,y,z, x_max, y_max); + dig(MCache, what, type, priority, x,y,z, x_max, y_max); } return true; }; +int32_t parse_priority(color_ostream &out, vector ¶meters) +{ + int32_t default_priority = ui_sidebar_menus->designation.priority; + + for (auto it = parameters.begin(); it != parameters.end(); ++it) + { + const string &s = *it; + if (s.substr(0, 2) == "p=" || s.substr(0, 2) == "-p") + { + if (s.size() >= 3) + { + auto priority = int32_t(1000 * atof(s.c_str() + 2)); + parameters.erase(it); + return priority; + } + else if (it + 1 != parameters.end()) + { + auto priority = int32_t(1000 * atof((*(it + 1)).c_str())); + parameters.erase(it, it + 2); + return priority; + } + else + { + out.printerr("invalid priority specified; reverting to %i\n", default_priority); + break; + } + } + } + + return default_priority; +} + +string forward_priority(color_ostream &out, vector ¶meters) +{ + return string("-p") + int_to_string(parse_priority(out, parameters) / 1000); +} + command_result digcircle (color_ostream &out, vector & parameters) { static bool filled = false; @@ -215,6 +263,8 @@ command_result digcircle (color_ostream &out, vector & parameters) static int diameter = 0; auto saved_d = diameter; bool force_help = false; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") @@ -293,6 +343,7 @@ command_result digcircle (color_ostream &out, vector & parameters) " chan = dig channel\n" "\n" " # = diameter in tiles (default = 0)\n" + " -p # = designation priority (default = 4)\n" "\n" "After you have set the options, the command called with no options\n" "repeats with the last selected parameters:\n" @@ -326,12 +377,12 @@ command_result digcircle (color_ostream &out, vector & parameters) // paint center if(filled) { - lineY(MCache,what,type, cx - r, cx + r, cy, cz,x_max,y_max); + lineY(MCache, what, type, priority, cx - r, cx + r, cy, cz, x_max, y_max); } else { - dig(MCache, what, type,cx - r, cy, cz,x_max,y_max); - dig(MCache, what, type,cx + r, cy, cz,x_max,y_max); + dig(MCache, what, type, priority, cx - r, cy, cz, x_max, y_max); + dig(MCache, what, type, priority, cx + r, cy, cz, x_max, y_max); } adjust = false; iter = 2; @@ -363,24 +414,24 @@ command_result digcircle (color_ostream &out, vector & parameters) // paint if(filled || iter == diameter - 1) { - lineY(MCache,what,type, left, right, top , cz,x_max,y_max); - lineY(MCache,what,type, left, right, bottom , cz,x_max,y_max); + lineY(MCache, what, type, priority, left, right, top, cz, x_max, y_max); + lineY(MCache, what, type, priority, left, right, bottom, cz, x_max, y_max); } else { - dig(MCache, what, type,left, top, cz,x_max,y_max); - dig(MCache, what, type,left, bottom, cz,x_max,y_max); - dig(MCache, what, type,right, top, cz,x_max,y_max); - dig(MCache, what, type,right, bottom, cz,x_max,y_max); + dig(MCache, what, type, priority, left, top, cz, x_max, y_max); + dig(MCache, what, type, priority, left, bottom, cz, x_max, y_max); + dig(MCache, what, type, priority, right, top, cz, x_max, y_max); + dig(MCache, what, type, priority, right, bottom, cz, x_max, y_max); } if(!filled && diff > 1) { int lright = cx + lastwhole; int lleft = cx - lastwhole + adjust; - lineY(MCache,what,type, lleft + 1, left - 1, top + 1 , cz,x_max,y_max); - lineY(MCache,what,type, right + 1, lright - 1, top + 1 , cz,x_max,y_max); - lineY(MCache,what,type, lleft + 1, left - 1, bottom - 1 , cz,x_max,y_max); - lineY(MCache,what,type, right + 1, lright - 1, bottom - 1 , cz,x_max,y_max); + lineY(MCache, what, type, priority, lleft + 1, left - 1, top + 1 , cz, x_max, y_max); + lineY(MCache, what, type, priority, right + 1, lright - 1, top + 1 , cz, x_max, y_max); + lineY(MCache, what, type, priority, lleft + 1, left - 1, bottom - 1 , cz, x_max, y_max); + lineY(MCache, what, type, priority, right + 1, lright - 1, bottom - 1 , cz, x_max, y_max); } lastwhole = whole; } @@ -761,14 +812,14 @@ bool stamp_pattern (uint32_t bx, uint32_t by, int z_level, int x = 0,mx = 16; if(bx == 0) x = 1; - if(bx == x_max - 1) + if(int(bx) == x_max - 1) mx = 15; for(; x < mx; x++) { int y = 0,my = 16; if(by == 0) y = 1; - if(by == y_max - 1) + if(int(by) == y_max - 1) my = 15; for(; y < my; y++) { @@ -787,8 +838,8 @@ bool stamp_pattern (uint32_t bx, uint32_t by, int z_level, if(dm[y][x]) { if(what == EXPLO_ALL - || des.bits.dig == tile_dig_designation::Default && what == EXPLO_DESIGNATED - || des.bits.hidden && what == EXPLO_HIDDEN) + || (des.bits.dig == tile_dig_designation::Default && what == EXPLO_DESIGNATED) + || (des.bits.hidden && what == EXPLO_HIDDEN)) { des.bits.dig = tile_dig_designation::Default; } @@ -808,6 +859,8 @@ command_result digexp (color_ostream &out, vector & parameters) bool force_help = false; static explo_how how = EXPLO_NOTHING; static explo_what what = EXPLO_HIDDEN; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") @@ -895,7 +948,7 @@ command_result digexp (color_ostream &out, vector & parameters) int which; for(uint32_t x = 0; x < x_max; x++) { - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { which = (4*x + y) % 5; stamp_pattern(x,y_max - 1 - y, z_level, diag5[which], @@ -908,7 +961,7 @@ command_result digexp (color_ostream &out, vector & parameters) int which; for(uint32_t x = 0; x < x_max; x++) { - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { which = (4*x + 1000-y) % 5; stamp_pattern(x,y_max - 1 - y, z_level, diag5r[which], @@ -922,7 +975,7 @@ command_result digexp (color_ostream &out, vector & parameters) for(uint32_t x = 0; x < x_max; x++) { which = x % 3; - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { stamp_pattern(x, y, z_level, ladder[which], how, what, x_max, y_max); @@ -932,7 +985,7 @@ command_result digexp (color_ostream &out, vector & parameters) else if(how == EXPLO_LADDERR) { int which; - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { which = y % 3; for(uint32_t x = 0; x < x_max; x++) @@ -963,14 +1016,14 @@ command_result digexp (color_ostream &out, vector & parameters) if(cross[y][x]) { des.bits.dig = tile_dig_designation::Default; - mx.setDesignationAt(pos,des); + mx.setDesignationAt(pos,des,priority); } } mx.WriteAll(); } else for(uint32_t x = 0; x < x_max; x++) { - for(int32_t y = 0 ; y < y_max; y++) + for(uint32_t y = 0 ; y < y_max; y++) { stamp_pattern(x, y, z_level, all_tiles, how, what, x_max, y_max); @@ -984,6 +1037,7 @@ command_result digvx (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); + lol.push_back(forward_priority(out, parameters)); return digv(out,lol); } @@ -992,6 +1046,8 @@ command_result digv (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED uint32_t x_max,y_max,z_max; bool updown = false; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters.size() && parameters[0]=="x") @@ -1019,7 +1075,7 @@ command_result digv (color_ostream &out, vector & parameters) return CR_FAILURE; } DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); - if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) + if(xy.x == 0 || xy.x == int32_t(tx_max) - 1 || xy.y == 0 || xy.y == int32_t(ty_max) - 1) { con.printerr("I won't dig the borders. That would be cheating!\n"); return CR_FAILURE; @@ -1080,10 +1136,10 @@ command_result digv (color_ostream &out, vector & parameters) { MCache->setTagAt(current, 1); - if(current.x < tx_max - 2) + if(current.x < int32_t(tx_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y, current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y + 1,current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1,current.z)); @@ -1097,7 +1153,7 @@ command_result digv (color_ostream &out, vector & parameters) if(current.x > 1) { flood.push(DFHack::DFCoord(current.x - 1, current.y,current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x - 1, current.y + 1,current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1,current.z)); @@ -1118,11 +1174,11 @@ command_result digv (color_ostream &out, vector & parameters) des_minus.bits.dig = tile_dig_designation::UpDownStair; else des_minus.bits.dig = tile_dig_designation::UpStair; - MCache->setDesignationAt(current-1,des_minus); + MCache->setDesignationAt(current-1,des_minus,priority); des.bits.dig = tile_dig_designation::DownStair; } - if(current.z < z_max - 1 && above && vmat_plus == vmat2) + if(current.z < int32_t(z_max) - 1 && above && vmat_plus == vmat2) { flood.push(current+ 1); @@ -1130,7 +1186,7 @@ command_result digv (color_ostream &out, vector & parameters) des_plus.bits.dig = tile_dig_designation::UpDownStair; else des_plus.bits.dig = tile_dig_designation::DownStair; - MCache->setDesignationAt(current+1,des_plus); + MCache->setDesignationAt(current+1,des_plus,priority); if(des.bits.dig == tile_dig_designation::DownStair) des.bits.dig = tile_dig_designation::UpDownStair; @@ -1140,7 +1196,7 @@ command_result digv (color_ostream &out, vector & parameters) } if(des.bits.dig == tile_dig_designation::No) des.bits.dig = tile_dig_designation::Default; - MCache->setDesignationAt(current,des); + MCache->setDesignationAt(current,des,priority); } } MCache->WriteAll(); @@ -1153,6 +1209,7 @@ command_result diglx (color_ostream &out, vector & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); + lol.push_back(forward_priority(out, parameters)); return digl(out,lol); } @@ -1168,6 +1225,8 @@ command_result digl (color_ostream &out, vector & parameters) uint32_t x_max,y_max,z_max; bool updown = false; bool undo = false; + int32_t priority = parse_priority(out, parameters); + for(size_t i = 0; i < parameters.size();i++) { if(parameters[i]=="x") @@ -1203,7 +1262,7 @@ command_result digl (color_ostream &out, vector & parameters) return CR_FAILURE; } DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); - if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) + if(xy.x == 0 || xy.x == int32_t(tx_max) - 1 || xy.y == 0 || xy.y == int32_t(ty_max) - 1) { con.printerr("I won't dig the borders. That would be cheating!\n"); return CR_FAILURE; @@ -1261,10 +1320,10 @@ command_result digl (color_ostream &out, vector & parameters) if(MCache->testCoord(current)) { MCache->setTagAt(current, 1); - if(current.x < tx_max - 2) + if(current.x < int32_t(tx_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y, current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y + 1, current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1, current.z)); @@ -1278,7 +1337,7 @@ command_result digl (color_ostream &out, vector & parameters) if(current.x > 1) { flood.push(DFHack::DFCoord(current.x - 1, current.y, current.z)); - if(current.y < ty_max - 2) + if(current.y < int32_t(ty_max) - 2) { flood.push(DFHack::DFCoord(current.x - 1, current.y + 1, current.z)); flood.push(DFHack::DFCoord(current.x, current.y + 1, current.z)); @@ -1326,11 +1385,11 @@ command_result digl (color_ostream &out, vector & parameters) // undo mode: clear designation if(undo) des_minus.bits.dig = tile_dig_designation::No; - MCache->setDesignationAt(current-1,des_minus); + MCache->setDesignationAt(current-1,des_minus,priority); des.bits.dig = tile_dig_designation::DownStair; } - if(current.z < z_max - 1 && above && vmat_plus == -1 && bmat_plus == basemat) + if(current.z < int32_t(z_max) - 1 && above && vmat_plus == -1 && bmat_plus == basemat) { flood.push(current+ 1); @@ -1341,7 +1400,7 @@ command_result digl (color_ostream &out, vector & parameters) // undo mode: clear designation if(undo) des_plus.bits.dig = tile_dig_designation::No; - MCache->setDesignationAt(current+1,des_plus); + MCache->setDesignationAt(current+1,des_plus,priority); if(des.bits.dig == tile_dig_designation::DownStair) des.bits.dig = tile_dig_designation::UpDownStair; @@ -1354,7 +1413,7 @@ command_result digl (color_ostream &out, vector & parameters) // undo mode: clear designation if(undo) des.bits.dig = tile_dig_designation::No; - MCache->setDesignationAt(current,des); + MCache->setDesignationAt(current,des,priority); } } MCache->WriteAll(); @@ -1371,6 +1430,7 @@ command_result digauto (color_ostream &out, vector & parameters) command_result digtype (color_ostream &out, vector & parameters) { //mostly copy-pasted from digv + int32_t priority = parse_priority(out, parameters); CoreSuspender suspend; if ( parameters.size() > 1 ) { @@ -1378,7 +1438,7 @@ command_result digtype (color_ostream &out, vector & parameters) return CR_FAILURE; } - uint32_t targetDigType; + int32_t targetDigType; if ( parameters.size() == 1 ) { string parameter = parameters[0]; @@ -1462,9 +1522,10 @@ command_result digtype (color_ostream &out, vector & parameters) tt = mCache->tiletypeAt(current); if (!DFHack::isWallTerrain(tt)) continue; + if (tileMaterial(tt) != df::enums::tiletype_material::MINERAL) + continue; //designate it for digging - df::tile_designation des = mCache->designationAt(current); if ( !mCache->testCoord(current) ) { out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z); @@ -1474,7 +1535,7 @@ command_result digtype (color_ostream &out, vector & parameters) df::tile_designation designation = mCache->designationAt(current); designation.bits.dig = baseDes.bits.dig; - mCache->setDesignationAt(current, designation); + mCache->setDesignationAt(current, designation,priority); } } } diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 6668f9994..2556d25ac 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -89,7 +89,7 @@ void onDig(color_ostream& out, void* ptr) { return; set jobLocations; - for ( df::job_list_link* link = &world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; @@ -137,7 +137,7 @@ void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, uint32_t xMax,yMax,zMax; Maps::getSize(xMax,yMax,zMax); - if ( pt.x == 0 || pt.y == 0 || pt.x+1 == xMax*16 || pt.y+1 == yMax*16 ) + if ( pt.x == 0 || pt.y == 0 || pt.x+1 == int32_t(xMax)*16 || pt.y+1 == int32_t(yMax)*16 ) return; if ( jobLocations.find(pt) != jobLocations.end() ) { return; diff --git a/plugins/diggingInvaders/CMakeLists.txt b/plugins/diggingInvaders/CMakeLists.txt index a88c93ca1..fa4279b0f 100644 --- a/plugins/diggingInvaders/CMakeLists.txt +++ b/plugins/diggingInvaders/CMakeLists.txt @@ -15,21 +15,4 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) -# this makes sure all the stuff is put in proper places and linked to dfhack - -DFHACK_PLUGIN(diggingInvaders ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +DFHACK_PLUGIN(diggingInvaders ${PROJECT_SRCS}) diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index b7ca6f161..2b780bd63 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -20,6 +20,7 @@ #include "df/item_type.h" #include "df/item_weaponst.h" #include "df/job.h" +#include "df/job_list_link.h" #include "df/job_skill.h" #include "df/job_type.h" #include "df/reaction_product_itemst.h" @@ -27,8 +28,11 @@ #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/world.h" #include "df/world_site.h" +using namespace DFHack; + void getRidOfOldJob(df::unit* unit) { if ( unit->job.current_job == NULL ) { return; @@ -64,8 +68,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map (%d,%d,%d)\n", pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z); - int32_t jobId = -1; - df::map_block* block1 = Maps::getTileBlock(pt1); df::map_block* block2 = Maps::getTileBlock(pt2); bool passable1 = block1->walkable[pt1.x&0xF][pt1.y&0xF]; @@ -107,7 +109,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjobs.clear(); building->jobs.push_back(job); Job::linkIntoWorld(job); - jobId = job->id; job->completion_timer = abilities.jobDelay[CostDimension::DestroyBuilding]; } else { df::tiletype* type1 = Maps::getTileType(pt1); @@ -132,7 +133,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjob.hunt_target = NULL; firstInvader->job.destroy_target = NULL; Job::linkIntoWorld(job); - jobId = job->id; df::construction* constr = df::construction::find(pt2); bool smooth = constr != NULL && constr->item_type != df::enums::item_type::BOULDER; if ( smooth ) @@ -200,7 +200,6 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mappath.path.y.clear(); firstInvader->path.path.z.clear(); Job::linkIntoWorld(job); - jobId = job->id; job->completion_timer = abilities.jobDelay[CostDimension::Dig]; //TODO: test if he already has a pick @@ -257,8 +256,8 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map in_reag; vector in_items; prod->produce(firstInvader, &out_products, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE, - df::historical_entity::find(firstInvader->civ_id), - df::world_site::find(df::global::ui->site_id)); + df::historical_entity::find(firstInvader->civ_id), 0, + df::world_site::find(df::global::ui->site_id), 0); if ( out_items.size() != 1 ) { out.print("%s, %d: wrong size: %d.\n", __FILE__, __LINE__, out_items.size()); diff --git a/plugins/diggingInvaders/assignJob.h b/plugins/diggingInvaders/assignJob.h index 0ad6419d6..1e5750c03 100644 --- a/plugins/diggingInvaders/assignJob.h +++ b/plugins/diggingInvaders/assignJob.h @@ -2,6 +2,7 @@ #include "edgeCost.h" +#include "ColorText.h" #include "modules/MapCache.h" #include @@ -9,5 +10,5 @@ using namespace std; -int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache, DigAbilities& abilities); +int32_t assignJob(DFHack::color_ostream& out, Edge firstImportantEdge, unordered_map parentMap, unordered_map& costMap, vector& invaders, unordered_set& requiresZNeg, unordered_set& requiresZPos, MapExtras::MapCache& cache, DigAbilities& abilities); diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index 8be58fd32..9bbc5191e 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -199,7 +199,7 @@ public: } - int32_t operator()(df::coord p1, df::coord p2) { + int32_t operator()(df::coord p1, df::coord p2) const { if ( p1 == p2 ) return 0; auto i1 = pointCost->find(p1); auto i2 = pointCost->find(p2); @@ -598,7 +598,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { lastInvasionDigger = firstInvader->id; lastInvasionJob = firstInvader->job.current_job ? firstInvader->job.current_job->id : -1; invaderJobs.erase(lastInvasionJob); - for ( df::job_list_link* link = &world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; df::job* job = link->item; diff --git a/plugins/diggingInvaders/edgeCost.cpp b/plugins/diggingInvaders/edgeCost.cpp index c9e83fa14..cd52f4e17 100644 --- a/plugins/diggingInvaders/edgeCost.cpp +++ b/plugins/diggingInvaders/edgeCost.cpp @@ -19,6 +19,8 @@ #include +using namespace DFHack; + /* cost_t costWeight[] = { //Distance @@ -244,7 +246,7 @@ cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilitie bool forbidden = false; if ( building1 && building1->getType() == df::building_type::Hatch ) { df::building_hatchst* hatch = (df::building_hatchst*)building1; - if ( hatch->door_flags.bits.forbidden || hatch->door_flags.bits.closed && hatch->door_flags.bits.operated_by_mechanisms ) + if ( hatch->door_flags.bits.forbidden || ( hatch->door_flags.bits.closed && hatch->door_flags.bits.operated_by_mechanisms ) ) forbidden = true; } diff --git a/plugins/diggingInvaders/edgeCost.h b/plugins/diggingInvaders/edgeCost.h index 560fec109..ef4cb3168 100644 --- a/plugins/diggingInvaders/edgeCost.h +++ b/plugins/diggingInvaders/edgeCost.h @@ -93,6 +93,6 @@ struct PointHash { } }; -cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities); -std::vector* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities); +cost_t getEdgeCost(DFHack::color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities); +std::vector* getEdgeSet(DFHack::color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities); diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 8e2a88611..cf920e84e 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -18,31 +18,35 @@ #include "modules/Translation.h" #include "modules/World.h" #include "modules/Maps.h" -#include "df/activity_event.h" + #include "df/activity_entry.h" -#include "df/unit_preference.h" -#include "df/unit_soul.h" +#include "df/activity_event.h" +#include "df/creature_raw.h" +#include "df/dance_form.h" +#include "df/descriptor_color.h" +#include "df/descriptor_shape.h" #include "df/item_type.h" - -#include "df/itemdef_weaponst.h" -#include "df/itemdef_trapcompst.h" -#include "df/itemdef_toyst.h" -#include "df/itemdef_toolst.h" -#include "df/itemdef_instrumentst.h" -#include "df/itemdef_armorst.h" #include "df/itemdef_ammost.h" -#include "df/itemdef_siegeammost.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_foodst.h" #include "df/itemdef_glovesst.h" -#include "df/itemdef_shoesst.h" -#include "df/itemdef_shieldst.h" #include "df/itemdef_helmst.h" +#include "df/itemdef_instrumentst.h" #include "df/itemdef_pantsst.h" -#include "df/itemdef_foodst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_siegeammost.h" +#include "df/itemdef_toolst.h" +#include "df/itemdef_toyst.h" +#include "df/itemdef_trapcompst.h" +#include "df/itemdef_weaponst.h" +#include "df/musical_form.h" +#include "df/poetic_form.h" #include "df/trapcomp_flags.h" -#include "df/creature_raw.h" +#include "df/unit_preference.h" +#include "df/unit_soul.h" +#include "df/viewscreen_unitst.h" #include "df/world_raws.h" -#include "df/descriptor_shape.h" -#include "df/descriptor_color.h" using std::deque; @@ -137,6 +141,14 @@ static string getUnitName(df::unit * unit) return label; } +template +static string getFormName(int32_t id, const string &default_ = "?") { + T *form = T::find(id); + if (form) + return Translation::TranslateName(&form->name); + return default_; +} + static void send_key(const df::interface_key &key) { set< df::interface_key > keys; @@ -147,11 +159,10 @@ static void send_key(const df::interface_key &key) static void move_cursor(df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); } -static void open_stats_srceen(); +static void open_stats_screen(); namespace dm_lua { static color_ostream_proxy *out; @@ -187,14 +198,6 @@ namespace dm_lua { return safe_call(nargs); } - template - void table_set (lua_State *L, KeyType k, ValueType v) - { - Lua::Push(L, k); - Lua::Push(L, v); - lua_settable(L, -3); - } - namespace api { int monitor_state (lua_State *L) { @@ -229,7 +232,7 @@ namespace dm_lua { } } lua_newtable(L); - #define WTYPE(type, name) dm_lua::table_set(L, #type, type); + #define WTYPE(type, name) Lua::TableInsert(L, #type, type); WEATHER_TYPES #undef WTYPE #undef WEATHER_TYPES @@ -242,9 +245,9 @@ namespace dm_lua { { Lua::Push(L, i); lua_newtable(L); - dm_lua::table_set(L, "value", misery[i]); - dm_lua::table_set(L, "color", monitor_colors[i]); - dm_lua::table_set(L, "last", (i == 6)); + Lua::TableInsert(L, "value", misery[i]); + Lua::TableInsert(L, "color", monitor_colors[i]); + Lua::TableInsert(L, "last", (i == 6)); lua_settable(L, -3); } return 1; @@ -441,7 +444,7 @@ public: else if (input->count(interface_key::CUSTOM_SHIFT_D)) { Screen::dismiss(this); - open_stats_srceen(); + open_stats_screen(); } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { @@ -489,6 +492,8 @@ public: void render() { + using namespace df::enums::interface_key; + if (Screen::isDismissed(this)) return; @@ -502,18 +507,18 @@ public: int32_t y = gps->dimy - 4; int32_t x = 2; - OutputHotkeyString(x, y, "Leave", "Esc"); + OutputHotkeyString(x, y, "Leave", LEAVESCREEN); x += 13; string window_label = "Window Months: " + int_to_string(window_days / min_window); - OutputHotkeyString(x, y, window_label.c_str(), "*"); + OutputHotkeyString(x, y, window_label.c_str(), SECONDSCROLL_PAGEDOWN); ++y; x = 2; - OutputHotkeyString(x, y, "Fort Stats", "Shift-D"); + OutputHotkeyString(x, y, "Fort Stats", CUSTOM_SHIFT_D); x += 3; - OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z); } std::string getFocusString() { return "dwarfmonitor_dwarfstats"; } @@ -1097,6 +1102,8 @@ public: void render() { + using namespace df::enums::interface_key; + if (Screen::isDismissed(this)) return; @@ -1111,18 +1118,18 @@ public: int32_t y = gps->dimy - 4; int32_t x = 2; - OutputHotkeyString(x, y, "Leave", "Esc"); + OutputHotkeyString(x, y, "Leave", LEAVESCREEN); x += 13; string window_label = "Window Months: " + int_to_string(window_days / min_window); - OutputHotkeyString(x, y, window_label.c_str(), "*"); + OutputHotkeyString(x, y, window_label.c_str(), SECONDSCROLL_PAGEDOWN); ++y; x = 2; - OutputHotkeyString(x, y, "Dwarf Stats", "Shift-D"); + OutputHotkeyString(x, y, "Dwarf Stats", CUSTOM_SHIFT_D); x += 3; - OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z); } std::string getFocusString() { return "dwarfmonitor_fortstats"; } @@ -1210,6 +1217,18 @@ struct preference_map break; default: + label = ENUM_ATTR_STR(item_type, caption, pref.item_type); + if (label.size()) + { + if (label[label.size() - 1] == 's') + label += "es"; + else + label += "s"; + } + else + { + label = "UNKNOWN"; + } break; } @@ -1226,15 +1245,13 @@ struct preference_map { case (T_type::LikeCreature): { - label = "Creature :"; - Units::getRaceNamePluralById(pref.creature_id); + label = "Creature :" + Units::getRaceNamePluralById(pref.creature_id); break; } case (T_type::HateCreature): { - label = "Hates :"; - Units::getRaceNamePluralById(pref.creature_id); + label = "Hates :" + Units::getRaceNamePluralById(pref.creature_id); break; } @@ -1286,7 +1303,7 @@ struct preference_map } case (T_type::LikeShape): - label += "Shape :" + raws.language.shapes[pref.shape_id]->name_plural; + label += "Shape :" + raws.descriptors.shapes[pref.shape_id]->name_plural; break; case (T_type::LikeTree): @@ -1297,7 +1314,23 @@ struct preference_map } case (T_type::LikeColor): - label += "Color :" + raws.language.colors[pref.color_id]->name; + label += "Color :" + raws.descriptors.colors[pref.color_id]->name; + break; + + case (T_type::LikePoeticForm): + label += "Poetry :" + getFormName(pref.poetic_form_id); + break; + + case (T_type::LikeMusicalForm): + label += "Music :" + getFormName(pref.musical_form_id); + break; + + case (T_type::LikeDanceForm): + label += "Dance :" + getFormName(pref.dance_form_id); + break; + + default: + label += string("UNKNOWN ") + ENUM_KEY_STR(unit_preference::T_type, pref.type); break; } } @@ -1313,14 +1346,14 @@ public: preferences_column.auto_select = true; preferences_column.setTitle("Preference"); preferences_column.bottom_margin = 3; - preferences_column.search_margin = 35; + preferences_column.search_margin = 50; dwarf_column.multiselect = false; dwarf_column.auto_select = true; dwarf_column.allow_null = true; dwarf_column.setTitle("Units with Preference"); dwarf_column.bottom_margin = 3; - dwarf_column.search_margin = 35; + dwarf_column.search_margin = 50; populatePreferencesColumn(); } @@ -1453,6 +1486,18 @@ public: return false; break; + case (T_type::LikePoeticForm): + return lhs.poetic_form_id == rhs.poetic_form_id; + break; + + case (T_type::LikeMusicalForm): + return lhs.musical_form_id == rhs.musical_form_id; + break; + + case (T_type::LikeDanceForm): + return lhs.dance_form_id == rhs.dance_form_id; + break; + default: return false; } @@ -1492,8 +1537,13 @@ public: case (T_type::LikeColor): return COLOR_BLUE; + case (T_type::LikePoeticForm): + case (T_type::LikeMusicalForm): + case (T_type::LikeDanceForm): + return COLOR_LIGHTCYAN; + default: - return false; + return COLOR_LIGHTMAGENTA; } return true; @@ -1552,6 +1602,11 @@ public: dwarf_column.setHighlight(0); } + df::unit *getSelectedUnit() override + { + return (selected_column == 1) ? dwarf_column.getFirstSelectedElem() : nullptr; + } + void feed(set *input) { bool key_processed = false; @@ -1581,9 +1636,19 @@ public: Screen::dismiss(this); return; } + else if (input->count(interface_key::CUSTOM_SHIFT_V)) + { + df::unit *unit = getSelectedUnit(); + if (unit) + { + auto unitscr = df::allocate(); + unitscr->unit = unit; + Screen::show(unitscr); + } + } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { - df::unit *selected_unit = (selected_column == 1) ? dwarf_column.getFirstSelectedElem() : nullptr; + df::unit *selected_unit = getSelectedUnit(); if (selected_unit) { input->clear(); @@ -1619,6 +1684,8 @@ public: void render() { + using namespace df::enums::interface_key; + if (Screen::isDismissed(this)) return; @@ -1632,10 +1699,15 @@ public: int32_t y = gps->dimy - 3; int32_t x = 2; - OutputHotkeyString(x, y, "Leave", "Esc"); + OutputHotkeyString(x, y, "Leave", LEAVESCREEN); x += 2; - OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + OutputHotkeyString(x, y, "View Unit", CUSTOM_SHIFT_V, false, 0, + getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY); + + x += 2; + OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z, false, 0, + getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY); } std::string getFocusString() { return "dwarfmonitor_preferences"; } @@ -1663,7 +1735,7 @@ private: }; -static void open_stats_srceen() +static void open_stats_screen() { Screen::show(new ViewscreenFortStats(), plugin_self); } @@ -1798,15 +1870,11 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - INTERPOSE_NEXT(feed)(input); - } - DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); + CoreSuspendClaimer suspend; if (Maps::IsValid()) { dm_lua::call("render_all"); @@ -1814,7 +1882,6 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest } }; -IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render); static bool set_monitoring_mode(const string &mode, const bool &state) @@ -1861,8 +1928,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) load_config(); if (is_enabled != enable) { - if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) || - !INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable)) + if (!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable)) return CR_FAILURE; reset(); diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp new file mode 100644 index 000000000..19f00e888 --- /dev/null +++ b/plugins/dwarfvet.cpp @@ -0,0 +1,830 @@ +/** + * Copyright (c) 2015, Michael Casadevall + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + **/ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/EventManager.h" +#include "modules/Units.h" +#include "modules/Buildings.h" +#include "modules/Maps.h" +#include "modules/Job.h" + +#include "df/animal_training_level.h" +#include "df/building_type.h" +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/creature_raw.h" +#include "df/job.h" +#include "df/general_ref_unit_workerst.h" +#include "df/profession.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_health_info.h" +#include "df/unit_health_flags.h" +#include "df/world.h" + +#include +#include + +using namespace DFHack; +using namespace DFHack::Units; +using namespace DFHack::Buildings; + +using namespace std; + +DFHACK_PLUGIN("dwarfvet"); +DFHACK_PLUGIN_IS_ENABLED(dwarfvet_enabled); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); + +static vector tracked_units; +static int32_t howOften = 100; + +struct hospital_spot { + int32_t x; + int32_t y; + int32_t z; +}; + +class Patient { + public: + // Constructor/Deconstrctor + Patient(int32_t id, int spot_index, int32_t x, int32_t y, int32_t z); + int32_t getID() { return this->id; }; + int32_t getSpotIndex() { return this->spot_index; }; + int32_t returnX() { return this->spot_in_hospital.x; }; + int32_t returnY() { return this->spot_in_hospital.y; }; + int32_t returnZ() { return this->spot_in_hospital.z; }; + + private: + struct hospital_spot spot_in_hospital; + int id; + int spot_index; +}; + +Patient::Patient(int32_t id, int32_t spot_index, int32_t x, int32_t y, int32_t z){ + this->id = id; + this->spot_in_hospital.x = x; + this->spot_in_hospital.y = y; + this->spot_in_hospital.z = z; +} + +class AnimalHospital { + + public: + // Constructor + AnimalHospital(df::building *, color_ostream &out); + ~AnimalHospital(); + int32_t getID() { return id; } + bool acceptPatient(int32_t id, color_ostream&); + void processPatients(color_ostream &out); + void dischargePatient(Patient * patient, color_ostream &out); + void calculateHospital(bool force, color_ostream &out); + void reportUsage(color_ostream &out); + + // GC + bool to_be_deleted; + + private: + int spots_open; + int32_t id; + int32_t x1; + int32_t x2; + int32_t y1; + int32_t y2; + int32_t z; + int height; + int length; + + // Doing an actual array in C++ is *annoying*, bloody copy constructors */ + vector spots_in_use; + vector building_in_hospital_notification; /* If present, we already notified about this */ + vector accepted_patients; +}; + +AnimalHospital::AnimalHospital(df::building * building, color_ostream &out) { + // Copy in what we need to know + id = building->id; + x1 = building->x1; + x2 = building->x2; + y1 = building->y1; + y2 = building->y2; + z = building->z; + + // Determine how many spots we have for animals + this->length = x2-x1+1; + this->height = y2-y1+1; + + // And calculate the hospital! + this->calculateHospital(true, out); +} + +AnimalHospital::~AnimalHospital() { + // Go through and delete all the patients + for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { + delete (*accepted_patient); + } +} + +bool AnimalHospital::acceptPatient(int32_t id, color_ostream &out) { + // This function determines if we can accept a patient, and if we will. + this->calculateHospital(true, out); + + // First, do we have room? + if (!spots_open) return false; + + // Yup, let's find the next open spot, + // and give it to our patient + int spot_cur = 0; // fuck the STL for requiring a second counter to make this usable + for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { + if (*spot == false) { + *spot = true; + break; + } + spot_cur++; + } + + spots_open--; + + // Convert the spot into x/y/z cords. + int offset_y = spot_cur/length; + int offset_x = spot_cur%length; + + // Create the patient! + Patient * patient = new Patient(id, + spot_cur, + this->x1+offset_x, + this->y1+offset_y, + this->z + ); + + accepted_patients.push_back(patient); + return true; +} + +// Before any use of the hospital, we need to make calculate open spots +// and such. This can change (i.e. stuff built in hospital) and +// such so it should be called on each function. +void AnimalHospital::reportUsage(color_ostream &out) { + // Debugging tool to see parts of the hospital in use + int length_cursor = this->length; + + for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { + if (*spot) out.print("t"); + if (!(*spot)) out.print("f"); + length_cursor--; + if (length_cursor < 0) { + out.print("\n"); + length_cursor = this->length; + } + } + out.print("\n"); + +} + +void AnimalHospital::calculateHospital(bool force, color_ostream &out) { + // Only calculate out the hospital if we actually have a patient in it + // (acceptPatient will forcibly rerun this to make sure everything OK + + // Should reduce FPS impact of each calculation tick when the hospitals + // are not in use + //if (!force || (spots_open == length*height)) { + // Hospital is idle, don't recalculate + // return; + //} + + // Calculate out the total area of the hospital + // This can change if a hospital has been resized + this->spots_open = length*height; + this->spots_in_use.assign(this->spots_open, false); + + // The spots_in_use maps one to one with a spot + // starting at the upper-left hand corner, then + // across, then down. i.e. + // + // given hospital zone: + // + // UU + // uU + // + // where U is in use, and u isn't, the array + // would be t,t,f,t + + // Walk the building array and see what stuff is in the hospital, + // then walk the patient array and remark those spots as used. + + // If a patient is in an invalid spot, reassign it + for (df::building *building : world->buildings.all) { + + // Check that we're not comparing ourselves; + if (building->id == this->id) { + continue; + } + + // Check if the building is on our z level, if it isn't + // then it can't overlap the hospital (until Toady implements + // multi-z buildings + if (building->z != this->z) { + continue; + } + + // DF defines activity zones multiple times in the building structure + // If axises agree with each other, we're looking at a reflection of + // ourselves + if (building->x1 == this->x1 && + building->x2 == this->x2 && + building->y1 == this->y1 && + building->y2 == this->y2) { + continue; + } + + // Check for X/Y overlap + // I can't believe I had to look this up -_-; + // http://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other + if ((this->x1 > building->x2 || + building->x1 > this->x2 || + this->y1 > building->y2 || + building->y1 > this->y2)) { + continue; + } + + // Crap, building overlaps, we need to figure out where it is in the hospital + // NOTE: under some conditions, this generates a false warning. Not a lot I can do about it + + // Mark spots used by that building as used; FIXME: handle special logic for traction benches and such + int building_offset_x = building->x1 - this->x1; + int building_offset_y = building->y1 - this->y1; + int building_length = building->x2 - building->x1 + 1; + int building_height = building->y2 - building->y1 + 1; + + // Cap the used calculation to only include the part in the hospital + if (this->x1 > building->x1) { + building_offset_x -= (this->x1 - building->x1); + } + + if (this->y1 > building->y1) { + building_offset_y -= (building->y1 - this->y1); + } + + if ((this->x2 < building->x2) && building_offset_x) { + building_length -= (this->x2 - building->x2) + 1; + } + + if ((this->y2 < building->y2) && building_offset_y) { + building_height = (building->y2 - this->y2) + 1; + } + + // Quick explination, if a building is north or east of the activity zone, + // we get a negative offset, we'll just skip those lines below. If its + // south or west, we make the building length/height lower to compinsate. + + /* if we have a negative x offset, we correct that */ + if (building_offset_x < 0) { + building_height += building_offset_x; + building_offset_x = 0; + } + + /* Handle negative y offset */ + if (building_offset_y < 0) { + building_length += building_offset_y; + building_offset_y = 0; + }; + + /* Advance the pointer to first row we need to mark */ + int spot_cur = 0; + if (building_offset_y) { + spot_cur = (length+1) * building_offset_y; + } + + spot_cur += building_offset_x; + /* Start marking! */ + for (int i = 0; i < building_height; i++) { + for (int j = 0; j < building_length; j++) { + spots_in_use[spot_cur+j] = true; + } + + // Wind the cursor to the start of the next row + spot_cur += length+1; + } + + // *phew*, done. Now repeat the process for the next building! + } + +} + +// Self explanatory +void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { + int32_t id = patient->getID(); + + // Remove them from the hospital + + // We can safely iterate here because once we delete the unit + // we no longer use the iterator + for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { + if ( (*accepted_patient)->getID() == id) { + out.print("Discharging unit %d from hospital %d\n", id, this->id); + // Reclaim their spot + spots_in_use[(*accepted_patient)->getSpotIndex()] = false; + this->spots_open++; + delete (*accepted_patient); + this->accepted_patients.erase(accepted_patient); + break; + } + } + + // And the master list + for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { + if ((*it) == id) { + tracked_units.erase(it); + break; + } + } + + return; +} + +void AnimalHospital::processPatients(color_ostream &out) { + // Where the magic happens + for (vector::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { + int id = (*patient)->getID(); + df::unit * real_unit = nullptr; + // Appears the health bits can get freed/realloced too -_-;, Find the unit from the main + // index and check it there. + auto units = world->units.all; + + for ( size_t a = 0; a < units.size(); a++ ) { + real_unit = units[a]; + if (real_unit->id == id) { + break; + } + } + + // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed + if (!real_unit || real_unit->flags1.bits.dead || !real_unit->health->flags.bits.needs_healthcare) { + // discharge the patient from the hospital + this->dischargePatient(*patient, out); + return; + } + + // Give the unit a job if they don't have any + if (!real_unit->job.current_job) { + // Create REST struct + df::job * job = new df::job; + DFHack::Job::linkIntoWorld(job); + + job->pos.x = (*patient)->returnX(); + job->pos.y = (*patient)->returnY(); + job->pos.z = (*patient)->returnZ(); + job->flags.bits.special = 1; + job->job_type = df::enums::job_type::Rest; + df::general_ref *ref = df::allocate(); + ref->setID(real_unit->id); + job->general_refs.push_back(ref); + real_unit->job.current_job = job; + job->wait_timer = 1600; + out.print("Telling intelligent unit %d to report to the hospital!\n", real_unit->id); + } + } +} + + +static vector animal_hospital_zones; + +void delete_animal_hospital_vector(color_ostream &out) { + out.print("Clearing all animal hospitals\n"); + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + delete (*animal_hospital); + } + animal_hospital_zones.clear(); +} + +command_result dwarfvet(color_ostream &out, std::vector & parameters); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "dwarfvet", + "Allows animals to be cared for in animal hospitals (activity zones that are animal training + hospital combined).", + dwarfvet, + false, //allow non-interactive use + "dwarfvet enable\n" + " enables animals to use animal hospitals (requires dwarf with Animal Caretaker labor enabled)\n" + "dwarfvet report\n" + " displays all zones dwarfvet considers animal hospitals and their current location on the map\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +bool isActiveAnimalHospital(df::building * building) { + if (Buildings::isHospital(building) && Buildings::isAnimalTraining(building) && Buildings::isActive(building)) { + return true; + } + + return false; +} + +bool compareAnimalHospitalZones(df::building * hospital1, df::building * hospital2) { + // We compare hospitals by checking if positions are identical, not by ID + // since activity zones can easily be changed in size + + if ( hospital1->x1 == hospital2->x1 && + hospital1->x2 == hospital2->x2 && + hospital1->y1 == hospital2->y1 && + hospital1->y2 == hospital2->y2 && + hospital1->z == hospital2->z) { + return true; + } + + return false; +} + +void tickHandler(color_ostream& out, void* data) { + if ( !dwarfvet_enabled ) + return; + CoreSuspender suspend; + int32_t own_race_id = df::global::ui->race_id; + int32_t own_civ_id = df::global::ui->civ_id; + auto units = world->units.all; + + /** + * Generate a list of animal hospitals on the map + * + * Since activity zones can change any instant given user interaction + * we need to be constantly on the lookout for changed zones, and update + * our cached list on the fly if necessary. + **/ + + vector hospitals_on_map; + + // Because C++ iterators suck, we're going to build a temporary vector with the AHZ, and then + // copy it for my own bloody sanity (and compilance with the STL spec) + vector ahz_scratch; + + // Holding area for things to be added to the scratch + vector to_be_added; + + + // Walk the building tree, and generate a list of animal hospitals on the map + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + if (isActiveAnimalHospital(building)) { + hospitals_on_map.push_back(building); + } + } + + int count_of_hospitals = hospitals_on_map.size(); + int hospitals_cached = animal_hospital_zones.size(); + //out.print ("count_of_Hospitals: %d, hospitals_cached: %d\n", count_of_hospitals, hospitals_cached); + // It's possible our hospital cache is empty, if so, simply copy it, and jump to the main logic + if (!hospitals_cached && count_of_hospitals) { + out.print("Populating hospital cache:\n"); + for (df::building *current_hospital : hospitals_on_map) { + AnimalHospital * hospital = new AnimalHospital(current_hospital, out); + out.print(" Found animal hospital %d at x1: %d, y1: %d, z: %d from valid hospital list\n", + hospital->getID(), + current_hospital->x1, + current_hospital->y1, + current_hospital->z + ); + animal_hospital_zones.push_back(hospital); + } + + goto processUnits; + } + + if (!count_of_hospitals && !hospitals_cached) { + // No hospitals found, delete any cache, and return + delete_animal_hospital_vector(out); + out.print("No hospitals found, plugin sleeping ...\n"); + goto cleanup; + } + + // Now walk our list of known hospitals, do a bit of checking, then compare + // TODO: this doesn't handle zone resizes at all + + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + // If a zone is changed at all, DF seems to reallocate it. + // + // Each AnimalHospital has a "to_be_deleted" bool. We're going to set that to true, and clear it if we can't + // find a matching hospital. This limits the number of times we need to walk through the AHZ list to twice, and + // lets us cleanly report it later + // + // Surviving hospitals will be copied to scratch which will become the new AHZ vector + + (*animal_hospital)->to_be_deleted = true; + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + + /* Keep the hospital if its still valid */ + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + ahz_scratch.push_back(*animal_hospital); + (*animal_hospital)->to_be_deleted = false; + break; + } + + } + } + + // Report what we're deleting by checking the to_be_deleted bool. + // + // Whatsever left is added to the pending add list + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + if ((*animal_hospital)->to_be_deleted) { + out.print("Hospital #%d removed\n", (*animal_hospital)->getID()); + delete *animal_hospital; + } + } + + /* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */ + + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + bool new_hospital = true; + + for (vector::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + // Next if we're already here + new_hospital = false; + break; + } + } + + // Add it if its new + if (new_hospital == true) to_be_added.push_back(*current_hospital); + } + + /* Now add it to the scratch AHZ */ + for (vector::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { + // Add it to the vector + out.print("Adding new hospital #id at x1 %d y1: %d z: %d\n", + (*current_hospital)->id, + (*current_hospital)->x1, + (*current_hospital)->y1, + (*current_hospital)->z + ); + AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + ahz_scratch.push_back(hospital); + } + + /* Copy the scratch to the AHZ */ + animal_hospital_zones = ahz_scratch; + + // We always recheck the cache instead of counts because someone might have removed then added a hospital +/* if (hospitals_cached != count_of_hospitals) { + out.print("Hospitals on the map changed, rebuilding cache\n"); + + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + bool add_hospital = true; + + for (vector::iterator map_hospital = animal_hospital_zones.begin(); map_hospital != animal_hospital_zones.end(); map_hospital++) { + if (compareAnimalHospitalZones(*map_hospital, *current_hospital)) { + // Same hospital, we're good + add_hospital = false; + break; + } + } + + // Add it to the list + if (add_hospital) { + out.print("Adding zone at x1: %d, y1: %d to valid hospital list\n", (*current_hospital)->x1, (*current_hospital)->y1); + animal_hospital_zones.push_back(*current_hospital); + } + } + } +*/ +processUnits: + /* Code borrowed from petcapRemover.cpp */ + for ( size_t a = 0; a < units.size(); a++ ) { + df::unit* unit = units[a]; + + /* As hilarious as it would be, lets not treat FB :) */ + if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) { + continue; + } + + if ( !Units::isTamable(unit)) { + continue; + } + + /** + * So, for a unit to be elligable for the hospital, all the following must be true + * + * 1. It must be a member of our civilization + * 2. It must be tame (semi-wild counts for this) + * 2.1 If its not a dwarf, AND untame clear its civ out so traps work + * 3. It must have a health struct (which is generated by combat) + * 4. health->needs_healthcare must be set to true + * 5. If health->requires_recovery is set, the creature can't move under its own power + * and a Recover Wounded or Pen/Pasture job MUST be created by hand - TODO + * 6. An open spot in the "Animal Hospital" (activity zone with hospital+animal training set) + * must be available + * + * I apologize if this excessively verbose, but the healthcare system is stupidly conplex + * and there's tons of edgecases to watch out for, and I want someone else to ACTUALLY + * beside me able to understand what's going on + */ + + // 1. Make sure its our own civ + if (!Units::isOwnCiv(unit)) { + continue; + } + + // 2. Check for tameness + if (unit->training_level == df::animal_training_level::WildUntamed) { + // We don't IMMEDIATELY continue here, if the unit is + // part of our civ, it indiciates it WAS tamed, and reverted + // from SemiWild. Clear its civ flag so it looses TRAPAVOID + // + // Unfortunately, dwarves (or whatever is CIV_SELECTABLE) + // also have a default taming level of WildUntamed so + // check for this case + // + // Furthermore, it MIGHT be a werebeast, so check THAT too + // and exclude those as well. + // + // Finally, this breaks makeown. I might need to write a patch + // to set the tameness of "makeowned" units so dwarfvet can notice + // it + + if (unit->race == own_race_id || unit->enemy.normal_race == own_race_id) { + continue; + } else { + unit->civ_id = -1; + out.print ("Clearing civ on unit: %d", unit->id); + } + } + + // 3. Check for health struct + if (!unit->health) { + // Unit has not been injured ever; health struct MIA + continue; + } + + // 4. Check the healthcare flags + if (unit->health->flags.bits.needs_healthcare) { + /** + * So, for dwarves to care for a unit it must be resting in + * in a hospital zone. Since non-dwarves never take jobs + * this why animal healthcare doesn't work for animals despite + * animal caretaker being coded in DF itself + * + * How a unit gets there is dependent on several factors. If + * a unit can move under its own power, it will take the rest + * job, with a position of a bed in the hospital, then move + * into that bed and fall asleep. This triggers a doctor to + * treat the unit. + * + * If a unit *can't* move, it will set needs_recovery, which + * creates a "Recover Wounded" job in the job list, and then + * create the "Rest" job as listed above. Another dwarf with + * the right labors will go recover the unit, then the above + * logic kicks off. + * + * The necessary flags seem to be properly set for all units + * on the map, so in theory, we just need to make the jobs and + * we're in business, but from a realism POV, I don't think + * non-sentient animals would be smart enough to go to the + * hospital on their own, so instead, we're going to do the following + * + * If a unit CAN_THINK, and can move let it act like a dwarf, + * it will try and find an open spot in the hospital, and if so, + * go there to be treated. In vanilla, the only tamable animal + * with CAN_THINK are Gremlins, so this is actually an edge case + * but its the easiest to code. + * + * TODO: figure out exact logic for non-thinking critters. + */ + + // Now we need to find if this unit can be accepted at a hospital + bool awareOfUnit = false; + for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { + if ((*it) == unit->id) { + awareOfUnit = true; + } + } + // New unit for dwarfvet to be aware of! + if (!awareOfUnit) { + // The master list handles all patients which are accepted + // Check if this is a unit we're already aware of + + for (auto animal_hospital : animal_hospital_zones) { + if (animal_hospital->acceptPatient(unit->id, out)) { + out.print("Accepted patient %d at hospital %d\n", unit->id, animal_hospital->getID()); + tracked_units.push_back(unit->id); + break; + } + } + } + } + } + + // The final step, process all patients! + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + (*animal_hospital)->calculateHospital(true, out); + (*animal_hospital)->processPatients(out); + } + +cleanup: + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); +} + +command_result dwarfvet (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + for ( size_t a = 0; a < parameters.size(); a++ ) { + if ( parameters[a] == "enable" ) { + out.print("dwarfvet enabled!\n"); + dwarfvet_enabled = true; + } + if ( parameters[a] == "disable") { + out.print("dwarvet disabled!\n"); + dwarfvet_enabled = false; + } + if ( parameters[a] == "report") { + out.print("Current animal hospitals are:\n"); + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + if (isActiveAnimalHospital(building)) { + out.print(" at x1: %d, x2: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z); + } + } + return CR_OK; + } + if ( parameters[a] == "report-usage") { + out.print("Current animal hospitals are:\n"); + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + (*animal_hospital)->calculateHospital(true, out); + (*animal_hospital)->reportUsage(out); + } + return CR_OK; + } + } + + if ( !dwarfvet_enabled ) { + return CR_OK; + } + + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (enable && !dwarfvet_enabled) { + dwarfvet_enabled = true; + } + else if (!enable && dwarfvet_enabled) { + delete_animal_hospital_vector(out); + dwarfvet_enabled = false; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) + { + case DFHack::SC_MAP_LOADED: + break; + case DFHack::SC_MAP_UNLOADED: + delete_animal_hospital_vector(out); + dwarfvet_enabled = false; + break; + default: + break; + } + return CR_OK; +} diff --git a/plugins/embark-assistant/CMakeLists.txt b/plugins/embark-assistant/CMakeLists.txt new file mode 100644 index 000000000..917d2ec5a --- /dev/null +++ b/plugins/embark-assistant/CMakeLists.txt @@ -0,0 +1,30 @@ +PROJECT (embark-assistant) +# A list of source files +SET(PROJECT_SRCS + biome_type.cpp + embark-assistant.cpp + finder_ui.cpp + help_ui.cpp + matcher.cpp + overlay.cpp + screen.cpp + survey.cpp +) +# A list of headers +SET(PROJECT_HDRS + biome_type.h + defs.h + embark-assistant.h + finder_ui.h + help_ui.h + matcher.h + overlay.h + screen.h + survey.h +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +DFHACK_PLUGIN(embark-assistant ${PROJECT_SRCS}) diff --git a/plugins/embark-assistant/biome_type.cpp b/plugins/embark-assistant/biome_type.cpp new file mode 100644 index 000000000..01bc0044a --- /dev/null +++ b/plugins/embark-assistant/biome_type.cpp @@ -0,0 +1,754 @@ +/* The code is copied from Ragundo's repo referenced below. +The changes are: +- The addition of a .h file reference. +- The simplification of the code using ofsub to remove the use of (and + .h reference to) that function (analysis of the code showed the + simplified code is the result, as the ofsub expressions will never be + true given the range of the values it can be passed in these functions). +- The change of the main function to take a separate y coordinate for + use in the tropicality determination to allow proper determination of + the tropicality of mid level tiles ("region tiles") referencing a + neighboring world tile's biome. +*/ +/* +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +// You can always find the latest version of this plugin in Github +// https://github.com/ragundo/exportmaps + +#include + +#include "DataDefs.h" +#include +#include +#include +#include + +#include "biome_type.h" + +/***************************************************************************** +Local functions forward declaration +*****************************************************************************/ +std::pair check_tropicality(df::region_map_entry& region, + int a1 +); + +int get_lake_biome(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude +); + +int get_ocean_biome(df::region_map_entry& region, + bool is_tropical_area_by_latitude +); + +int get_desert_biome(df::region_map_entry& region); + +int get_biome_grassland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_savanna(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_shrubland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_forest(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_swamp(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +int get_biome_shrubland_or_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +); + +/***************************************************************************** +Module main function. +Return the biome type, given a position coordinate expressed in world_tiles +The world ref coordinates are used for tropicality determination and may refer +to a tile neighboring the "official" one. +*****************************************************************************/ +int get_biome_type(int world_coord_x, + int world_coord_y, + int world_ref_coord_y +) +{ + // Biome is per region, so get the region where this biome exists + df::region_map_entry& region = df::global::world->world_data->region_map[world_coord_x][world_coord_y]; + + // Check if the y reference position coordinate belongs to a tropical area + std::pair p = check_tropicality(region, + world_ref_coord_y + ); + bool is_possible_tropical_area_by_latitude = p.first; + bool is_tropical_area_by_latitude = p.second; + + // Begin the discrimination + if (region.flags.is_set(df::region_map_entry_flags::is_lake)) // is it a lake? + return get_lake_biome(region, + is_possible_tropical_area_by_latitude + ); + + // Not a lake. Check elevation + // Elevation greater then 149 means a mountain biome + // Elevation below 100 means a ocean biome + // Elevation between 100 and 149 are land biomes + + if (region.elevation >= 150) // is it a mountain? + return df::enums::biome_type::biome_type::MOUNTAIN; // 0 + + if (region.elevation < 100) // is it a ocean? + return get_ocean_biome(region, + is_possible_tropical_area_by_latitude + ); + + // land biome. Elevation between 100 and 149 + if (region.temperature <= -5) + { + if (region.drainage < 75) + return df::enums::biome_type::biome_type::TUNDRA; // 2 + else + return df::enums::biome_type::biome_type::GLACIER; // 1 + } + + // Not a lake, mountain, ocean, glacier or tundra + // Vegetation determines the biome type + if (region.vegetation < 66) + { + if (region.vegetation < 33) + return get_biome_desert_or_grassland_or_savanna(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x + ); + else // vegetation between 33 and 65 + return get_biome_shrubland_or_marsh(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x + ); + } + + // Not a lake, mountain, ocean, glacier, tundra, desert, grassland or savanna + // vegetation >= 66 + if (region.drainage >= 33) + return get_biome_forest(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x + ); + + // Not a lake, mountain, ocean, glacier, tundra, desert, grassland, savanna or forest + // vegetation >= 66, drainage < 33 + return get_biome_swamp(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + world_coord_y, + world_coord_x); +} + + + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_no_poles_world(df::region_map_entry& region, + int y_pos +) +{ + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + + // If there're no poles, tropical area is determined by temperature + if (region.temperature >= 75) + is_possible_tropical_area_by_latitude = true; + is_tropical_area_by_latitude = region.temperature >= 85; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_north_pole_only_world(df::region_map_entry& region, + int y_pos +) +{ + int v6; + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + df::world_data* wdata = df::global::world->world_data; + + // Scale the smaller worlds to the big one + if (wdata->world_height == 17) + v6 = 16 * y_pos; + else if (wdata->world_height == 33) + v6 = 8 * y_pos; + else if (wdata->world_height == 65) + v6 = 4 * y_pos; + else if (wdata->world_height == 129) + v6 = 2 * y_pos; + else + v6 = y_pos; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_south_pole_only_world(df::region_map_entry& region, + int y_pos +) +{ + int v6 = df::global::world->world_data->world_height - y_pos - 1; + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + df::world_data* wdata = df::global::world->world_data; + + if (wdata->world_height == 17) + v6 *= 16; + else if (wdata->world_height == 33) + v6 *= 8; + else if (wdata->world_height == 65) + v6 *= 4; + else if (wdata->world_height == 129) + v6 *= 2; + else + v6 *= 1; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality_both_poles_world(df::region_map_entry& region, + int y_pos +) +{ + int v6; + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + df::world_data* wdata = df::global::world->world_data; + + if (y_pos < wdata->world_height / 2) + v6 = 2 * y_pos; + else + { + v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1; + if (v6 < 0) + v6 = 0; + if (v6 >= wdata->world_height) + v6 = wdata->world_height - 1; + } + + if (wdata->world_height == 17) + v6 *= 16; + else if (wdata->world_height == 33) + v6 *= 8; + else if (wdata->world_height == 65) + v6 *= 4; + else if (wdata->world_height == 129) + v6 *= 2; + else + v6 *= 1; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; + + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair check_tropicality(df::region_map_entry& region, + int y_pos +) +{ + int flip_latitude = df::global::world->world_data->flip_latitude; + + if (flip_latitude == -1) // NO POLES + return check_tropicality_no_poles_world(region, + y_pos + ); + + else if (flip_latitude == 0) // NORTH POLE ONLY + return check_tropicality_north_pole_only_world(region, + y_pos + ); + + else if (flip_latitude == 1) // SOUTH_POLE ONLY + return check_tropicality_south_pole_only_world(region, + y_pos + ); + + else if (flip_latitude == 2) // BOTH POLES + return check_tropicality_both_poles_world(region, + y_pos + ); + + return std::pair(false, false); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_parameter_percentage(int flip_latitude, + int y_pos, + int rainfall, + int world_height +) +{ + int result; + int ypos = y_pos; + + if (flip_latitude == -1) // NO POLES + return 100; + + else if (flip_latitude == 1) // SOUTH POLE + ypos = world_height - y_pos - 1; + else if (flip_latitude == 2) // NORTH & SOUTH POLE + { + if (ypos < world_height / 2) + ypos *= 2; + else + { + ypos = world_height + 2 * (world_height / 2 - ypos) - 1; + if (ypos < 0) + ypos = 0; + if (ypos >= world_height) + ypos = world_height - 1; + } + } + + int latitude; // 0 - 256 (size of a large world) + switch (world_height) + { + case 17: // Pocket world + latitude = 16 * ypos; + break; + case 33: // Smaller world + latitude = 8 * ypos; + break; + case 65: // Small world + latitude = 4 * ypos; + break; + case 129: // Medium world + latitude = 2 * ypos; + break; + default: // Large world + latitude = ypos; + break; + } + + // latitude > 220 + if ((latitude - 171) > 49) + return 100; + + + // Latitude between 191 and 200 + if ((latitude > 190) && (latitude < 201)) + return 0; + + // Latitude between 201 and 220 + if ((latitude > 190) && (latitude >= 201)) + result = rainfall + 16 * (latitude - 207); + else + // Latitude between 0 and 190 + result = (16 * (184 - latitude) - rainfall); + + if (result < 0) + return 0; + + if (result > 100) + return 100; + + return result; +} + +//----------------------------------------------------------------------------// +// Utility function +// +// return some unknow parameter as a percentage +//----------------------------------------------------------------------------// +int get_region_parameter(int y, + int x, + char a4 +) +{ + int result = 100; + + if ((df::global::cur_season && *df::global::cur_season != 1) || !a4) + { + int world_height = df::global::world->world_data->world_height; + if (world_height > 65) // Medium and large worlds + { + // access to region 2D array + df::region_map_entry& region = df::global::world->world_data->region_map[x][y]; + return get_parameter_percentage(df::global::world->world_data->flip_latitude, + y, + region.rainfall, + world_height + ); + } + } + return result; +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_lake_biome(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude +) +{ + // salinity values tell us the lake type + // greater than 66 is a salt water lake + // between 33 and 65 is a brackish water lake + // less than 33 is a fresh water lake + if (region.salinity < 66) + { + if (region.salinity < 33) + if (is_possible_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::LAKE_TROPICAL_FRESHWATER; // 39 + else + return df::enums::biome_type::biome_type::LAKE_TEMPERATE_FRESHWATER; // 36 + else // salinity >= 33 + if (is_possible_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::LAKE_TROPICAL_BRACKISHWATER; // 40 + else + return df::enums::biome_type::biome_type::LAKE_TEMPERATE_BRACKISHWATER; // 37 + } + else // salinity >= 66 + { + if (is_possible_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::LAKE_TROPICAL_SALTWATER;// 41 + else + return df::enums::biome_type::biome_type::LAKE_TEMPERATE_SALTWATER; // 38 + } +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_ocean_biome(df::region_map_entry& region, + bool is_tropical_area_by_latitude +) +{ + if (is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::OCEAN_TROPICAL; // 27 + else + if (region.temperature <= -5) + return df::enums::biome_type::biome_type::OCEAN_ARCTIC; // 29 + else + return df::enums::biome_type::biome_type::OCEAN_TEMPERATE; // 28 +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_desert_biome(df::region_map_entry& region) +{ + if (region.drainage < 66) + { + if (region.drainage < 33) + return df::enums::biome_type::biome_type::DESERT_SAND; // 26 + else // drainage between 33 and 65 + return df::enums::biome_type::biome_type::DESERT_ROCK; // 25 + } + // drainage >= 66 + return df::enums::biome_type::biome_type::DESERT_BADLAND; // 24 +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_grassland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::GRASSLAND_TROPICAL; // 21 + else + return df::enums::biome_type::biome_type::GRASSLAND_TEMPERATE; //18; +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_savanna(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) <= 6)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::SAVANNA_TROPICAL; // 22 + else + return df::enums::biome_type::biome_type::SAVANNA_TEMPERATE; //19; + +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (region.vegetation < 20) + { + if (region.vegetation < 10) + return get_desert_biome(region); + else // vegetation between 10 and 19 + return get_biome_grassland(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x); + } + // vegetation between 20 and 32 + return get_biome_savanna(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_shrubland(bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66 || is_tropical_area_by_latitude)) + return df::enums::biome_type::biome_type::SHRUBLAND_TROPICAL; // 23 + else + return df::enums::biome_type::biome_type::SHRUBLAND_TEMPERATE; // 20 +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (region.salinity < 66) + { + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::MARSH_TROPICAL_FRESHWATER; // 10 + else + return df::enums::biome_type::biome_type::MARSH_TEMPERATE_FRESHWATER; // 5 + } + else // drainage < 33, salinity >= 66 + { + if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::MARSH_TROPICAL_SALTWATER; // 11 + else + return df::enums::biome_type::biome_type::MARSH_TEMPERATE_SALTWATER; // 6 + } +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_shrubland_or_marsh(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + if (region.drainage >= 33) + return get_biome_shrubland(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + y, + x + ); + // drainage < 33 + return get_biome_marsh(region, + is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude, + y, + x + ); +} + + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_forest(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + int parameter = get_region_parameter(y, x, 0); + + // drainage >= 33, not tropical area + if (!is_possible_tropical_area_by_latitude) + { + if ((region.rainfall < 75) || (region.temperature < 65)) + { + if (region.temperature >= 10) + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13 + else + return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12 + } + else + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14 + } + else // drainage >= 33, tropical area + { + if (((parameter < 66) || is_tropical_area_by_latitude) && (region.rainfall < 75)) + return df::enums::biome_type::biome_type::FOREST_TROPICAL_CONIFER; // 15 + if (parameter < 66) + return df::enums::biome_type::biome_type::FOREST_TROPICAL_DRY_BROADLEAF; // 16 + if (is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF; // 17 + else + { + if ((region.rainfall < 75) || (region.temperature < 65)) + { + if (region.temperature >= 10) + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13 + else + return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12 + } + else + return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14 + } + } +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +int get_biome_swamp(df::region_map_entry& region, + bool is_possible_tropical_area_by_latitude, + bool is_tropical_area_by_latitude, + int y, + int x +) +{ + int parameter = get_region_parameter(y, x, 0); + + if (is_possible_tropical_area_by_latitude) + { + if (region.salinity < 66) + { + if ((parameter < 66) || is_tropical_area_by_latitude) + return df::enums::biome_type::biome_type::SWAMP_TROPICAL_FRESHWATER; // 7 + else + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER;// 3 + } + else // elevation between 100 and 149, vegetation >= 66, drainage < 33, salinity >= 66 + { + if ((parameter < 66) || is_tropical_area_by_latitude) + { + if (region.drainage < 10) + return df::enums::biome_type::biome_type::SWAMP_MANGROVE; //9 + else // drainage >= 10 + return df::enums::biome_type::biome_type::SWAMP_TROPICAL_SALTWATER; // 8 + } + else + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4 + } + } + else // elevation between 100 and 149, vegetation >= 66, drainage < 33, not tropical area + { + if (region.salinity >= 66) + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4 + else + return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER; // 3 + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/biome_type.h b/plugins/embark-assistant/biome_type.h new file mode 100644 index 000000000..9ea562749 --- /dev/null +++ b/plugins/embark-assistant/biome_type.h @@ -0,0 +1,7 @@ +// world_coord_x/y is the location of the tile "owning" the biome, while world_ref_coord_y is the +// location of the tile the biome appears on. They differ when a mid level tile ("region tile") +// refers to a neighboring tile for the biome parameters. The difference can affect the tropicality +// determination. Since Tropicality is determined by latitude, the x coordinate of the reference is +// omitted. +// +int get_biome_type(int world_coord_x, int world_coord_y, int world_ref_coord_y); diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h new file mode 100644 index 000000000..3196bd5ee --- /dev/null +++ b/plugins/embark-assistant/defs.h @@ -0,0 +1,277 @@ +#pragma once + +#include +#include +#include + +using namespace std; +using std::array; +using std::ostringstream; +using std::string; +using std::vector; + +namespace embark_assist { + namespace defs { + // Survey types + // + enum class river_sizes { + None, + Brook, + Stream, + Minor, + Medium, + Major + }; + + struct mid_level_tile { + bool aquifer = false; + bool clay = false; + bool sand = false; + bool flux = false; + int8_t soil_depth; + int8_t offset; + int16_t elevation; + bool river_present = false; + int16_t river_elevation = 100; + int8_t adamantine_level; // -1 = none, 0 .. 3 = cavern 1 .. magma sea. Currently not used beyond present/absent. + int8_t magma_level; // -1 = none, 0 .. 3 = cavern 3 .. surface/volcano + int8_t biome_offset; + uint8_t savagery_level; // 0 - 2 + uint8_t evilness_level; // 0 - 2 + std::vector metals; + std::vector economics; + std::vector minerals; + }; + + typedef std::array, 16> mid_level_tiles; + + struct region_tile_datum { + bool surveyed = false; + uint16_t aquifer_count = 0; + uint16_t clay_count = 0; + uint16_t sand_count = 0; + uint16_t flux_count = 0; + uint8_t min_region_soil = 10; + uint8_t max_region_soil = 0; + bool waterfall = false; + river_sizes river_size; + int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used + int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used + uint8_t biome_count; + bool evil_weather[10]; + bool evil_weather_possible; + bool evil_weather_full; + bool reanimating[10]; + bool reanimating_possible; + bool reanimating_full; + bool thralling[10]; + bool thralling_possible; + bool thralling_full; + uint16_t savagery_count[3]; + uint16_t evilness_count[3]; + std::vector metals; + std::vector economics; + std::vector minerals; + }; + + struct geo_datum { + uint8_t soil_size = 0; + bool top_soil_only = true; + bool top_soil_aquifer_only = true; + bool aquifer_absent = true; + bool clay_absent = true; + bool sand_absent = true; + bool flux_absent = true; + std::vector possible_metals; + std::vector possible_economics; + std::vector possible_minerals; + }; + + typedef std::vector geo_data; + + struct sites { + uint8_t x; + uint8_t y; + char type; + }; + + struct site_infos { + bool aquifer; + bool aquifer_full; + uint8_t min_soil; + uint8_t max_soil; + bool flat; + bool waterfall; + bool clay; + bool sand; + bool flux; + std::vector metals; + std::vector economics; + std::vector minerals; + // Could add savagery, evilness, and biomes, but DF provides those easily. + }; + + typedef std::vector site_lists; + + typedef std::vector> world_tile_data; + + typedef bool mlt_matches[16][16]; + // An embark region match is indicated by marking the top left corner + // tile as a match. Thus, the bottom and right side won't show matches + // unless the appropriate dimension has a width of 1. + + struct matches { + bool preliminary_match; + bool contains_match; + mlt_matches mlt_match; + }; + + typedef std::vector> match_results; + + // matcher types + // + enum class evil_savagery_values : int8_t { + NA = -1, + All, + Present, + Absent + }; + + enum class evil_savagery_ranges : int8_t { + Low, + Medium, + High + }; + + enum class aquifer_ranges : int8_t { + NA = -1, + All, + Present, + Partial, + Not_All, + Absent + }; + + enum class river_ranges : int8_t { + NA = -1, + None, + Brook, + Stream, + Minor, + Medium, + Major + }; + +// For possible future use. That's the level of data actually collected. +// enum class adamantine_ranges : int8_t { +// NA = -1, +// Cavern_1, +// Cavern_2, +// Cavern_3, +// Magma_Sea +// }; + + enum class magma_ranges : int8_t { + NA = -1, + Cavern_3, + Cavern_2, + Cavern_1, + Volcano + }; + + enum class yes_no_ranges : int8_t { + NA = -1, + Yes, + No + }; + + enum class all_present_ranges : int8_t { + All, + Present + }; + enum class present_absent_ranges : int8_t { + NA = -1, + Present, + Absent + }; + + enum class soil_ranges : int8_t { + NA = -1, + None, + Very_Shallow, + Shallow, + Deep, + Very_Deep + }; + + /* // Future possible enhancement + enum class freezing_ranges : int8_t { + NA = -1, + Permanent, + At_Least_Partial, + Partial, + At_Most_Partial, + Never + }; + */ + + struct finders { + uint16_t x_dim; + uint16_t y_dim; + evil_savagery_values savagery[static_cast(evil_savagery_ranges::High) + 1]; + evil_savagery_values evilness[static_cast(evil_savagery_ranges::High) + 1]; + aquifer_ranges aquifer; + river_ranges min_river; + river_ranges max_river; + yes_no_ranges waterfall; + yes_no_ranges flat; + present_absent_ranges clay; + present_absent_ranges sand; + present_absent_ranges flux; + soil_ranges soil_min; + all_present_ranges soil_min_everywhere; + soil_ranges soil_max; + /*freezing_ranges freezing;*/ + yes_no_ranges evil_weather; // Will probably blow up with the magic release arcs... + yes_no_ranges reanimation; + yes_no_ranges thralling; + int8_t spire_count_min; // N/A(-1), 0-9 + int8_t spire_count_max; // N/A(-1), 0-9 + magma_ranges magma_min; + magma_ranges magma_max; + int8_t biome_count_min; // N/A(-1), 1-9 + int8_t biome_count_max; // N/A(-1), 1-9 + int8_t region_type_1; // N/A(-1), df::world_region_type + int8_t region_type_2; // N/A(-1), df::world_region_type + int8_t region_type_3; // N/A(-1), df::world_region_type + int8_t biome_1; // N/A(-1), df::biome_type + int8_t biome_2; // N/A(-1), df::biome_type + int8_t biome_3; // N/A(-1), df::biome_type + int16_t metal_1; // N/A(-1), 0-max_inorganic; + int16_t metal_2; // N/A(-1), 0-max_inorganic; + int16_t metal_3; // N/A(-1), 0-max_inorganic; + int16_t economic_1; // N/A(-1), 0-max_inorganic; + int16_t economic_2; // N/A(-1), 0-max_inorganic; + int16_t economic_3; // N/A(-1), 0-max_inorganic; + int16_t mineral_1; // N/A(-1), 0-max_inorganic; + int16_t mineral_2; // N/A(-1), 0-max_inorganic; + int16_t mineral_3; // N/A(-1), 0-max_inorganic; + }; + + struct match_iterators { + bool active; + uint16_t x; // x position of focus when iteration started so we can return it. + uint16_t y; // y + uint16_t i; + uint16_t k; + bool x_right; + bool y_down; + bool inhibit_x_turn; + bool inhibit_y_turn; + uint16_t count; + finders finder; + }; + + typedef void(*find_callbacks) (embark_assist::defs::finders finder); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp new file mode 100644 index 000000000..bd70df06a --- /dev/null +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -0,0 +1,296 @@ +#include "Core.h" +#include +#include +#include + +#include +#include +#include + +#include "DataDefs.h" +#include "df/coord2d.h" +#include "df/inorganic_flags.h" +#include "df/inorganic_raw.h" +#include "df/interfacest.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_raws.h" + +#include "defs.h" +#include "embark-assistant.h" +#include "finder_ui.h" +#include "matcher.h" +#include "overlay.h" +#include "survey.h" + +DFHACK_PLUGIN("embark-assistant"); + +using namespace DFHack; +using namespace df::enums; +using namespace Gui; + +REQUIRE_GLOBAL(world); + +namespace embark_assist { + namespace main { + struct states { + embark_assist::defs::geo_data geo_summary; + embark_assist::defs::world_tile_data survey_results; + embark_assist::defs::site_lists region_sites; + embark_assist::defs::site_infos site_info; + embark_assist::defs::match_results match_results; + embark_assist::defs::match_iterators match_iterator; + uint16_t max_inorganic; + }; + + static states *state = nullptr; + + void embark_update (); + void shutdown(); + + //=============================================================================== + + void embark_update() { + auto screen = Gui::getViewscreenByType(0); + embark_assist::defs::mid_level_tiles mlt; + embark_assist::survey::initiate(&mlt); + + embark_assist::survey::survey_mid_level_tile(&state->geo_summary, + &state->survey_results, + &mlt); + + embark_assist::survey::survey_embark(&mlt, &state->site_info, false); + embark_assist::overlay::set_embark(&state->site_info); + + embark_assist::survey::survey_region_sites(&state->region_sites); + embark_assist::overlay::set_sites(&state->region_sites); + + embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match); + } + + //=============================================================================== + + void match() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + + uint16_t count = embark_assist::matcher::find(&state->match_iterator, + &state->geo_summary, + &state->survey_results, + &state->match_results); + + embark_assist::overlay::match_progress(count, &state->match_results, !state->match_iterator.active); + + if (!state->match_iterator.active) { + auto screen = Gui::getViewscreenByType(0); + embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match); + } + } + + //=============================================================================== + + void clear_match() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (state->match_iterator.active) { + embark_assist::matcher::move_cursor(state->match_iterator.x, state->match_iterator.y); + } + embark_assist::survey::clear_results(&state->match_results); + embark_assist::overlay::clear_match_results(); + embark_assist::main::state->match_iterator.active = false; + } + + //=============================================================================== + + void find(embark_assist::defs::finders finder) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + + state->match_iterator.x = embark_assist::survey::get_last_pos().x; + state->match_iterator.y = embark_assist::survey::get_last_pos().y; + state->match_iterator.finder = finder; + embark_assist::overlay::initiate_match(); + } + + //=============================================================================== + + void shutdown() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::survey::shutdown(); + embark_assist::finder_ui::shutdown(); + embark_assist::overlay::shutdown(); + delete state; + state = nullptr; + } + } +} + +//======================================================================================= + +command_result embark_assistant (color_ostream &out, std::vector & parameters); + +//======================================================================================= + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "embark-assistant", "Embark site selection support.", + embark_assistant, true, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command starts the embark-assist plugin that provides embark site\n" + " selection help. It has to be called while the pre-embark screen is\n" + " displayed and shows extended (and correct(?)) resource information for\n" + " the embark rectangle as well as normally undisplayed sites in the\n" + " current embark region. It also has a site selection tool with more\n" + " options than DF's vanilla search tool. For detailed help invoke the\n" + " in game info screen. Requires 46 lines to display properly.\n" + )); + return CR_OK; +} + +//======================================================================================= + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +//======================================================================================= + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case DFHack::SC_UNKNOWN: + break; + + case DFHack::SC_WORLD_LOADED: + break; + + case DFHack::SC_WORLD_UNLOADED: + case DFHack::SC_MAP_LOADED: + if (embark_assist::main::state) { + embark_assist::main::shutdown(); + } + break; + + case DFHack::SC_MAP_UNLOADED: + break; + + case DFHack::SC_VIEWSCREEN_CHANGED: + break; + + case DFHack::SC_CORE_INITIALIZED: + break; + + case DFHack::SC_BEGIN_UNLOAD: + break; + + case DFHack::SC_PAUSED: + break; + + case DFHack::SC_UNPAUSED: + break; + } + return CR_OK; +} + + +//======================================================================================= + +command_result embark_assistant(color_ostream &out, std::vector & parameters) +{ + if (!parameters.empty()) + return CR_WRONG_USAGE; + + CoreSuspender suspend; + + auto screen = Gui::getViewscreenByType(0); + if (!screen) { + out.printerr("This plugin works only in the embark site selection phase.\n"); + return CR_WRONG_USAGE; + } + + df::world_data *world_data = world->world_data; + + if (embark_assist::main::state) { + out.printerr("You can't invoke the embark assistant while it's already active.\n"); + return CR_WRONG_USAGE; + } + + embark_assist::main::state = new embark_assist::main::states; + + embark_assist::main::state->match_iterator.active = false; + + // Find the end of the normal inorganic definitions. + embark_assist::main::state->max_inorganic = 0; + for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) { + if (world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i; + } + embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement + + if (!embark_assist::overlay::setup(plugin_self, + embark_assist::main::embark_update, + embark_assist::main::match, + embark_assist::main::clear_match, + embark_assist::main::find, + embark_assist::main::shutdown, + embark_assist::main::state->max_inorganic)) { + return CR_FAILURE; + } + + embark_assist::survey::setup(embark_assist::main::state->max_inorganic); + embark_assist::main::state->geo_summary.resize(world_data->geo_biomes.size()); + embark_assist::main::state->survey_results.resize(world->worldgen.worldgen_parms.dim_x); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + embark_assist::main::state->survey_results[i].resize(world->worldgen.worldgen_parms.dim_y); + + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + embark_assist::main::state->survey_results[i][k].surveyed = false; + embark_assist::main::state->survey_results[i][k].aquifer_count = 0; + embark_assist::main::state->survey_results[i][k].clay_count = 0; + embark_assist::main::state->survey_results[i][k].sand_count = 0; + embark_assist::main::state->survey_results[i][k].flux_count = 0; + embark_assist::main::state->survey_results[i][k].min_region_soil = 10; + embark_assist::main::state->survey_results[i][k].max_region_soil = 0; + embark_assist::main::state->survey_results[i][k].waterfall = false; + embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None; + + for (uint8_t l = 1; l < 10; l++) { + embark_assist::main::state->survey_results[i][k].biome_index[l] = -1; + embark_assist::main::state->survey_results[i][k].biome[l] = -1; + embark_assist::main::state->survey_results[i][k].evil_weather[l] = false; + embark_assist::main::state->survey_results[i][k].reanimating[l] = false; + embark_assist::main::state->survey_results[i][k].thralling[l] = false; + } + + for (uint8_t l = 0; l < 2; l++) { + embark_assist::main::state->survey_results[i][k].savagery_count[l] = 0; + embark_assist::main::state->survey_results[i][k].evilness_count[l] = 0; + } + embark_assist::main::state->survey_results[i][k].metals.resize(embark_assist::main::state->max_inorganic); + embark_assist::main::state->survey_results[i][k].economics.resize(embark_assist::main::state->max_inorganic); + embark_assist::main::state->survey_results[i][k].minerals.resize(embark_assist::main::state->max_inorganic); + } + } + + embark_assist::survey::high_level_world_survey(&embark_assist::main::state->geo_summary, + &embark_assist::main::state->survey_results); + + embark_assist::main::state->match_results.resize(world->worldgen.worldgen_parms.dim_x); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + embark_assist::main::state->match_results[i].resize(world->worldgen.worldgen_parms.dim_y); + } + + embark_assist::survey::clear_results(&embark_assist::main::state->match_results); + embark_assist::survey::survey_region_sites(&embark_assist::main::state->region_sites); + embark_assist::overlay::set_sites(&embark_assist::main::state->region_sites); + + embark_assist::defs::mid_level_tiles mlt; + embark_assist::survey::survey_mid_level_tile(&embark_assist::main::state->geo_summary, &embark_assist::main::state->survey_results, &mlt); + embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->site_info, false); + embark_assist::overlay::set_embark(&embark_assist::main::state->site_info); + + return CR_OK; +} diff --git a/plugins/embark-assistant/embark-assistant.h b/plugins/embark-assistant/embark-assistant.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/plugins/embark-assistant/embark-assistant.h @@ -0,0 +1 @@ +#pragma once diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp new file mode 100644 index 000000000..3f972635f --- /dev/null +++ b/plugins/embark-assistant/finder_ui.cpp @@ -0,0 +1,1402 @@ +#include +#include "Core.h" +#include + +#include + +#include "Types.h" + +#include "MemAccess.h" +#include "df/biome_type.h" +#include "df/inorganic_raw.h" +#include "df/material_flags.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_region_type.h" +#include "df/world_raws.h" + +#include "embark-assistant.h" +#include "finder_ui.h" +#include "screen.h" + +using df::global::world; + +#define profile_file_name "./data/init/embark_assistant_profile.txt" + +namespace embark_assist { + namespace finder_ui { + + enum class fields : int8_t { + x_dim, + y_dim, + savagery_calm, + savagery_medium, + savagery_savage, + good, + neutral, + evil, + aquifer, + min_river, + max_river, + waterfall, + flat, + clay, + sand, + flux, + soil_min, + soil_min_everywhere, + soil_max, + evil_weather, + reanimation, + thralling, + spire_count_min, + spire_count_max, + magma_min, + magma_max, + biome_count_min, + biome_count_max, + region_type_1, + region_type_2, + region_type_3, + biome_1, + biome_2, + biome_3, + metal_1, + metal_2, + metal_3, + economic_1, + economic_2, + economic_3, + mineral_1, + mineral_2, + mineral_3 + }; + fields first_fields = fields::x_dim; + fields last_fields = fields::mineral_3; + + struct display_map_elements { + std::string text; + int16_t key; + }; + + typedef std::vector display_maps; + typedef std::vector name_lists; + typedef std::list< display_map_elements> sort_lists; + + struct ui_lists { + uint16_t current_display_value; // Not the value itself, but a reference to its index. + int16_t current_value; // The integer representation of the value (if an enum). + uint16_t current_index; // What's selected + uint16_t focus; // The value under the (possibly inactive) cursor + display_maps list; // The strings to be displayed together with keys + // to allow location of the actual elements (e.g. a raws.inorganics mat_index + // or underlying enum value). + }; + + typedef std::vector uis; + + const DFHack::Screen::Pen active_pen(' ', COLOR_YELLOW); + const DFHack::Screen::Pen passive_pen(' ', COLOR_DARKGREY); + const DFHack::Screen::Pen normal_pen(' ', COLOR_GREY); + const DFHack::Screen::Pen white_pen(' ', COLOR_WHITE); + const DFHack::Screen::Pen lr_pen(' ', COLOR_LIGHTRED); + + //========================================================================================================== + + struct states { + embark_assist::defs::find_callbacks find_callback; + uis ui; + display_maps finder_list; // Don't need the element key, but it's easier to use the same type. + uint16_t finder_list_focus; + bool finder_list_active; + uint16_t max_inorganic; + }; + + static states *state = 0; + + //========================================================================================================== + + bool compare(const display_map_elements& first, const display_map_elements& second) { + uint16_t i = 0; + while (i < first.text.length() && i < second.text.length()) { + if (first.text[i] < second.text[i]) { + return true; + } + else if (first.text[i] > second.text[i]) { + return false; + } + ++i; + } + return first.text.length() < second.text.length(); + } + + //========================================================================================================== + + void append(sort_lists *sort_list, display_map_elements element) { + sort_lists::iterator iterator; + for (iterator = sort_list->begin(); iterator != sort_list->end(); ++iterator) { + if (iterator->key == element.key) { + return; + } + } + sort_list->push_back(element); + } + + //========================================================================================================== + + void save_profile() { + color_ostream_proxy out(Core::getInstance().getConsole()); + + FILE* outfile = fopen(profile_file_name, "w"); + fields i = first_fields; + + while (true) { + for (size_t k = 0; k < state->ui[static_cast(i)]->list.size(); k++) { + if (state->ui[static_cast(i)]->current_value == state->ui[static_cast(i)]->list[k].key) { + fprintf(outfile, "[%s:%s]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->list[k].text.c_str()); + break; + } + } +// fprintf(outfile, "[%s:%i]\n", state->finder_list[static_cast(i)].text.c_str(), state->ui[static_cast(i)]->current_value); + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + fclose(outfile); + } + + //========================================================================================================== + + void load_profile() { + color_ostream_proxy out(Core::getInstance().getConsole()); + FILE* infile = fopen(profile_file_name, "r"); + + if (!infile) { + out.printerr("No profile file found at %s\n", profile_file_name); + return; + } + + fields i = first_fields; + char line[80]; + int count = 80; + bool found; + + while (true) { + if (!fgets(line, count, infile) || line[0] != '[') { + out.printerr("Failed to find token start '[' at line %i\n", static_cast(i)); + fclose(infile); + return; + } + + for (int k = 1; k < count; k++) { + if (line[k] == ':') { + for (int l = 1; l < k; l++) { + if (state->finder_list[static_cast(i)].text.c_str()[l - 1] != line[l]) { + out.printerr("Token mismatch of %s vs %s\n", line, state->finder_list[static_cast(i)].text.c_str()); + fclose(infile); + return; + } + } + + found = false; + + for (size_t l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { + for (int m = k + 1; m < count; m++) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' && + line[m] == ']') { + found = true; + } + break; + } + } + if (found) { + break; + } + } + + if (!found) { + out.printerr("Value extraction failure from %s\n", line); + fclose(infile); + return; + } + + break; + } + } + + if (!found) { + out.printerr("Value delimiter not found in %s\n", line); + fclose(infile); + return; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + fclose(infile); + + // Checking done. No do the work. + + infile = fopen(profile_file_name, "r"); + i = first_fields; + + while (true) { + if (!fgets(line, count, infile)) + { + break; + } + + for (int k = 1; k < count; k++) { + if (line[k] == ':') { + + found = false; + + for (size_t l = 0; l < state->ui[static_cast(i)]->list.size(); l++) { + for (int m = k + 1; m < count; m++) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) { + if (state->ui[static_cast(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' && + line[m] == ']') { + state->ui[static_cast(i)]->current_value = state->ui[static_cast(i)]->list[l].key; + state->ui[static_cast(i)]->current_display_value = l; + found = true; + } + + break; + } + } + if (found) { + break; + } + } + + break; + } + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + fclose(infile); + } + + //========================================================================================================== + + void ui_setup(embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (!embark_assist::finder_ui::state) { + state = new(states); + state->finder_list_focus = 0; + state->finder_list_active = true; + state->find_callback = find_callback; + state->max_inorganic = max_inorganic; + } + + fields i = first_fields; + ui_lists *element; + + while (true) { + element = new ui_lists; + element->current_display_value = 0; + element->current_index = 0; + element->focus = 0; + + switch (i) { + case fields::x_dim: + for (int16_t k = 1; k < 16; k++) { + element->list.push_back({ std::to_string(k), k }); + } + + break; + + case fields::y_dim: + for (int16_t k = 1; k < 16; k++) { + element->list.push_back({ std::to_string(k), k }); + } + + break; + + case fields::savagery_calm: + case fields::savagery_medium: + case fields::savagery_savage: + case fields::good: + case fields::neutral: + case fields::evil: + { + embark_assist::defs::evil_savagery_values k = embark_assist::defs::evil_savagery_values::NA; + while (true) { + switch (k) { + case embark_assist::defs::evil_savagery_values::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::evil_savagery_values::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::aquifer: + { + embark_assist::defs::aquifer_ranges k = embark_assist::defs::aquifer_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::aquifer_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Partial: + element->list.push_back({ "Partial", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + element->list.push_back({ "Not All", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::aquifer_ranges::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::min_river: + case fields::max_river: + { + embark_assist::defs::river_ranges k = embark_assist::defs::river_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::river_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::None: + element->list.push_back({ "None", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Brook: + element->list.push_back({ "Brook", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Stream: + element->list.push_back({ "Stream", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Minor: + element->list.push_back({ "Minor", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Medium: + element->list.push_back({ "Medium", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Major: + element->list.push_back({ "Major", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::river_ranges::Major) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::waterfall: + case fields::flat: + case fields::evil_weather: + case fields::reanimation: + case fields::thralling: + { + embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::yes_no_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::yes_no_ranges::Yes: + element->list.push_back({ "Yes", static_cast(k) }); + break; + + case embark_assist::defs::yes_no_ranges::No: + element->list.push_back({ "No", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::yes_no_ranges::No) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::soil_min_everywhere: + { + embark_assist::defs::all_present_ranges k = embark_assist::defs::all_present_ranges::All; + while (true) { + switch (k) { + case embark_assist::defs::all_present_ranges::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::all_present_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::all_present_ranges::Present) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::clay: + case fields::sand: + case fields::flux: + { + embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::present_absent_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::present_absent_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::present_absent_ranges::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::present_absent_ranges::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::soil_min: + case fields::soil_max: + { + embark_assist::defs::soil_ranges k = embark_assist::defs::soil_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::soil_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::None: + element->list.push_back({ "None", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Very_Shallow: + element->list.push_back({ "Very Shallow", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Shallow: + element->list.push_back({ "Shallow", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Deep: + element->list.push_back({ "Deep", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + element->list.push_back({ "Very Deep", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::soil_ranges::Very_Deep) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::spire_count_min: + case fields::spire_count_max: + for (int16_t k = -1; k <= 9; k++) { + if (k == -1) { + element->list.push_back({ "N/A", k }); + } + else { + element->list.push_back({ std::to_string(k), k }); + } + } + + break; + + case fields::magma_min: + case fields::magma_max: + { + embark_assist::defs::magma_ranges k = embark_assist::defs::magma_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::magma_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Cavern_3: + element->list.push_back({ "Third Cavern", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Cavern_2: + element->list.push_back({ "Second Cavern", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Cavern_1: + element->list.push_back({ "First Cavern", static_cast(k) }); + break; + + case embark_assist::defs::magma_ranges::Volcano: + element->list.push_back({ "Volcano", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::magma_ranges::Volcano) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::biome_count_min: + case fields::biome_count_max: + for (int16_t k = 0; k < 10; k++) { + if (k == 0) { + element->list.push_back({ "N/A", -1 }); + } + else { + element->list.push_back({ std::to_string(k), k }); + } + } + + break; + + case fields::region_type_1: + case fields::region_type_2: + case fields::region_type_3: + { + std::list name; + std::list::iterator iterator; + + FOR_ENUM_ITEMS(world_region_type, iter) { + name.push_back({ ENUM_KEY_STR(world_region_type, iter), static_cast(iter) }); + } + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::biome_1: + case fields::biome_2: + case fields::biome_3: + { + sort_lists name; + sort_lists::iterator iterator; + + FOR_ENUM_ITEMS(biome_type, iter) { + std::string s = ENUM_KEY_STR(biome_type, iter); + + if (s.substr(0, 4) != "POOL" && + s.substr(0, 5) != "RIVER" && + s.substr(0, 3) != "SUB") { + name.push_back({ s, static_cast(iter) }); + } + } + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::metal_1: + case fields::metal_2: + case fields::metal_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (uint16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + for (uint16_t l = 0; l < world->raws.inorganics[k]->metal_ore.mat_index.size(); l++) { + append(&name, { world->raws.inorganics[world->raws.inorganics[k]->metal_ore.mat_index[l]]->id, + world->raws.inorganics[k]->metal_ore.mat_index[l] }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::economic_1: + case fields::economic_2: + case fields::economic_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (int16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + if (world->raws.inorganics[k]->economic_uses.size() != 0 && + !world->raws.inorganics[k]->material.flags.is_set(df::material_flags::IS_METAL)) { + append(&name, { world->raws.inorganics[k]->id, k }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::mineral_1: + case fields::mineral_2: + case fields::mineral_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (int16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + if (world->raws.inorganics[k]->environment.location.size() != 0 || + world->raws.inorganics[k]->environment_spec.mat_index.size() != 0 || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::SEDIMENTARY) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::IGNEOUS_EXTRUSIVE) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::IGNEOUS_INTRUSIVE) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::METAMORPHIC) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::SOIL)) { + append(&name, { world->raws.inorganics[k]->id, k }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + } + + element->current_value = element->list[0].key; + + state->ui.push_back(element); + + switch (i) { + case fields::x_dim: + state->finder_list.push_back({ "X Dimension", static_cast(i) }); + break; + + case fields::y_dim: + state->finder_list.push_back({ "Y Dimension", static_cast(i) }); + break; + + case fields::savagery_calm: + state->finder_list.push_back({ "Low Savagery", static_cast(i) }); + break; + + case fields::savagery_medium: + state->finder_list.push_back({ "Medium Savagery", static_cast(i) }); + break; + + case fields::savagery_savage: + state->finder_list.push_back({ "High Savagery", static_cast(i) }); + break; + + case fields::good: + state->finder_list.push_back({ "Good", static_cast(i) }); + break; + + case fields::neutral: + state->finder_list.push_back({ "Neutral", static_cast(i) }); + break; + + case fields::evil: + state->finder_list.push_back({ "Evil", static_cast(i) }); + break; + + case fields::aquifer: + state->finder_list.push_back({ "Aquifer", static_cast(i) }); + break; + + case fields::min_river: + state->finder_list.push_back({ "Min River", static_cast(i) }); + break; + + case fields::max_river: + state->finder_list.push_back({ "Max River", static_cast(i) }); + break; + + case fields::waterfall: + state->finder_list.push_back({ "Waterfall", static_cast(i) }); + break; + + case fields::flat: + state->finder_list.push_back({ "Flat", static_cast(i) }); + break; + + case fields::soil_min_everywhere: + state->finder_list.push_back({ "Min Soil Everywhere", static_cast(i) }); + break; + + case fields::evil_weather: + state->finder_list.push_back({ "Evil Weather", static_cast(i) }); + break; + + case fields::reanimation: + state->finder_list.push_back({ "Reanimation", static_cast(i) }); + break; + + case fields::thralling: + state->finder_list.push_back({ "Thralling", static_cast(i) }); + break; + + case fields::clay: + state->finder_list.push_back({ "Clay", static_cast(i) }); + break; + + case fields::sand: + state->finder_list.push_back({ "Sand", static_cast(i) }); + break; + + case fields::flux: + state->finder_list.push_back({ "Flux", static_cast(i) }); + break; + + case fields::soil_min: + state->finder_list.push_back({ "Min Soil", static_cast(i) }); + break; + + case fields::soil_max: + state->finder_list.push_back({ "Max Soil", static_cast(i) }); + break; + + case fields::spire_count_min: + state->finder_list.push_back({ "Min Adamantine", static_cast(i) }); + break; + + case fields::spire_count_max: + state->finder_list.push_back({ "Max Adamantine", static_cast(i) }); + break; + + case fields::magma_min: + state->finder_list.push_back({ "Min Magma", static_cast(i) }); + break; + + case fields::magma_max: + state->finder_list.push_back({ "Max Magma", static_cast(i) }); + break; + + case fields::biome_count_min: + state->finder_list.push_back({ "Min Biome Count", static_cast(i) }); + break; + + case fields::biome_count_max: + state->finder_list.push_back({ "Max Biome Count", static_cast(i) }); + break; + + case fields::region_type_1: + state->finder_list.push_back({ "Region Type 1", static_cast(i) }); + break; + + case fields::region_type_2: + state->finder_list.push_back({ "Region Type 2", static_cast(i) }); + break; + + case fields::region_type_3: + state->finder_list.push_back({ "Region Type 3", static_cast(i) }); + break; + + case fields::biome_1: + state->finder_list.push_back({ "Biome 1", static_cast(i) }); + break; + + case fields::biome_2: + state->finder_list.push_back({ "Biome 2", static_cast(i) }); + break; + + case fields::biome_3: + state->finder_list.push_back({ "Biome 3", static_cast(i) }); + break; + + case fields::metal_1: + state->finder_list.push_back({ "Metal 1", static_cast(i) }); + break; + + case fields::metal_2: + state->finder_list.push_back({ "Metal 2", static_cast(i) }); + break; + + case fields::metal_3: + state->finder_list.push_back({ "Metal 3", static_cast(i) }); + break; + + case fields::economic_1: + state->finder_list.push_back({ "Economic 1", static_cast(i) }); + break; + + case fields::economic_2: + state->finder_list.push_back({ "Economic 2", static_cast(i) }); + break; + + case fields::economic_3: + state->finder_list.push_back({ "Economic 3", static_cast(i) }); + break; + + case fields::mineral_1: + state->finder_list.push_back({ "Mineral 1", static_cast(i) }); + break; + + case fields::mineral_2: + state->finder_list.push_back({ "Mineral 2", static_cast(i) }); + break; + + case fields::mineral_3: + state->finder_list.push_back({ "Mineral 3", static_cast(i) }); + break; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + // Default embark area size to that of the current selection. The "size" calculation is actually one + // off to compensate for the list starting with 1 at index 0. + // + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + state->ui[static_cast(fields::x_dim)]->current_display_value = + Gui::getViewscreenByType(0)->location.embark_pos_max.x - + Gui::getViewscreenByType(0)->location.embark_pos_min.x; + state->ui[static_cast(fields::x_dim)]->current_index = + state->ui[static_cast(fields::x_dim)]->current_display_value; + state->ui[static_cast(fields::x_dim)]->current_value = + state->ui[static_cast(fields::x_dim)]->current_display_value + 1; + + state->ui[static_cast(fields::y_dim)]->current_display_value = + Gui::getViewscreenByType(0)->location.embark_pos_max.y - + Gui::getViewscreenByType(0)->location.embark_pos_min.y; + state->ui[static_cast(fields::y_dim)]->current_index = + state->ui[static_cast(fields::y_dim)]->current_display_value; + state->ui[static_cast(fields::y_dim)]->current_value = + state->ui[static_cast(fields::y_dim)]->current_display_value + 1; + } + + + //========================================================================================================== + + void find() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::defs::finders finder = {}; + fields i = first_fields; + + while (true) { + switch (i) { + case fields::x_dim: + finder.x_dim = state->ui[static_cast(i)]->current_value; + break; + + case fields::y_dim: + finder.y_dim = state->ui[static_cast(i)]->current_value; + break; + + case fields::savagery_calm: + finder.savagery[0] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::savagery_medium: + finder.savagery[1] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + case fields::savagery_savage: + finder.savagery[2] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::good: + finder.evilness[0] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::neutral: + finder.evilness[1] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::evil: + finder.evilness[2] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::aquifer: + finder.aquifer = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::min_river: + finder.min_river = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::max_river: + finder.max_river = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::waterfall: + finder.waterfall = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::flat: + finder.flat = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_min_everywhere: + finder.soil_min_everywhere = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::evil_weather: + finder.evil_weather = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::reanimation: + finder.reanimation = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::thralling: + finder.thralling = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::clay: + finder.clay = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::sand: + finder.sand = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::flux: + finder.flux = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_min: + finder.soil_min = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_max: + finder.soil_max = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::spire_count_min: + finder.spire_count_min = state->ui[static_cast(i)]->current_value; + break; + + case fields::spire_count_max: + finder.spire_count_max = state->ui[static_cast(i)]->current_value; + break; + + case fields::magma_min: + finder.magma_min = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::magma_max: + finder.magma_max = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::biome_count_min: + finder.biome_count_min = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_count_max: + finder.biome_count_max = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_1: + finder.region_type_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_2: + finder.region_type_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_3: + finder.region_type_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_1: + finder.biome_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_2: + finder.biome_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_3: + finder.biome_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_1: + finder.metal_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_2: + finder.metal_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_3: + finder.metal_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_1: + finder.economic_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_2: + finder.economic_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_3: + finder.economic_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_1: + finder.mineral_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_2: + finder.mineral_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_3: + finder.mineral_3 = state->ui[static_cast(i)]->current_value; + break; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + state->find_callback(finder); + } + + //========================================================================================================== + + class ViewscreenFindUi : public dfhack_viewscreen + { + public: + ViewscreenFindUi(); + + void feed(std::set *input); + + void render(); + + std::string getFocusString() { return "Finder UI"; } + + private: + }; + + //=============================================================================== + + void ViewscreenFindUi::feed(std::set *input) { + if (input->count(df::interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + + } else if (input->count(df::interface_key::CURSOR_LEFT) || + input->count(df::interface_key::CURSOR_RIGHT)) { + state->finder_list_active = !state->finder_list_active; + + } else if (input->count(df::interface_key::CURSOR_UP)) { + if (state->finder_list_active) { + if (state->finder_list_focus > 0) { + state->finder_list_focus--; + } + else { + state->finder_list_focus = static_cast(last_fields); + } + } + else { + if (state->ui[state->finder_list_focus]->current_index > 0) { + state->ui[state->finder_list_focus]->current_index--; + } else { + state->ui[state->finder_list_focus]->current_index = static_cast(state->ui[state->finder_list_focus]->list.size()) - 1; + } + } + + } else if (input->count(df::interface_key::CURSOR_DOWN)) { + if (state->finder_list_active) { + if (state->finder_list_focus < static_cast(last_fields)) { + state->finder_list_focus++; + } else { + state->finder_list_focus = 0; + } + } + else { + if (state->ui[state->finder_list_focus]->current_index < state->ui[state->finder_list_focus]->list.size() - 1) { + state->ui[state->finder_list_focus]->current_index++; + } else { + state->ui[state->finder_list_focus]->current_index = 0; + } + } + + } else if (input->count(df::interface_key::SELECT)) { + if (!state->finder_list_active) { + state->ui[state->finder_list_focus]->current_display_value = state->ui[state->finder_list_focus]->current_index; + state->ui[state->finder_list_focus]->current_value = state->ui[state->finder_list_focus]->list[state->ui[state->finder_list_focus]->current_index].key; + state->finder_list_active = true; + } + + } else if (input->count(df::interface_key::CUSTOM_F)) { + input->clear(); + Screen::dismiss(this); + find(); + return; + + } else if (input->count(df::interface_key::CUSTOM_S)) { // Save + save_profile(); + + } else if (input->count(df::interface_key::CUSTOM_L)) { // Load + load_profile(); + } + } + + //=============================================================================== + + void ViewscreenFindUi::render() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen_size = DFHack::Screen::getWindowSize(); + const int list_column = 53; + uint16_t offset = 0; + + Screen::clear(); + Screen::drawBorder("Embark Assistant Site Finder"); + + embark_assist::screen::paintString(lr_pen, 1, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_LEFT).c_str()); + embark_assist::screen::paintString(white_pen, 2, 1, "/"); + embark_assist::screen::paintString(lr_pen, 3, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_RIGHT).c_str()); + embark_assist::screen::paintString(white_pen, 4, 1, ":\x1b/\x1a"); + embark_assist::screen::paintString(lr_pen, 9, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_UP).c_str()); + embark_assist::screen::paintString(white_pen, 10, 1, "/"); + embark_assist::screen::paintString(lr_pen, 11, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_DOWN).c_str()); + embark_assist::screen::paintString(white_pen, 12, 1, ":Up/Down"); + embark_assist::screen::paintString(lr_pen, 21, 1, DFHack::Screen::getKeyDisplay(df::interface_key::SELECT).c_str()); + embark_assist::screen::paintString(white_pen, 26, 1, ":Select"); + embark_assist::screen::paintString(lr_pen, 34, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str()); + embark_assist::screen::paintString(white_pen, 35, 1, ":Find"); + embark_assist::screen::paintString(lr_pen, 41, 1, DFHack::Screen::getKeyDisplay(df::interface_key::LEAVESCREEN).c_str()); + embark_assist::screen::paintString(white_pen, 44, 1, ":Abort"); + embark_assist::screen::paintString(lr_pen, 51, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_S).c_str()); + embark_assist::screen::paintString(white_pen, 52, 1, ":Save"); + embark_assist::screen::paintString(lr_pen, 58, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_L).c_str()); + embark_assist::screen::paintString(white_pen, 59, 1, ":Load"); + + for (uint16_t i = 0; i < state->finder_list.size(); i++) { + if (i == state->finder_list_focus) { + if (state->finder_list_active) { + embark_assist::screen::paintString(active_pen, 1, 2 + i, state->finder_list[i].text); + } + else { + embark_assist::screen::paintString(passive_pen, 1, 2 + i, state->finder_list[i].text); + } + + embark_assist::screen::paintString(active_pen, + 21, + 2 + i, + state->ui[i]->list[state->ui[i]->current_display_value].text); + } + else { + embark_assist::screen::paintString(normal_pen, 1, 2 + i, state->finder_list[i].text); + + embark_assist::screen::paintString(white_pen, + 21, + 2 + i, + state->ui[i]->list[state->ui[i]->current_display_value].text); + } + + } + + // Implement scrolling lists if they don't fit on the screen. + if (int32_t(state->ui[state->finder_list_focus]->list.size()) > screen_size.y - 3) { + offset = (screen_size.y - 3) / 2; + if (state->ui[state->finder_list_focus]->current_index < offset) { + offset = 0; + } + else { + offset = state->ui[state->finder_list_focus]->current_index - offset; + } + + if (int32_t(state->ui[state->finder_list_focus]->list.size() - offset) < screen_size.y - 3) { + offset = static_cast(state->ui[state->finder_list_focus]->list.size()) - (screen_size.y - 3); + } + } + + for (uint16_t i = 0; i < state->ui[state->finder_list_focus]->list.size(); i++) { + if (i == state->ui[state->finder_list_focus]->current_index) { + if (!state->finder_list_active) { // Negated expression to get the display lines in the same order as above. + embark_assist::screen::paintString(active_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + else { + embark_assist::screen::paintString(passive_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + } + else { + embark_assist::screen::paintString(normal_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + } + + dfhack_viewscreen::render(); + } + + //=============================================================================== + + ViewscreenFindUi::ViewscreenFindUi() { + } + } +} + +//=============================================================================== +// Exported operations +//=============================================================================== + +void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { + if (!embark_assist::finder_ui::state) { // First call. Have to do the setup + embark_assist::finder_ui::ui_setup(find_callback, max_inorganic); + } + Screen::show(new ViewscreenFindUi(), plugin_self); +} + +//=============================================================================== + +void embark_assist::finder_ui::activate() { +} + +//=============================================================================== + +void embark_assist::finder_ui::shutdown() { + if (embark_assist::finder_ui::state) { + for (uint16_t i = 0; i < embark_assist::finder_ui::state->ui.size(); i++) { + delete embark_assist::finder_ui::state->ui[i]; + } + + delete embark_assist::finder_ui::state; + embark_assist::finder_ui::state = nullptr; + } +} diff --git a/plugins/embark-assistant/finder_ui.h b/plugins/embark-assistant/finder_ui.h new file mode 100644 index 000000000..70bf4ce42 --- /dev/null +++ b/plugins/embark-assistant/finder_ui.h @@ -0,0 +1,17 @@ +#pragma once + +#include "PluginManager.h" + +#include "DataDefs.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace finder_ui { + void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic); + void activate(); + void shutdown(); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp new file mode 100644 index 000000000..32fb28dbb --- /dev/null +++ b/plugins/embark-assistant/help_ui.cpp @@ -0,0 +1,326 @@ +#include "Core.h" +#include + +#include +#include +#include + +#include "Types.h" + +#include "help_ui.h" +#include "screen.h" + +using std::vector; + +namespace embark_assist{ + namespace help_ui { + enum class pages { + Intro, + General, + Finder, + Caveats + }; + + class ViewscreenHelpUi : public dfhack_viewscreen + { + public: + ViewscreenHelpUi(); + + void feed(std::set *input); + + void render(); + + std::string getFocusString() { return "Help UI"; } + + private: + pages current_page = pages::Intro; + }; + + //=============================================================================== + + void ViewscreenHelpUi::feed(std::set *input) { + if (input->count(df::interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + else if (input->count(df::interface_key::CHANGETAB)) { + switch (current_page) { + case pages::Intro: + current_page = pages::General; + break; + + case pages::General: + current_page = pages::Finder; + break; + + case pages::Finder: + current_page = pages::Caveats; + break; + + case pages::Caveats: + current_page = pages::Intro; + break; + } + } + else if (input->count(df::interface_key::SEC_CHANGETAB)) { + switch (current_page) { + case pages::Intro: + current_page = pages::Caveats; + break; + + case pages::General: + current_page = pages::Intro; + break; + + case pages::Finder: + current_page = pages::General; + break; + + case pages::Caveats: + current_page = pages::Intro; + break; + } + } + } + + //=============================================================================== + + void ViewscreenHelpUi::render() { + color_ostream_proxy out(Core::getInstance().getConsole()); + Screen::Pen pen(' ', COLOR_WHITE); + Screen::Pen site_pen = Screen::Pen(' ', COLOR_YELLOW, COLOR_BLACK, false); + Screen::Pen pen_lr(' ', COLOR_LIGHTRED); + + std::vector help_text; + + Screen::clear(); + + switch (current_page) { + case pages::Intro: + Screen::drawBorder("Embark Assistant Help/Info Introduction Page"); + + help_text.push_back("Embark Assistant is used on the embark selection screen to provide information"); + help_text.push_back("to help selecting a suitable embark site. It provides three services:"); + help_text.push_back("- Display of normally invisible sites overlayed on the region map."); + help_text.push_back("- Embark rectangle resources. More detailed and correct than vanilla DF."); + help_text.push_back("- Site find search. Richer set of selection criteria than the vanilla"); + help_text.push_back(" DF Find that Embark Assistant suppresses (by using the same key)."); + help_text.push_back(""); + help_text.push_back("The functionality requires a screen height of at least 46 lines to display"); + help_text.push_back("correctly (that's the height of the Finder screen), as fitting everything"); + help_text.push_back("onto a standard 80*25 screen would be too challenging. The help is adjusted"); + help_text.push_back("to fit into onto an 80*46 screen as well."); + help_text.push_back("This help/info is split over several screens, and you can move between them"); + help_text.push_back("using the TAB/Shift-TAB keys, and leave the help from any screen using ESC."); + help_text.push_back(""); + help_text.push_back("When the Embark Assistant is started it provides site information (if any)"); + help_text.push_back("as an overlay over the region map. Beneath that you will find a summary of"); + help_text.push_back("the resources in the current embark rectangle (explained in more detail on"); + help_text.push_back("the the next screen)."); + help_text.push_back("On the right side the command keys the Embark Assistant uses are listed"); + help_text.push_back("(this partially overwrites the DFHack Embark Tools information until the"); + help_text.push_back("screen is resized). It can also be mentioned that the DF 'f'ind key help"); + help_text.push_back("at the bottom of the screen is masked as the functionality is overridden by"); + help_text.push_back("the Embark Assistant."); + help_text.push_back("Main screen control keys used by the Embark Assistant:"); + help_text.push_back("i: Info/Help. Brings up this display."); + help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information."); + help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if"); + help_text.push_back(" one is under way."); + help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface."); + help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself"); + help_text.push_back(" when DF leaves the embark screen either through Abort Game or by"); + help_text.push_back(" embarking."); + help_text.push_back("Below this a Matching World Tiles count is displayed. It shows the number"); + help_text.push_back("of World Tiles that have at least one embark matching the Find criteria."); + + break; + + case pages::General: + Screen::drawBorder("Embark Assistant Help/Info General Page"); + + help_text.push_back("The Embark Assistant overlays the region map with characters indicating sites"); + help_text.push_back("normally not displayed by DF. The following key is used:"); + help_text.push_back("C: Camp"); + help_text.push_back("c: Cave. Only displayed if the DF worldgen parameter does not display caves."); + help_text.push_back("i: Important Location. The author doesn't actually know what those are."); + help_text.push_back("l: Lair"); + help_text.push_back("L: Labyrinth"); + help_text.push_back("M: Monument. The author is unsure how/if this is broken down further."); + help_text.push_back("S: Shrine"); + help_text.push_back("V: Vault"); + help_text.push_back("The Embark info below the region map differs from the vanilla DF display in a"); + help_text.push_back("few respects. Firstly, it shows resources in the embark rectangle, rather than"); + help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the"); + help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth"); + help_text.push_back("into consideration, so it can display resources that actually are cut away."); + help_text.push_back("(It can be noted that the DFHack Sand indicator does take these elements into"); + help_text.push_back("account)."); + help_text.push_back("The info the Embark Assistant displays is:"); + help_text.push_back("Sand, if present"); + help_text.push_back("Clay, if present"); + help_text.push_back("Min and Max soil depth in the embark rectangle."); + help_text.push_back("Flat indicator if all the tiles in the embark have the same elevation."); + help_text.push_back("Aquifer indicator, color coded as blue if all tiles have an aquifer and light"); + help_text.push_back("blue if some, but not all, tiles have one."); + help_text.push_back("Waterfall, if the embark has river elevation differences."); + help_text.push_back("Flux, if present"); + help_text.push_back("A list of all metals present in the embark."); + help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux"); + help_text.push_back("stones are economic, so they show up here as well."); + help_text.push_back("In addition to the above, the Find functionality can also produce blinking"); + help_text.push_back("overlays over the region map and the middle world map to indicate where"); + help_text.push_back("matching embarks are found. The region display marks the top left corner of"); + help_text.push_back("a matching embark rectangle as a matching tile."); + + break; + + case pages::Finder: + Screen::drawBorder("Embark Assistant Help/Info Find Page"); + + help_text.push_back("The Embark Assist Finder page is brought up with the f command key."); + help_text.push_back("The top of the Finder page lists the command keys available on the page:"); + help_text.push_back("4/6 or horizontal arrow keys to move between the element and element values."); + help_text.push_back("8/2 or vertical arrow keys to move up/down in the current list."); + help_text.push_back("ENTER to select a value in the value list, entering it among the selections."); + help_text.push_back("f to activate the Find functionality using the values in the middle column."); + help_text.push_back("ESC to leave the screen without activating a Find operation."); + help_text.push_back("s/l is used to save/load search profile to/from embark_assistant_profile.txt"); + help_text.push_back("stored in ./data/init. There's some minor error detection that will refuse"); + help_text.push_back("to load a file that doesn't check out."); + help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF"); + help_text.push_back("itself these parameters are initiated to match the actual embark rectangle"); + help_text.push_back("when a new search is initiated (prior results are cleared."); + help_text.push_back("The 6 Savagery and Evilness parameters takes some getting used to. They"); + help_text.push_back("allow for searching for embarks with e.g. has both Good and Evil tiles."); + help_text.push_back("All as a parameter means every embark tile has to have this value."); + help_text.push_back("Present means at least one embark tile has to have this value."); + help_text.push_back("Absent means the feature mustn't exist in any of the embark tiles."); + help_text.push_back("N/A means no restrictions are applied."); + help_text.push_back("The Aquifer criterion introduces some new parameters:"); + help_text.push_back("Partial means at least one tile has to have an aquifer, but it also has"); + help_text.push_back("to be absent from at least one tile. Not All means an aquifer is tolerated"); + help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have"); + help_text.push_back("any at all."); + help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of"); + help_text.push_back("Waterfall, Flat, etc. means one has to be Present and Absent respectivey."); + help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil"); + help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and"); + help_text.push_back("and Present."); + help_text.push_back("The parameters for biomes, regions, etc. all require that the required"); + help_text.push_back("feature is Present in the embark, and entering the same value multiple"); + help_text.push_back("times does nothing (the first match ticks all requirements off). It can be"); + help_text.push_back("noted that all the Economic materials are found in the much longer Mineral"); + help_text.push_back("list. Note that Find is a fairly time consuming task (as it is in vanilla)."); + break; + + case pages::Caveats: + Screen::drawBorder("Embark Assistant Help/Info Caveats Page"); + + help_text.push_back("Find searching first does a sanity check (e.g. max < min) and then a rough"); + help_text.push_back("world tile match to find tiles that may have matching embarks. This results"); + help_text.push_back("in an overlay of inverted yellow X on top of the middle world map. Then"); + help_text.push_back("those tiles are scanned in detail, one feature shell (16*16 world tile"); + help_text.push_back("block) at a time, and the results are displayed as green inverted X on"); + help_text.push_back("the same map (replacing or erasing the yellow ones). region map overlay"); + help_text.push_back("data is generated as well."); + help_text.push_back(""); + help_text.push_back("Caveats & technical stuff:"); + help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it"); + help_text.push_back(" to load feature shells and detailed region data, and this costs the"); + help_text.push_back(" least when done one feature shell at a time."); + help_text.push_back("- The search strategy causes detailed region data to update surveyed"); + help_text.push_back(" world info, and this can cause a subsequent search to generate a smaller"); + help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search."); + help_text.push_back(" However, this is a bug only if it causes the search to fail to find"); + help_text.push_back(" actual existing matches."); + help_text.push_back("- The site info is deduced by the author, so there may be errors and"); + help_text.push_back(" there are probably site types that end up not being identified."); + help_text.push_back("- Aquifer indications are based on the author's belief that they occur"); + help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or"); + help_text.push_back(" more."); + help_text.push_back("- The biome determination logic comes from code provided by Ragundo,"); + help_text.push_back(" with only marginal changes by the author. References can be found in"); + help_text.push_back(" the source file."); + help_text.push_back("- Thralling is determined by weather material interactions causing"); + help_text.push_back(" blinking, which the author believes is one of 4 thralling changes."); + help_text.push_back("- The geo information is gathered by code which is essentially a"); + help_text.push_back(" copy of parts of prospector's code adapted for this plugin."); + help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS."); + help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); + help_text.push_back("- Right world map overlay not implemented as author has failed to"); + help_text.push_back(" emulate the sizing logic exactly."); + help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires"); + help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); + help_text.push_back(" generated. It's likely this bug also affects magma pools."); + help_text.push_back(" This plugin does not address this but scripts can correct it."); + help_text.push_back("Version 0.3 2018-02-26"); + + break; + } + + // Add control keys to first line. + embark_assist::screen::paintString(pen_lr, 1, 1, DFHack::Screen::getKeyDisplay(df::interface_key::CHANGETAB).c_str()); + embark_assist::screen::paintString(pen, 4, 1, "/"); + embark_assist::screen::paintString(pen_lr, 5, 1, DFHack::Screen::getKeyDisplay(df::interface_key::SEC_CHANGETAB).c_str()); + embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page"); + embark_assist::screen::paintString(pen_lr, 34, 1, DFHack::Screen::getKeyDisplay(df::interface_key::LEAVESCREEN).c_str()); + embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help"); + + for (uint16_t i = 0; i < help_text.size(); i++) { + embark_assist::screen::paintString(pen, 1, 2 + i, help_text[i]); + } + + switch (current_page) { + case pages::Intro: + embark_assist::screen::paintString(pen_lr, 1, 26, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 27, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 28, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_C).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 30, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_Q).c_str()); + break; + + case pages::General: + embark_assist::screen::paintString(site_pen, 1, 4, "C"); + embark_assist::screen::paintString(site_pen, 1, 5, "c"); + embark_assist::screen::paintString(site_pen, 1, 6, "i"); + embark_assist::screen::paintString(site_pen, 1, 7, "l"); + embark_assist::screen::paintString(site_pen, 1, 8, "L"); + embark_assist::screen::paintString(site_pen, 1, 9, "M"); + embark_assist::screen::paintString(site_pen, 1, 10, "S"); + embark_assist::screen::paintString(site_pen, 1, 11, "V"); + break; + + case pages::Finder: + embark_assist::screen::paintString(pen_lr, 1, 4, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_LEFT).c_str()); + embark_assist::screen::paintString(pen_lr, 3, 4, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_RIGHT).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 5, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_UP).c_str()); + embark_assist::screen::paintString(pen_lr, 3, 5, DFHack::Screen::getKeyDisplay(df::interface_key::CURSOR_DOWN).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 6, DFHack::Screen::getKeyDisplay(df::interface_key::SELECT).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 7, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 8, DFHack::Screen::getKeyDisplay(df::interface_key::LEAVESCREEN).c_str()); + embark_assist::screen::paintString(pen_lr, 1, 9, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_S).c_str()); + embark_assist::screen::paintString(pen_lr, 3, 9, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_L).c_str()); + break; + + case pages::Caveats: + break; + } + dfhack_viewscreen::render(); + } + + //=============================================================================== + + ViewscreenHelpUi::ViewscreenHelpUi() { + } + } +} + +//=============================================================================== +// Exported operations +//=============================================================================== + +void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) { + Screen::show(new embark_assist::help_ui::ViewscreenHelpUi(), plugin_self); +} diff --git a/plugins/embark-assistant/help_ui.h b/plugins/embark-assistant/help_ui.h new file mode 100644 index 000000000..65a2e4f1d --- /dev/null +++ b/plugins/embark-assistant/help_ui.h @@ -0,0 +1,15 @@ +#pragma once + +#include "PluginManager.h" + +#include "DataDefs.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace help_ui { + void init(DFHack::Plugin *plugin_self); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp new file mode 100644 index 000000000..d4a2f8ee3 --- /dev/null +++ b/plugins/embark-assistant/matcher.cpp @@ -0,0 +1,1486 @@ +#include + +#include + +#include "DataDefs.h" +#include "df/biome_type.h" +#include "df/inorganic_raw.h" +#include "df/region_map_entry.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_raws.h" +#include "df/world_region.h" +#include "df/world_region_type.h" + +#include "matcher.h" +#include "survey.h" + +using df::global::world; + +namespace embark_assist { + namespace matcher { + + //======================================================================================= + + //======================================================================================= + + bool embark_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + uint16_t start_x, + uint16_t start_y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + bool savagery_found[3] = { false, false, false }; + bool evilness_found[3] = { false, false, false }; + uint16_t aquifer_count = 0; + bool river_found = false; + bool waterfall_found = false; + uint16_t river_elevation = 0xffff; + uint16_t elevation = mlt->at(start_x).at(start_y).elevation; + bool clay_found = false; + bool sand_found = false; + bool flux_found = false; + uint8_t max_soil = 0; + bool uneven = false; + bool evil_weather_found = false; + bool reanimation_found = false; + bool thralling_found = false; + uint8_t spire_count = 0; + int8_t magma_level = -1; + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + bool region_types[ENUM_LAST_ITEM(world_region_type) + 1]; + uint8_t biome_count; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + const uint16_t embark_size = finder->x_dim * finder->y_dim; + + if (finder->biome_count_min != -1 || + finder->biome_count_max != -1 || + finder->biome_1 != -1 || + finder->biome_2 != -1 || + finder->biome_3 != -1) { + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) biomes[i] = false; + } + + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(world_region_type); i++) region_types[i] = false; + + for (uint16_t i = start_x; i < start_x + finder->x_dim; i++) { + for (uint16_t k = start_y; k < start_y + finder->y_dim; k++) { + + // Savagery & Evilness + { + savagery_found[mlt->at(i).at(k).savagery_level] = true; + evilness_found[mlt->at(i).at(k).evilness_level] = true; + + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + while (true) { + if (mlt->at(i).at(k).savagery_level == static_cast(l)) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) return false; + } + else { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) return false; + } + + if (mlt->at(i).at(k).evilness_level == static_cast(l)) { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) return false; + } + else { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) return false; + } + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; + + case embark_assist::defs::aquifer_ranges::All: + if (!mlt->at(i).at(k).aquifer) return false; + aquifer_count++; + break; + + case embark_assist::defs::aquifer_ranges::Present: + case embark_assist::defs::aquifer_ranges::Partial: + case embark_assist::defs::aquifer_ranges::Not_All: + if (mlt->at(i).at(k).aquifer) aquifer_count++; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (mlt->at(i).at(k).aquifer) return false; + break; + } + + // River & Waterfall + if (mlt->at(i).at(k).river_present) { + // Actual size values were checked on the world tile level for min rivers + if (finder->max_river != embark_assist::defs::river_ranges::NA && + finder->max_river < static_cast(survey_results->at(x).at(y).river_size)) return false; + + if (river_found && river_elevation != mlt->at(i).at(k).river_elevation) { + if (finder->waterfall == embark_assist::defs::yes_no_ranges::No) return false; + waterfall_found = true; + } + river_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; + } + + // Flat + if (finder->flat == embark_assist::defs::yes_no_ranges::Yes && + elevation != mlt->at(i).at(k).elevation) return false; + + if (elevation != mlt->at(i).at(k).elevation) uneven = true; + + // Clay + if (mlt->at(i).at(k).clay) { + if (finder->clay == embark_assist::defs::present_absent_ranges::Absent) return false; + clay_found = true; + } + + // Sand + if (mlt->at(i).at(k).sand) { + if (finder->sand == embark_assist::defs::present_absent_ranges::Absent) return false; + sand_found = true; + } + + // Flux + if (mlt->at(i).at(k).flux) { + if (finder->flux == embark_assist::defs::present_absent_ranges::Absent) return false; + flux_found = true; + } + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + mlt->at(i).at(k).soil_depth < static_cast(finder->soil_min) && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::All) return false; + + if (max_soil < mlt->at(i).at(k).soil_depth) { + max_soil = mlt->at(i).at(k).soil_depth; + } + + // Max Soil + if (finder->soil_max != embark_assist::defs::soil_ranges::NA && + mlt->at(i).at(k).soil_depth > static_cast(finder->soil_max)) return false; + + // Evil Weather + if (survey_results->at(x).at(y).evil_weather[mlt->at(i).at(k).biome_offset]) { + if (finder->evil_weather == embark_assist::defs::yes_no_ranges::No) return false; + evil_weather_found = true; + } + + // Reanmation + if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) { + if (finder->reanimation == embark_assist::defs::yes_no_ranges::No) return false; + reanimation_found = true; + } + + // Thralling + if (survey_results->at(x).at(y).thralling[mlt->at(i).at(k).biome_offset]) { + if (finder->thralling == embark_assist::defs::yes_no_ranges::No) return false; + thralling_found = true; + } + + // Spires + if (mlt->at(i).at(k).adamantine_level != -1) { + spire_count++; + + if (finder->spire_count_max != -1 && + finder->spire_count_max < spire_count) return false; + } + + // Magma + if (mlt->at(i).at(k).magma_level != -1) { + if (mlt->at(i).at(k).magma_level > magma_level) + { + magma_level = mlt->at(i).at(k).magma_level; + if (finder->magma_max != embark_assist::defs::magma_ranges::NA && + static_cast(finder->magma_max) < magma_level) return false; + } + } + + // Biomes + biomes[survey_results->at(x).at(y).biome[mlt->at(i).at(k).biome_offset]] = true; + + // Region Type + region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset]]->type] = true; + + // Metals + metal_1 = metal_1 || mlt->at(i).at(k).metals[finder->metal_1]; + metal_2 = metal_2 || mlt->at(i).at(k).metals[finder->metal_2]; + metal_3 = metal_3 || mlt->at(i).at(k).metals[finder->metal_3]; + + // Economics + economic_1 = economic_1 || mlt->at(i).at(k).economics[finder->economic_1]; + economic_2 = economic_2 || mlt->at(i).at(k).economics[finder->economic_2]; + economic_3 = economic_3 || mlt->at(i).at(k).economics[finder->economic_3]; + + // Minerals + mineral_1 = mineral_1 || mlt->at(i).at(k).minerals[finder->mineral_1]; + mineral_2 = mineral_2 || mlt->at(i).at(k).minerals[finder->mineral_2]; + mineral_3 = mineral_3 || mlt->at(i).at(k).minerals[finder->mineral_3]; + } + } + + // Summary section, for all the stuff that require the complete picture + // + // Savagery & Evilness + { + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + + while (true) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Present && + !savagery_found[static_cast(l)]) return false; + + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Present && + !evilness_found[static_cast(l)]) return false; + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + case embark_assist::defs::aquifer_ranges::All: // Checked above + case embark_assist::defs::aquifer_ranges::Absent: // Ditto + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (aquifer_count == 0 || aquifer_count == embark_size) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (aquifer_count == embark_size) return false; + break; + } + + // River & Waterfall + if (!river_found && finder->min_river > embark_assist::defs::river_ranges::None) return false; + if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && !waterfall_found) return false; + + // Flat + if (!uneven && finder->flat == embark_assist::defs::yes_no_ranges::No) return false; + + // Clay + if (finder->clay == embark_assist::defs::present_absent_ranges::Present && !clay_found) return false; + + // Sand + if (finder->sand == embark_assist::defs::present_absent_ranges::Present && !sand_found) return false; + + // Flux + if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false; + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && + max_soil < static_cast(finder->soil_min)) return false; + + // Evil Weather + if (finder->evil_weather == embark_assist::defs::yes_no_ranges::Yes && !evil_weather_found) return false; + + // Reanimation + if (finder->reanimation == embark_assist::defs::yes_no_ranges::Yes && !reanimation_found) return false; + + // Thralling + if (finder->thralling == embark_assist::defs::yes_no_ranges::Yes && !thralling_found) return false; + + // Spires + if (finder->spire_count_min != -1 && finder->spire_count_min > spire_count) return false; + if (finder->spire_count_max != -1 && finder->spire_count_max < spire_count) return false; + + // Magma + if (// finder->magma_min != embark_assist::defs::magma_ranges::NA && // This check is redundant. + finder->magma_min > static_cast(magma_level)) return false; + + // Biomes + if (finder->biome_count_min != -1 || + finder->biome_count_max != -1) { + biome_count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) biome_count++; + } + + if (biome_count < finder->biome_count_min || + (finder->biome_count_max != -1 && + finder->biome_count_max < biome_count)) return false; + } + + if (finder->biome_1 != -1 && !biomes[finder->biome_1]) return false; + if (finder->biome_2 != -1 && !biomes[finder->biome_2]) return false; + if (finder->biome_3 != -1 && !biomes[finder->biome_3]) return false; + + // Region Type + if (finder->region_type_1 != -1 && !region_types[finder->region_type_1]) return false; + if (finder->region_type_2 != -1 && !region_types[finder->region_type_2]) return false; + if (finder->region_type_3 != -1 && !region_types[finder->region_type_3]) return false; + + // Metals, Economics, and Minerals + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + + return true; + } + + //======================================================================================= + + void mid_level_tile_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + bool match = false; + + for (uint16_t i = 0; i < 16; i++) { + for (uint16_t k = 0; k < 16; k++) { + if (i < 16 - finder->x_dim + 1 && k < 16 - finder->y_dim + 1) { + match_results->at(x).at(y).mlt_match[i][k] = embark_match(survey_results, mlt, x, y, i, k, finder); + match = match || match_results->at(x).at(y).mlt_match[i][k]; + } + else { + match_results->at(x).at(y).mlt_match[i][k] = false; + } + } + } + match_results->at(x).at(y).contains_match = match; + match_results->at(x).at(y).preliminary_match = false; + } + + //======================================================================================= + + bool world_tile_match(embark_assist::defs::world_tile_data *survey_results, + uint16_t x, + uint16_t y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + const uint16_t embark_size = finder->x_dim * finder->y_dim; + bool found; + + if (tile->surveyed) { + // Savagery + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->savagery[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->savagery_count[i] < embark_size) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->savagery_count[i] > 256 - embark_size) return false; + break; + } + } + + // Evilness + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->evilness[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->evilness_count[i] < embark_size) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->evilness_count[i] > 256 - embark_size) return false; + break; + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; // No restriction + + case embark_assist::defs::aquifer_ranges::All: + if (tile->aquifer_count < 256 - embark_size) return false; + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (tile->aquifer_count == 0 || + tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (tile->aquifer_count > 256 - embark_size) return false; + break; + } + + // River size. Every tile has riverless tiles, so max rivers has to be checked on the detailed level. + switch (tile->river_size) { + case embark_assist::defs::river_sizes::None: + if (finder->min_river > embark_assist::defs::river_ranges::None) return false; + break; + + case embark_assist::defs::river_sizes::Brook: + if (finder->min_river > embark_assist::defs::river_ranges::Brook) return false; + break; + + case embark_assist::defs::river_sizes::Stream: + if (finder->min_river > embark_assist::defs::river_ranges::Stream) return false; + break; + + case embark_assist::defs::river_sizes::Minor: + if (finder->min_river > embark_assist::defs::river_ranges::Minor) return false; + break; + + case embark_assist::defs::river_sizes::Medium: + if (finder->min_river > embark_assist::defs::river_ranges::Medium) return false; + break; + + case embark_assist::defs::river_sizes::Major: + if (finder->max_river != embark_assist::defs::river_ranges::NA) return false; + break; + } + + // Waterfall + switch (finder->waterfall) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->waterfall) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->waterfall && + embark_size == 256) return false; + break; + } + + // Flat. No world tile checks. Need to look at the details + + // Clay + switch (finder->clay) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->clay_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->clay_count > 256 - embark_size) return false; + break; + } + + // Sand + switch (finder->sand) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->sand_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->sand_count > 256 - embark_size) return false; + break; + } + + // Flux + switch (finder->flux) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->flux_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->flux_count > 256 - embark_size) return false; + break; + } + + // Soil Min + switch (finder->soil_min) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::None: + break; // No restriction + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->max_region_soil < 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->max_region_soil < 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->max_region_soil < 3) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + if (tile->max_region_soil < 4) return false; + break; + } + + // soil_min_everywhere only applies on the detailed level + + // Soil Max + switch (finder->soil_max) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::Very_Deep: + break; // No restriction + + case embark_assist::defs::soil_ranges::None: + if (tile->min_region_soil > 0) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->min_region_soil > 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->min_region_soil > 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->min_region_soil > 3) return false; + break; + } + + // Evil Weather + switch (finder->evil_weather) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->evil_weather_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->evil_weather_full) return false; + break; + } + + // Reanimating + switch (finder->reanimation) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->reanimating_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->reanimating_full) return false; + break; + } + + // Thralling + switch (finder->thralling) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->thralling_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->thralling_full) return false; + break; + } + + // Spire Count Min/Max + // Magma Min/Max + // Biome Count Min (Can't do anything with Max at this level) + if (finder->biome_count_min > tile->biome_count) return false; + + // Region Type 1 + if (finder->region_type_1 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_1) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 2 + if (finder->region_type_2 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_2) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 3 + if (finder->region_type_3 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_3) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Biome 1 + if (finder->biome_1 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_1) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 2 + if (finder->biome_2 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_2) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 3 + if (finder->biome_3 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_3) { + found = true; + break; + } + } + + if (!found) return false; + } + + if (finder->metal_1 != -1 || + finder->metal_2 != -1 || + finder->metal_3 != -1 || + finder->economic_1 != -1 || + finder->economic_2 != -1 || + finder->economic_3 != -1 || + finder->mineral_1 != -1 || + finder->mineral_2 != -1 || + finder->mineral_3 != -1) { + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + metal_1 = metal_1 || tile->metals[finder->metal_1]; + metal_2 = metal_2 || tile->metals[finder->metal_2]; + metal_3 = metal_3 || tile->metals[finder->metal_3]; + economic_1 = economic_1 || tile->economics[finder->economic_1]; + economic_2 = economic_2 || tile->economics[finder->economic_2]; + economic_3 = economic_3 || tile->economics[finder->economic_3]; + mineral_1 = mineral_1 || tile->minerals[finder->mineral_1]; + mineral_2 = mineral_2 || tile->minerals[finder->mineral_2]; + mineral_3 = mineral_3 || tile->minerals[finder->mineral_3]; + + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + } + } + else { // Not surveyed + // Savagery + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->savagery[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->savagery_count[i] == 256) return false; + break; + } + } + + // Evilness + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->evilness[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->evilness_count[i] == 256) return false; + break; + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; // No restriction + + case embark_assist::defs::aquifer_ranges::All: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (tile->aquifer_count == 0 || + tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (tile->aquifer_count == 256) return false; + break; + } + + // River size + switch (tile->river_size) { + case embark_assist::defs::river_sizes::None: + if (finder->min_river > embark_assist::defs::river_ranges::None) return false; + break; + + case embark_assist::defs::river_sizes::Brook: + if (finder->min_river > embark_assist::defs::river_ranges::Brook) return false; + break; + + case embark_assist::defs::river_sizes::Stream: + if (finder->min_river > embark_assist::defs::river_ranges::Stream) return false; + break; + + case embark_assist::defs::river_sizes::Minor: + if (finder->min_river > embark_assist::defs::river_ranges::Minor) return false; + break; + + case embark_assist::defs::river_sizes::Medium: + if (finder->min_river > embark_assist::defs::river_ranges::Medium) return false; + break; + + case embark_assist::defs::river_sizes::Major: + if (finder->max_river != embark_assist::defs::river_ranges::NA) return false; + break; + } + + // Waterfall + if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && + tile->river_size == embark_assist::defs::river_sizes::None) return false; + + // Flat. No world tile checks. Need to look at the details + + // Clay + switch (finder->clay) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->clay_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->clay_count == 256) return false; + break; + } + + // Sand + switch (finder->sand) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->sand_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->sand_count == 256) return false; + break; + } + + // Flux + switch (finder->flux) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->flux_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->flux_count == 256) return false; + break; + } + + // Soil Min + switch (finder->soil_min) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::None: + break; // No restriction + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->max_region_soil < 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->max_region_soil < 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->max_region_soil < 3) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + if (tile->max_region_soil < 4) return false; + break; + } + + // soil_min_everywhere only applies on the detailed level + + // Soil Max + // Can't say anything as the preliminary data isn't reliable + + // Evil Weather + switch (finder->evil_weather) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->evil_weather_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->evil_weather_full) return false; + break; + } + + // Reanimating + switch (finder->reanimation) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->reanimating_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->reanimating_full) return false; + break; + } + + // Thralling + switch (finder->thralling) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->thralling_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->thralling_full) return false; + break; + } + + // Spire Count Min/Max + // Magma Min/Max + // Biome Count Min (Can't do anything with Max at this level) + if (finder->biome_count_min > tile->biome_count) return false; + + // Region Type 1 + if (finder->region_type_1 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_1) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 2 + if (finder->region_type_2 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_2) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 3 + if (finder->region_type_3 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_3) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Biome 1 + if (finder->biome_1 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_1) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 2 + if (finder->biome_2 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_2) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 3 + if (finder->biome_3 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_3) { + found = true; + break; + } + } + + if (!found) return false; + } + + if (finder->metal_1 != -1 || + finder->metal_2 != -1 || + finder->metal_3 != -1 || + finder->economic_1 != -1 || + finder->economic_2 != -1 || + finder->economic_3 != -1 || + finder->mineral_1 != -1 || + finder->mineral_2 != -1 || + finder->mineral_3 != -1) { + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + metal_1 = metal_1 || tile->metals[finder->metal_1]; + metal_2 = metal_2 || tile->metals[finder->metal_2]; + metal_3 = metal_3 || tile->metals[finder->metal_3]; + economic_1 = economic_1 || tile->economics[finder->economic_1]; + economic_2 = economic_2 || tile->economics[finder->economic_2]; + economic_3 = economic_3 || tile->economics[finder->economic_3]; + mineral_1 = mineral_1 || tile->minerals[finder->mineral_1]; + mineral_2 = mineral_2 || tile->minerals[finder->mineral_2]; + mineral_3 = mineral_3 || tile->minerals[finder->mineral_3]; + + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + } + } + return true; + } + + //======================================================================================= + + uint32_t preliminary_world_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + uint32_t count = 0; + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + match_results->at(i).at(k).preliminary_match = + world_tile_match(survey_results, i, k, finder); + if (match_results->at(i).at(k).preliminary_match) count++; + match_results->at(i).at(k).contains_match = false; + } + } + + return count; + } + + //======================================================================================= + + void match_world_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results, + uint16_t x, + uint16_t y) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::defs::mid_level_tiles mlt; + + embark_assist::survey::survey_mid_level_tile(geo_summary, + survey_results, + &mlt); + + mid_level_tile_match(survey_results, + &mlt, + x, + y, + finder, + match_results); + } + } +} + +//======================================================================================= +// Visible operations +//======================================================================================= + +void embark_assist::matcher::move_cursor(uint16_t x, uint16_t y) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t original_x = screen->location.region_pos.x; + uint16_t original_y = screen->location.region_pos.y; + + uint16_t large_x = std::abs(original_x - x) / 10; + uint16_t small_x = std::abs(original_x - x) % 10; + uint16_t large_y = std::abs(original_y - y) / 10; + uint16_t small_y = std::abs(original_y - y) % 10; + + while (large_x > 0 || large_y > 0) { + if (large_x > 0 && large_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT_FAST); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT_FAST); + } + large_x--; + large_y--; + } + else if (large_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT_FAST); + } + large_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN_FAST); + } + large_y--; + } + } + + while (small_x > 0 || small_y > 0) { + if (small_x > 0 && small_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT); + } + small_x--; + small_y--; + } + else if (small_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + small_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + small_y--; + } + } +} + +//======================================================================================= + +uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iterator, + embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::match_results *match_results) { + + color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t x_end; + uint16_t y_end; + bool turn = false; + uint16_t count; + uint16_t preliminary_matches; + + if (!iterator->active) { + embark_assist::survey::clear_results(match_results); + + // Static check for impossible requirements + // + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.evilness[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } + + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All evilness requirements\n"); + return 0; + } + + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.savagery[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } + + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All savagery requirements\n"); + return 0; + } + + if (iterator->finder.max_river < iterator->finder.min_river && + iterator->finder.max_river != embark_assist::defs::river_ranges::NA) { + out.printerr("matcher::find: Will never find any due to max river < min river\n"); + return 0; + } + + if (iterator->finder.waterfall == embark_assist::defs::yes_no_ranges::Yes && + iterator->finder.max_river == embark_assist::defs::river_ranges::None) { + out.printerr("matcher::find: Will never find any waterfalls with None as max river\n"); + return 0; + } + + if (iterator->finder.soil_max < iterator->finder.soil_min && + iterator->finder.soil_max != embark_assist::defs::soil_ranges::NA) { + out.printerr("matcher::find: Will never find any matches with max soil < min soil\n"); + return 0; + } + + if (iterator->finder.spire_count_max < iterator->finder.spire_count_min && + iterator->finder.spire_count_max != -1) { + out.printerr("matcher::find: Will never find any matches with max spires < min spires\n"); + return 0; + } + + if (iterator->finder.magma_max < iterator->finder.magma_min && + iterator->finder.magma_max != embark_assist::defs::magma_ranges::NA) { + out.printerr("matcher::find: Will never find any matches with max magma < min magma\n"); + return 0; + } + + if (iterator->finder.biome_count_max < iterator->finder.biome_count_min && + iterator->finder.biome_count_max != -1) { + out.printerr("matcher::find: Will never find any matches with max biomes < min biomes\n"); + return 0; + } + + preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); + + if (preliminary_matches == 0) { + out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + return 0; + } + else { + out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + } + + while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + iterator->active = true; + iterator->i = 0; + iterator->k = 0; + iterator->x_right = true; + iterator->y_down = true; + iterator->inhibit_x_turn = false; + iterator->inhibit_y_turn = false; + iterator->count = 0; + } + + if ((iterator->k == world->worldgen.worldgen_parms.dim_x / 16 && iterator->x_right) || + (iterator->k == 0 && !iterator->x_right)) { + x_end = 0; + } + else { + x_end = 15; + } + + if (iterator->i == world->worldgen.worldgen_parms.dim_y / 16) { + y_end = 0; + } + else { + y_end = 15; + } + + for (uint16_t l = 0; l <= x_end; l++) { + for (uint16_t m = 0; m <= y_end; m++) { + // This is where the payload goes + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).preliminary_match) { + match_world_tile(geo_summary, + survey_results, + &iterator->finder, + match_results, + screen->location.region_pos.x, + screen->location.region_pos.y); + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).contains_match) { + iterator->count++; + } + } + else { + for (uint16_t n = 0; n < 16; n++) { + for (uint16_t p = 0; p < 16; p++) { + match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match[n][p] = false; + } + } + } + // End of payload section + + if (m != y_end) { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } + } + else { + if (screen->location.region_pos.x != 0 && + screen->location.region_pos.x != world->worldgen.worldgen_parms.dim_x - 1) { + turn = true; + } + else { + iterator->inhibit_y_turn = !iterator->inhibit_y_turn; + turn = iterator->inhibit_y_turn; + } + + if (turn) { + iterator->y_down = !iterator->y_down; + } + else { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } + } + } + } + + if (iterator->x_right) { // Won't do anything at the edge, so we don't bother filter those cases. + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + + if (!iterator->x_right && + screen->location.region_pos.x == 0) { + turn = !turn; + + if (turn) { + iterator->x_right = true; + } + } + else if (iterator->x_right && + screen->location.region_pos.x == world->worldgen.worldgen_parms.dim_x - 1) { + turn = !turn; + + if (turn) { + iterator->x_right = false; + } + } + } + // } + + iterator->k++; + if (iterator->k > world->worldgen.worldgen_parms.dim_x / 16) + { + iterator->k = 0; + iterator->i++; + iterator->active = !(iterator->i > world->worldgen.worldgen_parms.dim_y / 16); + + if (!iterator->active) { + embark_assist::matcher::move_cursor(iterator->x, iterator->y); + } + } + + return iterator->count; +} diff --git a/plugins/embark-assistant/matcher.h b/plugins/embark-assistant/matcher.h new file mode 100644 index 000000000..9887ffbd0 --- /dev/null +++ b/plugins/embark-assistant/matcher.h @@ -0,0 +1,21 @@ +#pragma once + +#include "DataDefs.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace matcher { + void move_cursor(uint16_t x, uint16_t y); + + // Used to iterate over the whole world to generate a map of world tiles + // that contain matching embarks. + // + uint16_t find(embark_assist::defs::match_iterators *iterator, + embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::match_results *match_results); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp new file mode 100644 index 000000000..13bbb4fc1 --- /dev/null +++ b/plugins/embark-assistant/overlay.cpp @@ -0,0 +1,439 @@ +#include + +#include "df/coord2d.h" +#include "df/inorganic_raw.h" +#include "df/dfhack_material_category.h" +#include "df/interface_key.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_raws.h" + +#include "finder_ui.h" +#include "help_ui.h" +#include "overlay.h" +#include "screen.h" + +using df::global::world; + +namespace embark_assist { + namespace overlay { + DFHack::Plugin *plugin_self; + const Screen::Pen empty_pen = Screen::Pen('\0', COLOR_YELLOW, COLOR_BLACK, false); + const Screen::Pen yellow_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_YELLOW, false); + const Screen::Pen green_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_GREEN, false); + + struct display_strings { + Screen::Pen pen; + std::string text; + }; + + typedef Screen::Pen *pen_column; + + struct states { + int blink_count = 0; + bool show = true; + + bool matching = false; + bool match_active = false; + + embark_update_callbacks embark_update; + match_callbacks match_callback; + clear_match_callbacks clear_match_callback; + embark_assist::defs::find_callbacks find_callback; + shutdown_callbacks shutdown_callback; + + Screen::Pen site_grid[16][16]; + uint8_t current_site_grid = 0; + + std::vector embark_info; + + Screen::Pen region_match_grid[16][16]; + + pen_column *world_match_grid = nullptr; + uint16_t match_count = 0; + + uint16_t max_inorganic; + }; + + static states *state = nullptr; + + //==================================================================== + +/* // Attempt to replicate the DF logic for sizing the right world map. This + // code seems to compute the values correctly, but the author hasn't been + // able to apply them at the same time as DF does to 100%. + // DF seems to round down on 0.5 values. + df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) { + uint16_t result; + + for (uint16_t factor = 1; factor < 17; factor++) { + result = map_size / factor; + if ((map_size - result * factor) * 2 != factor) { + result = (map_size + factor / 2) / factor; + } + + if (result <= available_screen) { + return {result, factor}; + } + } + return{16, 16}; // Should never get here. + } +*/ + //==================================================================== + + class ViewscreenOverlay : public df::viewscreen_choose_start_sitest + { + public: + typedef df::viewscreen_choose_start_sitest interpose_base; + + void send_key(const df::interface_key &key) + { + std::set< df::interface_key > keys; + keys.insert(key); + this->feed(&keys); + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (input->count(df::interface_key::CUSTOM_Q)) { + state->shutdown_callback(); + return; + + } + else if (input->count(df::interface_key::SETUP_LOCAL_X_MUP) || + input->count(df::interface_key::SETUP_LOCAL_X_MDOWN) || + input->count(df::interface_key::SETUP_LOCAL_Y_MUP) || + input->count(df::interface_key::SETUP_LOCAL_Y_MDOWN) || + input->count(df::interface_key::SETUP_LOCAL_X_UP) || + input->count(df::interface_key::SETUP_LOCAL_X_DOWN) || + input->count(df::interface_key::SETUP_LOCAL_Y_UP) || + input->count(df::interface_key::SETUP_LOCAL_Y_DOWN)) { + INTERPOSE_NEXT(feed)(input); + state->embark_update(); + } + else if (input->count(df::interface_key::CUSTOM_C)) { + state->match_active = false; + state->matching = false; + state->clear_match_callback(); + } + else if (input->count(df::interface_key::CUSTOM_F)) { + if (!state->match_active && !state->matching) { + embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic); + } + } + else if (input->count(df::interface_key::CUSTOM_I)) { + embark_assist::help_ui::init(embark_assist::overlay::plugin_self); + } + else { + INTERPOSE_NEXT(feed)(input); + } + } + + //==================================================================== + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto current_screen = Gui::getViewscreenByType(0); + int16_t x = current_screen->location.region_pos.x; + int16_t y = current_screen->location.region_pos.y; + auto width = Screen::getWindowSize().x; + auto height = Screen::getWindowSize().y; + + state->blink_count++; + if (state->blink_count == 35) { + state->blink_count = 0; + state->show = !state->show; + } + + if (state->matching) state->show = true; + + Screen::drawBorder("Embark Assistant"); + + Screen::Pen pen_lr(' ', COLOR_LIGHTRED); + Screen::Pen pen_w(' ', COLOR_WHITE); + + Screen::paintString(pen_lr, width - 28, 20, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str(), false); + Screen::paintString(pen_w, width - 27, 20, ":Embark Assistant Info", false); + Screen::paintString(pen_lr, width - 28, 21, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str(), false); + Screen::paintString(pen_w, width - 27, 21, ":Find Embark ", false); + Screen::paintString(pen_lr, width - 28, 22, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_C).c_str(), false); + Screen::paintString(pen_w, width - 27, 22, ":Cancel/Clear Find", false); + Screen::paintString(pen_lr, width - 28, 23, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_Q).c_str(), false); + Screen::paintString(pen_w, width - 27, 23, ":Quit Embark Assistant", false); + Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles", false); + Screen::paintString(empty_pen, width - 7, 25, to_string(state->match_count), false); + + if (height > 25) { // Mask the vanilla DF find help as it's overridden. + Screen::paintString(pen_w, 50, height - 2, " ", false); + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (state->site_grid[i][k].ch) { + Screen::paintTile(state->site_grid[i][k], i + 1, k + 2); + } + } + } + + for (size_t i = 0; i < state->embark_info.size(); i++) { + embark_assist::screen::paintString(state->embark_info[i].pen, 1, i + 19, state->embark_info[i].text, false); + } + + if (state->show) { + int16_t left_x = x - (width / 2 - 7 - 18 + 1) / 2; + int16_t right_x; + int16_t top_y = y - (height - 8 - 2 + 1) / 2; + int16_t bottom_y; + + if (left_x < 0) { left_x = 0; } + + if (top_y < 0) { top_y = 0; } + + right_x = left_x + width / 2 - 7 - 18; + bottom_y = top_y + height - 8 - 2; + + if (right_x >= world->worldgen.worldgen_parms.dim_x) { + right_x = world->worldgen.worldgen_parms.dim_x - 1; + left_x = right_x - (width / 2 - 7 - 18); + } + + if (bottom_y >= world->worldgen.worldgen_parms.dim_y) { + bottom_y = world->worldgen.worldgen_parms.dim_y - 1; + top_y = bottom_y - (height - 8 - 2); + } + + if (left_x < 0) { left_x = 0; } + + if (top_y < 0) { top_y = 0; } + + + for (uint16_t i = left_x; i <= right_x; i++) { + for (uint16_t k = top_y; k <= bottom_y; k++) { + if (state->world_match_grid[i][k].ch) { + Screen::paintTile(state->world_match_grid[i][k], i - left_x + 18, k - top_y + 2); + } + } + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (state->region_match_grid[i][k].ch) { + Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2); + } + } + } + +/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there. + Screen::Pen pen(' ', COLOR_YELLOW); + // Boundaries of the top level world map + Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant +// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area. +// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area. +// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area. + + uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map. + uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map. + df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x); + df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y); + + Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false); + Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false); + Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false); + */ + } + + if (state->matching) { + embark_assist::overlay::state->match_callback(); + } + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, feed); + IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, render); + } +} + +//==================================================================== + +bool embark_assist::overlay::setup(DFHack::Plugin *plugin_self, + embark_update_callbacks embark_update_callback, + match_callbacks match_callback, + clear_match_callbacks clear_match_callback, + embark_assist::defs::find_callbacks find_callback, + shutdown_callbacks shutdown_callback, + uint16_t max_inorganic) +{ +// color_ostream_proxy out(Core::getInstance().getConsole()); + state = new(states); + + embark_assist::overlay::plugin_self = plugin_self; + embark_assist::overlay::state->embark_update = embark_update_callback; + embark_assist::overlay::state->match_callback = match_callback; + embark_assist::overlay::state->clear_match_callback = clear_match_callback; + embark_assist::overlay::state->find_callback = find_callback; + embark_assist::overlay::state->shutdown_callback = shutdown_callback; + embark_assist::overlay::state->max_inorganic = max_inorganic; + embark_assist::overlay::state->match_active = false; + + state->world_match_grid = new pen_column[world->worldgen.worldgen_parms.dim_x]; + if (!state->world_match_grid) { + return false; // Out of memory + } + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + state->world_match_grid[i] = new Screen::Pen[world->worldgen.worldgen_parms.dim_y]; + if (!state->world_match_grid[i]) { // Out of memory. + return false; + } + } + + clear_match_results(); + + return INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, feed).apply(true) && + INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, render).apply(true); +} + +//==================================================================== + +void embark_assist::overlay::set_sites(embark_assist::defs::site_lists *site_list) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + state->site_grid[i][k] = empty_pen; + } + } + + for (uint16_t i = 0; i < site_list->size(); i++) { + state->site_grid[site_list->at(i).x][site_list->at(i).y].ch = site_list->at(i).type; + } +} + +//==================================================================== + +void embark_assist::overlay::initiate_match() { + embark_assist::overlay::state->matching = true; +} + +//==================================================================== + +void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + state->matching = !done; + state->match_count = count; + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + if (match_results->at(i).at(k).preliminary_match) { + state->world_match_grid[i][k] = yellow_x_pen; + + } else if (match_results->at(i).at(k).contains_match) { + state->world_match_grid[i][k] = green_x_pen; + } + else { + state->world_match_grid[i][k] = empty_pen; + } + } + } +} + +//==================================================================== + +void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) { + state->embark_info.clear(); + + if (site_info->sand) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" }); + } + + if (site_info->clay) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" }); + } + + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) }); + + if (site_info->flat) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Flat" }); + } + + if (site_info->aquifer) { + if (site_info->aquifer_full) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" }); + + } + else { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aquifer" }); + } + } + + if (site_info->waterfall) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" }); + } + + if (site_info->flux) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), "Flux" }); + } + + for (auto const& i : site_info->metals) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), world->raws.inorganics[i]->id }); + } + + for (auto const& i : site_info->economics) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), world->raws.inorganics[i]->id }); + } +} + +//==================================================================== + +void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (mlt_matches[i][k]) { + state->region_match_grid[i][k] = green_x_pen; + + } + else { + state->region_match_grid[i][k] = empty_pen; + } + } + } +} + +//==================================================================== + +void embark_assist::overlay::clear_match_results() { + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + state->world_match_grid[i][k] = empty_pen; + } + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + state->region_match_grid[i][k] = empty_pen; + } + } +} + +//==================================================================== + +void embark_assist::overlay::shutdown() { + if (state && + state->world_match_grid) { + INTERPOSE_HOOK(ViewscreenOverlay, render).remove(); + INTERPOSE_HOOK(ViewscreenOverlay, feed).remove(); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + delete[] state->world_match_grid[i]; + } + + delete[] state->world_match_grid; + } + + if (state) { + state->embark_info.clear(); + delete state; + state = nullptr; + } +} diff --git a/plugins/embark-assistant/overlay.h b/plugins/embark-assistant/overlay.h new file mode 100644 index 000000000..d66d6d7fd --- /dev/null +++ b/plugins/embark-assistant/overlay.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include "PluginManager.h" + +#include "DataDefs.h" +#include "df/viewscreen_choose_start_sitest.h" + +#include "defs.h" + +using df::global::enabler; +using df::global::gps; + +namespace embark_assist { + namespace overlay { + typedef void(*embark_update_callbacks)(); + typedef void(*match_callbacks)(); + typedef void(*clear_match_callbacks)(); + typedef void(*shutdown_callbacks)(); + + bool setup(DFHack::Plugin *plugin_self, + embark_update_callbacks embark_update_callback, + match_callbacks match_callback, + clear_match_callbacks clear_match_callback, + embark_assist::defs::find_callbacks find_callback, + shutdown_callbacks shutdown_callback, + uint16_t max_inorganic); + + void set_sites(embark_assist::defs::site_lists *site_list); + void initiate_match(); + void match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done); + void set_embark(embark_assist::defs::site_infos *site_info); + void set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches); + void clear_match_results(); + void shutdown(); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/screen.cpp b/plugins/embark-assistant/screen.cpp new file mode 100644 index 000000000..34a0d815c --- /dev/null +++ b/plugins/embark-assistant/screen.cpp @@ -0,0 +1,27 @@ +#include "screen.h" + +namespace embark_assist { + namespace screen { + } +} + +bool embark_assist::screen::paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map) { + auto screen_size = DFHack::Screen::getWindowSize(); + + if (y < 1 || y + 1 >= screen_size.y || x < 1) + { + return false; // Won't paint outside of the screen or on the frame + } + + if (x + int32_t(text.length()) - 1 < screen_size.x - 2) { + DFHack::Screen::paintString(pen, x, y, text, map); + } + else if (x < screen_size.x - 2) { + DFHack::Screen::paintString(pen, x, y, text.substr(0, screen_size.x - 2 - x + 1), map); + } + else { + return false; + } + + return true; +} diff --git a/plugins/embark-assistant/screen.h b/plugins/embark-assistant/screen.h new file mode 100644 index 000000000..06b723f24 --- /dev/null +++ b/plugins/embark-assistant/screen.h @@ -0,0 +1,7 @@ +#include "modules/Screen.h" + +namespace embark_assist { + namespace screen { + bool paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map = false); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp new file mode 100644 index 000000000..b057ff290 --- /dev/null +++ b/plugins/embark-assistant/survey.cpp @@ -0,0 +1,1113 @@ +#include + +#include "Core.h" +#include +#include +#include + +#include +#include "modules/Materials.h" + +#include "DataDefs.h" +#include "df/coord2d.h" +#include "df/creature_interaction_effect.h" +#include "df/creature_interaction_effect_display_symbolst.h" +#include "df/creature_interaction_effect_type.h" +#include "df/feature_init.h" +#include "df/feature_init_deep_special_tubest.h" +#include "df/feature_init_magma_poolst.h" +#include "df/feature_init_volcanost.h" +#include "df/feature_type.h" +#include "df/inorganic_flags.h" +#include "df/inorganic_raw.h" +#include "df/interaction.h" +#include "df/interaction_effect.h" +#include "df/interaction_effect_type.h" +#include "df/interaction_effect_animatest.h" +#include "df/interaction_instance.h" +#include "df/interaction_source.h" +#include "df/interaction_source_regionst.h" +#include "df/interaction_source_type.h" +#include "df/interaction_target.h" +#include "df/interaction_target_corpsest.h" +#include "df/interaction_target_materialst.h" +#include "df/material_common.h" +#include "df/reaction.h" +#include "df/region_map_entry.h" +#include "df/syndrome.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_raws.h" +#include "df/world_region.h" +#include "df/world_region_details.h" +#include "df/world_region_feature.h" +#include "df/world_river.h" +#include "df/world_site.h" +#include "df/world_site_type.h" +#include "df/world_underground_region.h" + +#include "biome_type.h" +#include "defs.h" +#include "survey.h" + +using namespace DFHack; +using namespace df::enums; +using namespace Gui; + +using df::global::world; + +namespace embark_assist { + namespace survey { + struct states { + uint16_t clay_reaction = -1; + uint16_t flux_reaction = -1; + uint16_t x; + uint16_t y; + uint8_t local_min_x; + uint8_t local_min_y; + uint8_t local_max_x; + uint8_t local_max_y; + uint16_t max_inorganic; + }; + + static states *state; + + //======================================================================================= + + bool geo_survey(embark_assist::defs::geo_data *geo_summary) { + color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + auto reactions = df::reaction::get_vector(); + bool non_soil_found; + uint16_t size; + + for (uint16_t i = 0; i < reactions.size(); i++) { + if (reactions[i]->code == "MAKE_CLAY_BRICKS") { + state->clay_reaction = i; + } + + if (reactions[i]->code == "PIG_IRON_MAKING") { + state->flux_reaction = i; + } + } + + if (state->clay_reaction == -1) { + out.printerr("The reaction 'MAKE_CLAY_BRICKS' was not found, so clay can't be identified.\n"); + } + + if (state->flux_reaction == -1) { + out.printerr("The reaction 'PIG_IRON_MAKING' was not found, so flux can't be identified.\n"); + } + + for (uint16_t i = 0; i < world_data->geo_biomes.size(); i++) { + geo_summary->at(i).possible_metals.resize(state->max_inorganic); + geo_summary->at(i).possible_economics.resize(state->max_inorganic); + geo_summary->at(i).possible_minerals.resize(state->max_inorganic); + + non_soil_found = true; + df::world_geo_biome *geo = world_data->geo_biomes[i]; + + for (uint16_t k = 0; k < geo->layers.size() && k < 16; k++) { + df::world_geo_layer *layer = geo->layers[k]; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + geo_summary->at(i).soil_size += layer->top_height - layer->bottom_height + 1; + + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + geo_summary->at(i).sand_absent = false; + } + + if (non_soil_found) { + geo_summary->at(i).top_soil_only = false; + } + } + else { + non_soil_found = true; + } + + geo_summary->at(i).possible_minerals[layer->mat_index] = true; + + size = (uint16_t)world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size(); + + for (uint16_t l = 0; l < size; l++) { + geo_summary->at(i).possible_metals.at(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[l]) = true; + } + + size = (uint16_t)world->raws.inorganics[layer->mat_index]->economic_uses.size(); + if (size != 0) { + geo_summary->at(i).possible_economics[layer->mat_index] = true; + + for (uint16_t l = 0; l < size; l++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[l] == state->clay_reaction) { + geo_summary->at(i).clay_absent = false; + } + + if (world->raws.inorganics[layer->mat_index]->economic_uses[l] == state->flux_reaction) { + geo_summary->at(i).flux_absent = false; + } + } + } + + size = (uint16_t)layer->vein_mat.size(); + + for (uint16_t l = 0; l < size; l++) { + auto vein = layer->vein_mat[l]; + geo_summary->at(i).possible_minerals[vein] = true; + + for (uint16_t m = 0; m < world->raws.inorganics[vein]->metal_ore.mat_index.size(); m++) { + geo_summary->at(i).possible_metals.at(world->raws.inorganics[vein]->metal_ore.mat_index[m]) = true; + } + + if (world->raws.inorganics[vein]->economic_uses.size() != 0) { + geo_summary->at(i).possible_economics[vein] = true; + + for (uint16_t m = 0; m < world->raws.inorganics[vein]->economic_uses.size(); m++) { + if (world->raws.inorganics[vein]->economic_uses[m] == state->clay_reaction) { + geo_summary->at(i).clay_absent = false; + } + + if (world->raws.inorganics[vein]->economic_uses[m] == state->flux_reaction) { + geo_summary->at(i).flux_absent = false; + } + } + } + } + + if (layer->bottom_height <= -3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + geo_summary->at(i).aquifer_absent = false; + } + + if (non_soil_found == true) { + geo_summary->at(i).top_soil_aquifer_only = false; + } + } + } + return true; + } + + + //================================================================================= + + void survey_rivers(embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + int16_t x; + int16_t y; + + for (uint16_t i = 0; i < world_data->rivers.size(); i++) { + for (uint16_t k = 0; k < world_data->rivers[i]->path.x.size(); k++) { + x = world_data->rivers[i]->path.x[k]; + y = world_data->rivers[i]->path.y[k]; + + if (world_data->rivers[i]->flow[k] < 5000) { + if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; + } + } + else if (world_data->rivers[i]->flow[k] < 10000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; + } + else if (world_data->rivers[i]->flow[k] < 20000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Major; + } + } + + x = world_data->rivers[i]->end_pos.x; + y = world_data->rivers[i]->end_pos.y; + + // Make the guess the river size for the end is the same as the tile next to the end. Note that DF + // doesn't actually recognize this tile as part of the river in the pre embark river name display. + // We also assume the is_river/is_brook flags are actually set properly for the end tile. + // + if (x >= 0 && y >= 0 && x < world->worldgen.worldgen_parms.dim_x && y < world->worldgen.worldgen_parms.dim_y) { + if (survey_results->at(x).at(y).river_size == embark_assist::defs::river_sizes::None) { + if (world_data->rivers[i]->path.x.size() && + world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 5000) { + if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; + } + } + else if (world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 10000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; + } + else if (world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 20000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Major; + } + } + } + } + } + + //================================================================================= + + void survey_evil_weather(embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + + for (uint16_t i = 0; i < world->interaction_instances.all.size(); i++) { + auto interaction = world->raws.interactions[world->interaction_instances.all[i]->interaction_id]; + uint16_t region_index = world->interaction_instances.all[i]->region_index; + bool thralling = false; + bool reanimating = false; + + if (interaction->sources.size() && + interaction->sources[0]->getType() == df::interaction_source_type::REGION) { + for (uint16_t k = 0; k < interaction->targets.size(); k++) { + if (interaction->targets[k]->getType() == df::interaction_target_type::CORPSE) { + for (uint16_t l = 0; l < interaction->effects.size(); l++) { + if (interaction->effects[l]->getType() == df::interaction_effect_type::ANIMATE) { + reanimating = true; + break; + } + } + } + else if (interaction->targets[k]->getType() == df::interaction_target_type::MATERIAL) { + df::interaction_target_materialst* material = virtual_cast(interaction->targets[k]); + if (material && DFHack::MaterialInfo(material->mat_type, material->mat_index).isInorganic()) { + for (uint16_t l = 0; l < world->raws.inorganics[material->mat_index]->material.syndrome.size(); l++) { + for (uint16_t m = 0; m < world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce.size(); m++) { + if (world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { + // Using this as a proxy. There seems to be a group of 4 effects for thralls: + // display symbol, flash symbol, phys att change and one more. + thralling = true; + } + } + } + } + } + } + } + + for (uint16_t k = 0; k < world_data->regions[region_index]->region_coords.size(); k++) { + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).evil_weather[5] = true; + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).reanimating[5] = reanimating; + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).thralling[5] = thralling; + } + } + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + survey_results->at(i).at(k).evil_weather_possible = false; + survey_results->at(i).at(k).reanimating_possible = false; + survey_results->at(i).at(k).thralling_possible = false; + survey_results->at(i).at(k).evil_weather_full = true; + survey_results->at(i).at(k).reanimating_full = true; + survey_results->at(i).at(k).thralling_full = true; + + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome_index[l] != -1) { + df::coord2d adjusted = apply_offset(i, k, l); + survey_results->at(i).at(k).evil_weather[l] = survey_results->at(adjusted.x).at(adjusted.y).evil_weather[5]; + survey_results->at(i).at(k).reanimating[l] = survey_results->at(adjusted.x).at(adjusted.y).reanimating[5]; + survey_results->at(i).at(k).thralling[l] = survey_results->at(adjusted.x).at(adjusted.y).thralling[5]; + + if (survey_results->at(i).at(k).evil_weather[l]) { + survey_results->at(i).at(k).evil_weather_possible = true; + } + else { + survey_results->at(i).at(k).evil_weather_full = false; + } + + if (survey_results->at(i).at(k).reanimating[l]) { + survey_results->at(i).at(k).reanimating_possible = true; + } + else { + survey_results->at(i).at(k).reanimating_full = false; + } + + if (survey_results->at(i).at(k).thralling[l]) { + survey_results->at(i).at(k).thralling_possible = true; + } + else { + survey_results->at(i).at(k).thralling_full = false; + } + } + } + } + } + } + } +} + +//================================================================================= +// Exported operations +//================================================================================= + +void embark_assist::survey::setup(uint16_t max_inorganic) { + embark_assist::survey::state = new(embark_assist::survey::states); + embark_assist::survey::state->max_inorganic = max_inorganic; +} + +//================================================================================= + +df::coord2d embark_assist::survey::get_last_pos() { + return{ embark_assist::survey::state->x, embark_assist::survey::state->y }; +} + +//================================================================================= + +void embark_assist::survey::initiate(embark_assist::defs::mid_level_tiles *mlt) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } +} + +//================================================================================= + +void embark_assist::survey::clear_results(embark_assist::defs::match_results *match_results) { + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + match_results->at(i).at(k).preliminary_match = false; + match_results->at(i).at(k).contains_match = false; + + for (uint16_t l = 0; l < 16; l++) { + for (uint16_t m = 0; m < 16; m++) { + match_results->at(i).at(k).mlt_match[l][m] = false; + } + } + } + } +} + +//================================================================================= + +void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + + embark_assist::survey::geo_survey(geo_summary); + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + uint16_t geo_index; + uint16_t sav_ev; + uint8_t offset_count = 0; + survey_results->at(i).at(k).surveyed = false; + survey_results->at(i).at(k).aquifer_count = 0; + survey_results->at(i).at(k).clay_count = 0; + survey_results->at(i).at(k).sand_count = 0; + survey_results->at(i).at(k).flux_count = 0; + survey_results->at(i).at(k).min_region_soil = 10; + survey_results->at(i).at(k).max_region_soil = 0; + survey_results->at(i).at(k).waterfall = false; + survey_results->at(i).at(k).savagery_count[0] = 0; + survey_results->at(i).at(k).savagery_count[1] = 0; + survey_results->at(i).at(k).savagery_count[2] = 0; + survey_results->at(i).at(k).evilness_count[0] = 0; + survey_results->at(i).at(k).evilness_count[1] = 0; + survey_results->at(i).at(k).evilness_count[2] = 0; + survey_results->at(i).at(k).metals.resize(state->max_inorganic); + survey_results->at(i).at(k).economics.resize(state->max_inorganic); + survey_results->at(i).at(k).minerals.resize(state->max_inorganic); + // Evil weather and rivers are handled in later operations. Should probably be merged into one. + + for (uint8_t l = 1; l < 10; l++) + { + adjusted = apply_offset(i, k, l); + if (adjusted.x != i || adjusted.y != k || l == 5) { + offset_count++; + + survey_results->at(i).at(k).biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; + survey_results->at(i).at(k).biome[l] = get_biome_type(adjusted.x, adjusted.y, k); + geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index; + + if (!geo_summary->at(geo_index).aquifer_absent) survey_results->at(i).at(k).aquifer_count++; + if (!geo_summary->at(geo_index).clay_absent) survey_results->at(i).at(k).clay_count++; + if (!geo_summary->at(geo_index).sand_absent) survey_results->at(i).at(k).sand_count++; + if (!geo_summary->at(geo_index).flux_absent) survey_results->at(i).at(k).flux_count++; + + if (geo_summary->at(geo_index).soil_size < survey_results->at(i).at(k).min_region_soil) + survey_results->at(i).at(k).min_region_soil = geo_summary->at(geo_index).soil_size; + + if (geo_summary->at(geo_index).soil_size > survey_results->at(i).at(k).max_region_soil) + survey_results->at(i).at(k).max_region_soil = geo_summary->at(geo_index).soil_size; + + sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).savagery_count[sav_ev]++; + + sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).evilness_count[sav_ev]++; + + for (uint16_t m = 0; m < state->max_inorganic; m++) { + if (geo_summary->at(geo_index).possible_metals[m]) survey_results->at(i).at(k).metals[m] = true; + if (geo_summary->at(geo_index).possible_economics[m]) survey_results->at(i).at(k).economics[m] = true; + if (geo_summary->at(geo_index).possible_minerals[m]) survey_results->at(i).at(k).minerals[m] = true; + } + } + else { + survey_results->at(i).at(k).biome_index[l] = -1; + survey_results->at(i).at(k).biome[l] = -1; + } + } + + survey_results->at(i).at(k).biome_count = 0; + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome[l] != -1) survey_results->at(i).at(k).biome_count++; + } + + if (survey_results->at(i).at(k).aquifer_count == offset_count) survey_results->at(i).at(k).aquifer_count = 256; + if (survey_results->at(i).at(k).clay_count == offset_count) survey_results->at(i).at(k).clay_count = 256; + if (survey_results->at(i).at(k).sand_count == offset_count) survey_results->at(i).at(k).sand_count = 256; + if (survey_results->at(i).at(k).flux_count == offset_count) survey_results->at(i).at(k).flux_count = 256; + for (uint8_t l = 0; l < 3; l++) { + if (survey_results->at(i).at(k).savagery_count[l] == offset_count) survey_results->at(i).at(k).savagery_count[l] = 256; + if (survey_results->at(i).at(k).evilness_count[l] == offset_count) survey_results->at(i).at(k).evilness_count[l] = 256; + } + } + } + + embark_assist::survey::survey_rivers(survey_results); + embark_assist::survey::survey_evil_weather(survey_results); +} + +//================================================================================= + +void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + int8_t max_soil_depth; + int8_t offset; + int16_t elevation; + int16_t last_bottom; + int16_t top_z; + int16_t base_z; + int16_t min_z = 0; // Initialized to silence warning about potential usage of uninitialized data. + int16_t bottom_z; + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + df::world_region_details *details = world_data->region_details[0]; + df::region_map_entry *world_tile = &world_data->region_map[x][y]; + std::vector features; + uint8_t soil_erosion; + uint16_t end_check_l; + uint16_t end_check_m; + uint16_t end_check_n; + + for (uint16_t i = 0; i < state->max_inorganic; i++) { + tile->metals[i] = 0; + tile->economics[i] = 0; + tile->minerals[i] = 0; + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } + + for (uint8_t i = 1; i < 10; i++) survey_results->at(x).at(y).biome_index[i] = -1; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + max_soil_depth = -1; + + offset = details->biome[i][k]; + adjusted = apply_offset(x, y, offset); + + if (adjusted.x != x || adjusted.y != y) + { + mlt->at(i).at(k).biome_offset = offset; + } + else + { + mlt->at(i).at(k).biome_offset = 5; + }; + + survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] = + world_data->region_map[adjusted.x][adjusted.y].region_id; + + mlt->at(i).at(k).savagery_level = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (mlt->at(i).at(k).savagery_level == 3) { + mlt->at(i).at(k).savagery_level = 2; + } + mlt->at(i).at(k).evilness_level = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (mlt->at(i).at(k).evilness_level == 3) { + mlt->at(i).at(k).evilness_level = 2; + } + + elevation = details->elevation[i][k]; + + // Special biome adjustments + if (!world_data->region_map[adjusted.x][adjusted.y].flags.is_set(region_map_entry_flags::is_lake)) { + if (world_data->region_map[adjusted.x][adjusted.y].elevation >= 150) { // Mountain + max_soil_depth = 0; + + } + else if (world_data->region_map[adjusted.x][adjusted.y].elevation < 100) { // Ocean + if (elevation == 99) { + elevation = 98; + } + + if ((world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 4 || + world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 5) && + details->unk12e8 < 500) { + max_soil_depth = 0; + } + } + } + + base_z = elevation - 1; + features = details->features[i][k]; + std::map layer_bottom, layer_top; + mlt->at(i).at(k).adamantine_level = -1; + mlt->at(i).at(k).magma_level = -1; + + end_check_l = static_cast(features.size()); + for (size_t l = 0; l < end_check_l; l++) { + auto feature = features[l]; + + if (feature->feature_idx != -1) { + switch (world_data->feature_map[x / 16][y / 16].features->feature_init[x % 16][y % 16][feature->feature_idx]->getType()) + { + case df::feature_type::deep_special_tube: + mlt->at(i).at(k).adamantine_level = world_data->feature_map[x / 16][y / 16].features->feature_init[x % 16][y % 16][feature->feature_idx]->start_depth; + break; + + case df::feature_type::magma_pool: + mlt->at(i).at(k).magma_level = 2 - world_data->feature_map[x / 16][y / 16].features->feature_init[x % 16][y % 16][feature->feature_idx]->start_depth; + break; + + case df::feature_type::volcano: + mlt->at(i).at(k).magma_level = 3; + break; + + default: + break; + } + } + else if (feature->layer != -1 && + feature->min_z != -30000) { + auto layer = world_data->underground_regions[feature->layer]; + + layer_bottom[layer->layer_depth] = feature->min_z; + layer_top[layer->layer_depth] = feature->max_z; + base_z = std::min((int)base_z, (int)feature->min_z); + + if (layer->type == df::world_underground_region::MagmaSea) { + min_z = feature->min_z; // The features are individual per region tile + } + } + } + + // Compute shifts for layers in the stack. + + if (max_soil_depth == -1) { // Not set to zero by the biome + max_soil_depth = std::max((154 - elevation) / 5, 1); + } + + soil_erosion = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - + std::min((int)geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size, (int)max_soil_depth); + int16_t layer_shift[16]; + int16_t cur_shift = elevation + soil_erosion - 1; + + mlt->at(i).at(k).aquifer = false; + mlt->at(i).at(k).clay = false; + mlt->at(i).at(k).sand = false; + mlt->at(i).at(k).flux = false; + if (max_soil_depth == 0) { + mlt->at(i).at(k).soil_depth = 0; + } + else { + mlt->at(i).at(k).soil_depth = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - soil_erosion; + } + mlt->at(i).at(k).offset = offset; + mlt->at(i).at(k).elevation = details->elevation[i][k]; + mlt->at(i).at(k).river_present = false; + mlt->at(i).at(k).river_elevation = 100; + + if (details->rivers_vertical.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_vertical.elevation[i][k]; + } + else if (details->rivers_horizontal.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_horizontal.elevation[i][k]; + } + + if (tile->min_region_soil > mlt->at(i).at(k).soil_depth) { + tile->min_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (tile->max_region_soil < mlt->at(i).at(k).soil_depth) { + tile->max_region_soil = mlt->at(i).at(k).soil_depth; + } + + end_check_l = static_cast(world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers.size()); + if (end_check_l > 16) end_check_l = 16; + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + layer_shift[l] = cur_shift; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + int16_t size = layer->top_height - layer->bottom_height - 1; + // Comment copied from prospector.cpp(like the logic)... + // This is to replicate the behavior of a probable bug in the + // map generation code : if a layer is partially eroded, the + // removed levels are in fact transferred to the layer below, + // because unlike the case of removing the whole layer, the code + // does not execute a loop to shift the lower part of the stack up. + if (size > soil_erosion) { + cur_shift = cur_shift - soil_erosion; + } + + soil_erosion -= std::min((int)soil_erosion, (int)size); + } + } + + last_bottom = elevation; + // Don't have to set up the end_check as we can reuse the one above. + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + top_z = last_bottom - 1; + bottom_z = std::max((int)layer->bottom_height + layer_shift[l], (int)min_z); + + if (l == 15) { + bottom_z = min_z; // stretch layer if needed + } + + if (top_z >= bottom_z) { + mlt->at(i).at(k).minerals[layer->mat_index] = true; + + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size()); + + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[m]] = true; + } + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + mlt->at(i).at(k).sand = true; + } + } + + if (world->raws.inorganics[layer->mat_index]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->mat_index] = true; + + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->economic_uses.size()); + for (uint16_t m = 0; m < end_check_m; m++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } + + else if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + + end_check_m = static_cast(layer->vein_mat.size()); + + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).minerals[layer->vein_mat[m]] = true; + + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index.size()); + + for (uint16_t n = 0; n < end_check_n; n++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index[n]] = true; + } + + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->vein_mat[m]] = true; + + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size()); + for (uint16_t n = 0; n < end_check_n; n++) { + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } + + else if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + } + + if (bottom_z <= elevation - 3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + mlt->at(i).at(k).aquifer = true; + } + } + } + } + } + + survey_results->at(x).at(y).aquifer_count = 0; + survey_results->at(x).at(y).clay_count = 0; + survey_results->at(x).at(y).sand_count = 0; + survey_results->at(x).at(y).flux_count = 0; + survey_results->at(x).at(y).min_region_soil = 10; + survey_results->at(x).at(y).max_region_soil = 0; + survey_results->at(x).at(y).savagery_count[0] = 0; + survey_results->at(x).at(y).savagery_count[1] = 0; + survey_results->at(x).at(y).savagery_count[2] = 0; + survey_results->at(x).at(y).evilness_count[0] = 0; + survey_results->at(x).at(y).evilness_count[1] = 0; + survey_results->at(x).at(y).evilness_count[2] = 0; + + bool river_elevation_found = false; + int16_t river_elevation = 0; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; } + if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; } + if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; } + if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; } + + if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { + survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).soil_depth > survey_results->at(x).at(y).max_region_soil) { + survey_results->at(x).at(y).max_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).river_present) { + if (river_elevation_found) { + if (mlt->at(i).at(k).river_elevation != river_elevation) + { + survey_results->at(x).at(y).waterfall = true; + } + } + else { + river_elevation_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; + } + } + + // River size surveyed separately + // biome_index handled above + // biome handled below + // evil weather handled separately + // reanimating handled separately + // thralling handled separately + + survey_results->at(x).at(y).savagery_count[mlt->at(i).at(k).savagery_level]++; + survey_results->at(x).at(y).evilness_count[mlt->at(i).at(k).evilness_level]++; + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (mlt->at(i).at(k).metals[l]) { survey_results->at(x).at(y).metals[l] = true; } + if (mlt->at(i).at(k).economics[l]) { survey_results->at(x).at(y).economics[l] = true; } + if (mlt->at(i).at(k).minerals[l]) { survey_results->at(x).at(y).minerals[l] = true; } + } + } + } + + for (uint8_t i = 1; i < 10; i++) { + if (survey_results->at(x).at(y).biome_index[i] == -1) { + survey_results->at(x).at(y).biome[i] = -1; + } + } + + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + biomes[i] = false; + } + + for (uint8_t i = 1; i < 10; i++) + { + if (survey_results->at(x).at(y).biome[i] != -1) { + biomes[survey_results->at(x).at(y).biome[i]] = true; + } + } + int count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) count++; + } + + tile->biome_count = count; + tile->surveyed = true; +} +//================================================================================= + +df::coord2d embark_assist::survey::apply_offset(uint16_t x, uint16_t y, int8_t offset) { + df::coord2d result; + result.x = x; + result.y = y; + + switch (offset) { + case 1: + result.x--; + result.y++; + break; + + case 2: + result.y++; + break; + + case 3: + result.x++; + result.y++; + break; + + case 4: + result.x--; + break; + + case 5: + break; // Center. No change + + case 6: + result.x++; + break; + + case 7: + result.x--; + result.y--; + break; + + case 8: + result.y--; + break; + + case 9: + result.x++; + result.y--; + break; + + default: + // Bug. Just act as if it's the center... + break; + } + + if (result.x < 0) { + result.x = 0; + } + else if (result.x >= world->worldgen.worldgen_parms.dim_x) { + result.x = world->worldgen.worldgen_parms.dim_x - 1; + } + + if (result.y < 0) { + result.y = 0; + } + else if (result.y >= world->worldgen.worldgen_parms.dim_y) { + result.y = world->worldgen.worldgen_parms.dim_y - 1; + } + + return result; +} + +//================================================================================= + +void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists *site_list) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + df::world_data *world_data = world->world_data; + int8_t index = 0; + + site_list->clear(); + + for (uint32_t i = 0; i < world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites.size(); i++) { + auto site = world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites[i]; + switch (site->type) { + case df::world_site_type::PlayerFortress: + case df::world_site_type::DarkFortress: + case df::world_site_type::MountainHalls: + case df::world_site_type::ForestRetreat: + case df::world_site_type::Town: + case df::world_site_type::Fortress: + break; // Already visible + + case df::world_site_type::Cave: + if (!world->worldgen.worldgen_parms.all_caves_visible) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'c' }); // Cave + } + break; + + case df::world_site_type::Monument: + if (site->subtype_info->lair_type != -1 || + site->subtype_info->is_monument == 0) { // Not Tomb, which is visible already + } + else if (site->subtype_info->lair_type == -1) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'V' }); // Vault + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'M' }); // Any other Monument type. Pyramid? + } + break; + + case df::world_site_type::ImportantLocation: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'i' }); // Don't really know what that is... + break; + + case df::world_site_type::LairShrine: + if (site->subtype_info->lair_type == 0 || + site->subtype_info->lair_type == 1 || + site->subtype_info->lair_type == 4) { // Only Rocs seen. Mountain lair? + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'l' }); // Lair + } + else if (site->subtype_info->lair_type == 2) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'L' }); // Labyrinth + } + else if (site->subtype_info->lair_type == 3) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'S' }); // Shrine + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '?' }); // Can these exist? + } + break; + + case df::world_site_type::Camp: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'C' }); // Camp + break; + + default: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '!' }); // Not even in the enum... + break; + } + } +} + +//================================================================================= + +void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::site_infos *site_info, + bool use_cache) { + + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t elevation = 0; + uint16_t x = screen->location.region_pos.x; + uint16_t y = screen->location.region_pos.y; + bool river_found = false; + int16_t river_elevation = 0; + std::vector metals(state->max_inorganic); + std::vector economics(state->max_inorganic); + std::vector minerals(state->max_inorganic); + + if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...). + state->local_min_x = screen->location.embark_pos_min.x; + state->local_min_y = screen->location.embark_pos_min.y; + state->local_max_x = screen->location.embark_pos_max.x; + state->local_max_y = screen->location.embark_pos_max.y; + } + + state->x = x; + state->y = y; + + site_info->aquifer = false; + site_info->aquifer_full = true; + site_info->min_soil = 10; + site_info->max_soil = 0; + site_info->flat = true; + site_info->waterfall = false; + site_info->clay = false; + site_info->sand = false; + site_info->flux = false; + site_info->metals.clear(); + site_info->economics.clear(); + site_info->metals.clear(); + + for (uint8_t i = state->local_min_x; i <= state->local_max_x; i++) { + for (uint8_t k = state->local_min_y; k <= state->local_max_y; k++) { + if (mlt->at(i).at(k).aquifer) { + site_info->aquifer = true; + } + else { + site_info->aquifer_full = false; + } + + if (mlt->at(i).at(k).soil_depth < site_info->min_soil) { + site_info->min_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).soil_depth > site_info->max_soil) { + site_info->max_soil = mlt->at(i).at(k).soil_depth; + } + + if (i == state->local_min_x && k == state->local_min_y) { + elevation = mlt->at(i).at(k).elevation; + + } + else if (elevation != mlt->at(i).at(k).elevation) { + site_info->flat = false; + } + + if (mlt->at(i).at(k).river_present) { + if (river_found) { + if (river_elevation != mlt->at(i).at(k).river_elevation) { + site_info->waterfall = true; + } + } + else { + river_elevation = mlt->at(i).at(k).river_elevation; + river_found = true; + } + } + + if (mlt->at(i).at(k).clay) { + site_info->clay = true; + } + + if (mlt->at(i).at(k).sand) { + site_info->sand = true; + } + + if (mlt->at(i).at(k).flux) { + site_info->flux = true; + } + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + metals[l] = metals[l] || mlt->at(i).at(k).metals[l]; + economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; + minerals[l] = minerals[l] || mlt->at(i).at(k).minerals[l]; + } + } + } + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (metals[l]) { + site_info->metals.push_back(l); + } + + if (economics[l]) { + site_info->economics.push_back(l); + } + + if (minerals[l]) { + site_info->minerals.push_back(l); + } + } +} + +//================================================================================= + +void embark_assist::survey::shutdown() { + delete state; +} + diff --git a/plugins/embark-assistant/survey.h b/plugins/embark-assistant/survey.h new file mode 100644 index 000000000..03d2d9a03 --- /dev/null +++ b/plugins/embark-assistant/survey.h @@ -0,0 +1,38 @@ +#pragma once +#include + +#include "DataDefs.h" +#include "df/coord2d.h" + +#include "defs.h" + +using namespace DFHack; + +namespace embark_assist { + namespace survey { + void setup(uint16_t max_inorganic); + + df::coord2d get_last_pos(); + + void initiate(embark_assist::defs::mid_level_tiles *mlt); + + void clear_results(embark_assist::defs::match_results *match_results); + + void high_level_world_survey(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results); + + void survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt); + + df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset); + + void survey_region_sites(embark_assist::defs::site_lists *site_list); + + void survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::site_infos *site_info, + bool use_cache); + + void shutdown(); + } +} \ No newline at end of file diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index d2bdf4909..34613cafb 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -128,7 +128,7 @@ public: if (input->count(df::interface_key::SETUP_EMBARK)) { cancel = true; - screen->in_embark_normal = 1; + screen->in_embark_only_warning = 1; } }; }; @@ -178,7 +178,14 @@ public: auto dim = Screen::getWindowSize(); int x = dim.x - 28, y = 13; - if (screen->page == 0) + if (screen->page == start_sitest::T_page::Biome && ( + int(screen->in_embark_aquifer) + + int(screen->in_embark_salt) + + int(screen->in_embark_large) + + int(screen->in_embark_narrow) + + int(screen->in_embark_only_warning) + + int(screen->in_embark_civ_dying) + ) < 2) { OutputString(COLOR_YELLOW, x, y, indicator); } @@ -261,6 +268,8 @@ public: case df::interface_key::CURSOR_DOWNRIGHT_FAST: is_motion = true; break; + default: + break; } if (is_motion && !moved_position) { @@ -277,7 +286,7 @@ protected: // Used for event handling int prev_x; int prev_y; - bool prev_lbut; + int8_t prev_lbut; // Used for controls bool base_max_x; bool base_max_y; @@ -297,7 +306,7 @@ protected: return in_local_move || in_local_edge_resize_x || in_local_edge_resize_y || in_local_corner_resize; } - void lbut_press(start_sitest* screen, bool pressed, int x, int y) + void lbut_press(start_sitest* screen, int8_t pressed, int x, int y) { GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); in_local_move = in_local_edge_resize_x = in_local_edge_resize_y = @@ -453,7 +462,7 @@ public: :EmbarkTool(), prev_x(0), prev_y(0), - prev_lbut(false), + prev_lbut(0), base_max_x(false), base_max_y(false), in_local_move(false), @@ -674,7 +683,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest if (parts.size()) { std::string label = join_strings(", ", parts); - if (label.size() > dim.x - x - 1) + if (int16_t(label.size()) > dim.x - x - 1) { label.resize(dim.x - x - 1 - 3); label.append("..."); diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 57bd0ed7f..5d8dfdb59 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -78,17 +78,6 @@ struct ReactionInfo { static std::map reactions; static std::map products; -static ReactionInfo *find_reaction(const std::string &name) -{ - auto it = reactions.find(name); - return (it != reactions.end()) ? &it->second : NULL; -} - -static bool is_lua_hook(const std::string &name) -{ - return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0; -} - /* * Hooks */ @@ -158,12 +147,12 @@ void ev_mng_jobCompleted(color_ostream& out, void* job) } void ev_mng_unitDeath(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=(int32_t)(intptr_t)ptr; onUnitDeath(out,myId); } void ev_mng_itemCreate(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=(int32_t)(intptr_t)ptr; onItemCreated(out,myId); } void ev_mng_construction(color_ostream& out, void* ptr) @@ -178,13 +167,13 @@ void ev_mng_syndrome(color_ostream& out, void* ptr) } void ev_mng_invasion(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); + int32_t myId=(int32_t)(intptr_t)ptr; onInvasion(out,myId); } static void ev_mng_building(color_ostream& out, void* ptr) { - int32_t myId=int32_t(ptr); - onBuildingCreatedDestroyed(out,myId); + int32_t id=(int32_t)(intptr_t)ptr; + onBuildingCreatedDestroyed(out, id); } static void ev_mng_inventory(color_ostream& out, void* ptr) { @@ -204,7 +193,7 @@ static void ev_mng_inventory(color_ostream& out, void* ptr) onInventoryChange(out,unitId,itemId,item_old,item_new); } static void ev_mng_report(color_ostream& out, void* ptr) { - onReport(out,(int32_t)ptr); + onReport(out,(int32_t)(intptr_t)ptr); } static void ev_mng_unitAttack(color_ostream& out, void* ptr) { EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; @@ -289,7 +278,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(furnace_hook, fillSidebarMenu); struct product_hook : item_product { typedef item_product interpose_base; - + DEFINE_VMETHOD_INTERPOSE( void, produce, (df::unit *unit, @@ -298,12 +287,12 @@ struct product_hook : item_product { std::vector *in_reag, std::vector *in_items, int32_t quantity, df::job_skill skill, - df::historical_entity *entity, df::world_site *site) + df::historical_entity *entity, int32_t unk, df::world_site *site, void* unk2) ) { color_ostream_proxy out(Core::getInstance().getConsole()); auto product = products[this]; if ( !product ) { - INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, site); + INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, unk, site, unk2); return; } df::reaction* this_reaction=product->react; @@ -314,7 +303,8 @@ struct product_hook : item_product { return; size_t out_item_count = out_items->size(); - INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, site); + + INTERPOSE_NEXT(produce)(unit, out_products, out_items, in_reag, in_items, quantity, skill, entity, unk, site, unk2); if ( out_items->size() == out_item_count ) return; //if it produced something, call the scripts @@ -396,7 +386,7 @@ static bool find_reactions(color_ostream &out) { reactions.clear(); - auto &rlist = world->raws.reactions; + auto &rlist = df::reaction::get_vector(); for (size_t i = 0; i < rlist.size(); i++) { diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index e0b2882b6..09d831e26 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -1,16 +1,17 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" #include "modules/Units.h" #include "modules/Maps.h" -#include "DataDefs.h" -#include "df/world.h" +#include "df/map_block.h" #include "df/unit.h" #include "df/unit_action.h" -#include "df/map_block.h" +#include "df/unit_relationship_type.h" #include "df/units_other_id.h" +#include "df/world.h" using std::string; using std::vector; @@ -56,11 +57,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_teledwarf) do { // skip dwarves that are dragging creatures or being dragged - if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) + if ((unit->relationship_ids[df::unit_relationship_type::Draggee] != -1) || + (unit->relationship_ids[df::unit_relationship_type::Dragger] != -1)) break; // skip dwarves that are following other units - if (unit->relations.following != 0) + if (unit->following != 0) break; // skip unconscious units @@ -105,7 +107,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (size_t j = 0; j < world->units.other[units_other_id::ANY_RIDER].size(); j++) { df::unit *rider = world->units.other[units_other_id::ANY_RIDER][j]; - if (rider->relations.rider_mount_id == unit->id) + if (rider->relationship_ids[df::unit_relationship_type::RiderMount] == unit->id) rider->pos = unit->pos; } } @@ -118,6 +120,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::unit_action *action = unit->actions[i]; switch (action->type) { + case unit_action_type::None: + break; case unit_action_type::Move: action->data.move.timer = 1; break; @@ -169,6 +173,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) case unit_action_type::SuckBlood: action->data.suckblood.timer = 1; break; + case unit_action_type::Jump: + case unit_action_type::ReleaseTerrain: + case unit_action_type::Parry: + case unit_action_type::Block: + case unit_action_type::HoldItem: + case unit_action_type::ReleaseItem: + break; } } } diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index 0f9a7bb6f..ddb5ef877 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -215,7 +215,7 @@ command_result filltraffic(color_ostream &out, std::vector & params { flood.push(DFCoord(xy.x - 1, xy.y, xy.z)); } - if (xy.x < tx_max - 1) + if (xy.x < int32_t(tx_max) - 1) { flood.push(DFCoord(xy.x + 1, xy.y, xy.z)); } @@ -223,7 +223,7 @@ command_result filltraffic(color_ostream &out, std::vector & params { flood.push(DFCoord(xy.x, xy.y - 1, xy.z)); } - if (xy.y < ty_max - 1) + if (xy.y < int32_t(ty_max) - 1) { flood.push(DFCoord(xy.x, xy.y + 1, xy.z)); } @@ -234,7 +234,7 @@ command_result filltraffic(color_ostream &out, std::vector & params { flood.push(DFCoord(xy.x, xy.y, xy.z - 1)); } - if (xy.z < z_max && HighPassable(tt)) + if (xy.z < int32_t(z_max) && HighPassable(tt)) { flood.push(DFCoord(xy.x, xy.y, xy.z + 1)); } @@ -337,11 +337,11 @@ command_result setAllMatching(color_ostream &out, checkTile checkProc, out.print("Setting traffic...\n"); //Loop through every single tile - for(uint32_t x = minCoord.x; x <= maxCoord.x; x++) + for(int32_t x = minCoord.x; x <= maxCoord.x; x++) { - for(uint32_t y = minCoord.y; y <= maxCoord.y; y++) + for(int32_t y = minCoord.y; y <= maxCoord.y; y++) { - for(uint32_t z = minCoord.z; z <= maxCoord.z; z++) + for(int32_t z = minCoord.z; z <= maxCoord.z; z++) { DFCoord tile = DFCoord(x, y, z); checkProc(tile, MCache); diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 8b062290b..0f63b4d93 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -57,8 +57,6 @@ using df::global::ui; using df::global::world; using df::global::gamemode; using df::global::ui_build_selector; -using df::global::ui_menu_width; -using df::global::ui_area_map_width; using namespace DFHack::Gui; using Screen::Pen; diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index 8b7be0c0f..b3cac47c5 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -21,26 +21,13 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(world); -static int run_interval = 1200; // daily +static unsigned run_interval = 1200; // daily inline float getClock() { return (float)clock() / (float)CLOCKS_PER_SEC; } -static std::string get_unit_description(df::unit *unit) -{ - if (!unit) - return ""; - std::string desc; - auto name = Units::getVisibleName(unit); - if (name->has_name) - desc = Translation::TranslateName(name, false); - desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too - - return desc; -} - struct uo_buf { uint32_t dim_x, dim_y, dim_z; size_t size; diff --git a/plugins/fixveins.cpp b/plugins/fixveins.cpp index 5f612c9ad..88eeb3b61 100644 --- a/plugins/fixveins.cpp +++ b/plugins/fixveins.cpp @@ -14,6 +14,11 @@ #include "modules/Maps.h" #include "TileTypes.h" +#include "df/block_square_event.h" +#include "df/block_square_event_mineralst.h" +#include "df/map_block.h" +#include "df/world.h" + using std::vector; using std::string; using namespace DFHack; diff --git a/plugins/follow.cpp b/plugins/follow.cpp index 7b8ad3906..21a297bd1 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -1,17 +1,19 @@ // Make the camera follow the selected unit +#include "Console.h" #include "Core.h" -#include -#include -#include - -#include "DFHack.h" #include "DataDefs.h" +#include "DFHack.h" +#include "Export.h" +#include "PluginManager.h" + #include "modules/Gui.h" -#include "modules/World.h" #include "modules/Maps.h" -#include -#include +#include "modules/World.h" + +#include "df/creature_raw.h" +#include "df/unit.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; @@ -114,8 +116,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) y_max *= 16; //Calculate a new screen position centered on the selected unit - x = unitPos.x + w/2 >= x_max ? x_max-w : (unitPos.x >= w/2 ? unitPos.x - w/2 : 0); - y = unitPos.y + h/2 >= y_max ? y_max-h : (unitPos.y >= h/2 ? unitPos.y - h/2 : 0); + x = unitPos.x + w/2 >= int32_t(x_max) ? x_max-w : (unitPos.x >= w/2 ? unitPos.x - w/2 : 0); + y = unitPos.y + h/2 >= int32_t(y_max) ? y_max-h : (unitPos.y >= h/2 ? unitPos.y - h/2 : 0); z = unitPos.z; //Set the new screen position! diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index da6f1aff4..19c1202d6 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -274,7 +274,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u // Step 2: Try to find a bodypart which is eligible to receive equipment AND which is appropriate for the specified item df::body_part_raw * confirmedBodyPart = NULL; - int bpIndex; + size_t bpIndex; for(bpIndex = 0; bpIndex < unit->body.body_plan->body_parts.size(); bpIndex++) { df::body_part_raw * currPart = unit->body.body_plan->body_parts[bpIndex]; @@ -358,10 +358,9 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u { confirmedBodyPart = currPart; // Assume that the bodypart is valid; we'll invalidate it if we detect too many collisions while looping int collisions = 0; - for (int inventoryID=0; inventoryID < unit->inventory.size(); inventoryID++) + for (df::unit_inventory_item * currInvItem : unit->inventory) { - df::unit_inventory_item * currInvItem = unit->inventory[inventoryID]; - if (currInvItem->body_part_id == bpIndex) + if (currInvItem->body_part_id == int32_t(bpIndex)) { // Collision detected; have we reached the limit? if (++collisions >= multiEquipLimit) @@ -415,6 +414,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) // The "here" option is hardcoded to true, because the plugin currently doesn't support // equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit) bool here = true; + (void)here; // For balance (anti-cheating) reasons, the plugin applies a limit on the number of // item that can be equipped on any bodypart. This limit defaults to 1 but can be // overridden with cmdline switches. @@ -512,7 +512,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) pos_cursor = DFCoord(cx,cy,cz); // Iterate over all units, process the first one whose pos == pos_cursor - df::unit * targetUnit; + df::unit * targetUnit = nullptr; size_t numUnits = world->units.all.size(); for(size_t i=0; i< numUnits; i++) { @@ -522,11 +522,13 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (pos_unit == pos_cursor) break; - if (i + 1 == numUnits) - { - out.printerr("No unit found at cursor!\n"); - return CR_FAILURE; - } + targetUnit = nullptr; + } + + if (!targetUnit) + { + out.printerr("No unit found at cursor!\n"); + return CR_FAILURE; } // Assert: unit found. @@ -534,7 +536,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) // If a specific bodypart was included in the command arguments, then search for it now df::body_part_raw * targetBodyPart = NULL; if (targetBodyPartCode.size() > 0) { - for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) + for (size_t bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) { // Tentatively assume that the part is a match targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex); diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index a1934c4ab..8609cb13c 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -165,7 +165,7 @@ command_result fortplan(color_ostream &out, vector & params) { con.print("Loading file '%s'...\n",filename.c_str()); try { layout = tokenizeFile(filename); - } catch (int e) { + } catch (int) { con.print("Could not open the file.\n"); return CR_FAILURE; } diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp new file mode 100644 index 000000000..e70324dd2 --- /dev/null +++ b/plugins/generated-creature-renamer.cpp @@ -0,0 +1,274 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "df/world.h" +#include "df/world_raws.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" +#include "modules/World.h" +#include "MemAccess.h" + +//#include "df/world.h" + +using namespace DFHack; + +DFHACK_PLUGIN("generated-creature-renamer"); +REQUIRE_GLOBAL(world); + +#define RENAMER_VERSION 3 + +command_result list_creatures(color_ostream &out, std::vector & parameters); +command_result save_generated_raw(color_ostream &out, std::vector & parameters); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "list-generated", + "Prints a list of generated creature tokens.", + list_creatures, + false, //allow non-interactive use + "Use \"list-generated detailed\" to show descriptions." + )); + commands.push_back(PluginCommand( + "save-generated-raws", + "Saves a graphics raw file to use with the renamed generated creatures.", + save_generated_raw, + false, //allow non-interactive use + "Creates graphics_procedural_creatures.txt, with a full set of creature graphics definitions for all possible generated beasts. Modify the resulting file to suit your needs." + )); return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + return CR_OK; +} + +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + is_enabled = enable; + return CR_OK; +} + +std::vector descriptors = { + "blob", "quadruped", "humanoid", "silverfish", "mayfly", "dragonfly", "damselfly", "stonefly", + "earwig", "grasshopper", "cricket", "stick insect", "cockroach", "termite", "mantis", "louse", + "thrips", "aphid", "cicada", "assassin bug", "wasp", "hornet", "tiger beetle", "ladybug", + "weevil", "darkling beetle", "click beetle", "firefly", "scarab beetle", "stag beetle", "dung beetle", "rhinoceros beetle", + "rove beetle", "snakefly", "lacewing", "antlion larva", "mosquito", "flea", "scorpionfly", "caddisfly", + "butterfly", "moth", "caterpillar", "maggot", "spider", "tarantula", "scorpion", "tick", + "mite", "shrimp", "lobster", "crab", "nematode", "snail", "slug", "earthworm", + "leech", "bristleworm", "ribbon worm", "flat worm", "toad", "frog", "salamander", "newt", + "alligator", "crocodile", "lizard", "chameleon", "iguana", "gecko", "skink", "gila monster", + "monitor", "serpent", "viper", "rattlesnake", "cobra", "python", "anaconda", "turtle", + "tortoise", "pterosaur", "dimetrodon", "sauropod", "theropod", "iguanodont", "hadrosaurid", "stegosaurid", + "ceratopsid", "ankylosaurid", "duck", "goose", "swan", "turkey", "grouse", "chicken", + "quail", "pheasant", "gull", "loon", "grebe", "albatross", "petrel", "penguin", + "pelican", "stork", "vulture", "flamingo", "falcon", "kestrel", "condor", "osprey", + "buzzard", "eagle", "harrier", "kite", "crane", "dove", "pigeon", "parrot", + "cockatoo", "cuckoo", "nightjar", "swift", "hummingbird", "kingfisher", "hornbill", "quetzal", + "toucan", "woodpecker", "lyrebird", "thornbill", "honeyeater", "oriole", "fantail", "shrike", + "crow", "raven", "magpie", "kinglet", "lark", "swallow", "martin", "bushtit", + "warbler", "thrush", "oxpecker", "starling", "mockingbird", "wren", "nuthatch", "sparrow", + "tanager", "cardinal", "bunting", "finch", "titmouse", "chickadee", "waxwing", "flycatcher", + "opossum", "koala", "wombat", "kangaroo", "sloth", "anteater", "armadillo", "squirrel", + "marmot", "beaver", "gopher", "mouse", "porcupine", "chinchilla", "cavy", "capybara", + "rabbit", "hare", "lemur", "loris", "monkey", "hedgehog", "shrew", "mole", + "fruit bat", "wolf", "coyote", "jackal", "raccoon", "coati", "weasel", "otter", + "badger", "skunk", "bear", "panda", "panther", "mongoose", "hyena", "civet", + "walrus", "pangolin", "elephant", "mammoth", "horse", "zebra", "tapir", "rhinoceros", + "warthog", "hippopotamus", "camel", "llama", "giraffe", "deer", "moose", "antelope", + "sheep", "goat", "bison", "buffalo", "bull", "ape", "ant", "bat", + "owl", "pig", "bee", "fly", "hawk", "jay", "rat", "fox", + "cat", "ass", "elk" +}; + +std::vector prefixes = { + "FORGOTTEN_BEAST_", + "TITAN_", + "DEMON_", + "NIGHT_CREATURE_", + "HF" +}; + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + if (!is_enabled) + return CR_OK; + + if (event != DFHack::SC_WORLD_LOADED) + return CR_OK; + + CoreSuspender suspend; + + std::vector descriptorCount = std::vector(descriptors.size()); + + auto version = World::GetPersistentData("AlreadyRenamedCreatures"); + if (version.isValid() && version.ival(1) >= RENAMER_VERSION) + { + return CR_OK; + } + + int creatureCount = 0; + + for (size_t i = 0; i < world->raws.creatures.all.size(); i++) + { + auto creatureRaw = world->raws.creatures.all[i]; + if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) + continue; + size_t minPos = std::string::npos; + size_t foundIndex = -1; + size_t prefixIndex = -1; + + for (size_t j = 0; j < prefixes.size(); j++) + { + if (creatureRaw->creature_id.compare(0, prefixes[j].length(), prefixes[j]) == 0) + { + prefixIndex = j; + } + } + + if (prefixIndex < 0) + continue; //unrecognized generaed type. + + for (size_t j = 0; j < descriptors.size(); j++) + { + size_t pos = creatureRaw->caste[0]->description.find(" " + descriptors[j]); + if (pos < minPos) + { + minPos = pos; + foundIndex = j; + } + } + + if (foundIndex < 0) + continue; //can't find a match. + + auto descriptor = descriptors[foundIndex]; + + for (size_t j = 0; j < descriptor.size(); j++) + { + if (descriptor[j] == ' ') + descriptor[j] = '_'; + else + descriptor[j] = toupper(descriptor[j]); + } + + auto prefix = prefixes[prefixIndex]; + if (prefix[prefix.length() - 1] != '_') + prefix.append("_"); + + creatureRaw->creature_id = prefixes[prefixIndex] + descriptor; + + if (descriptorCount[foundIndex] > 0) + { + creatureRaw->creature_id.append("_" + std::to_string(descriptorCount[foundIndex])); + } + descriptorCount[foundIndex]++; + creatureCount++; + } + + version = World::AddPersistentData("AlreadyRenamedCreatures"); + version.ival(1) = RENAMER_VERSION; + + out << "Renamed " << creatureCount << " generated creatures to have sensible names." << endl; + + + return CR_OK; +} + +command_result list_creatures(color_ostream &out, std::vector & parameters) +{ + bool detailed = false; + if (!parameters.empty()) + { + if (parameters.size() > 1) + return CR_WRONG_USAGE; + if(parameters[0].compare("detailed") != 0) + return CR_WRONG_USAGE; + detailed = true; + } + + CoreSuspender suspend; + for (size_t i = 0; i < world->raws.creatures.all.size(); i++) + { + auto creatureRaw = world->raws.creatures.all[i]; + if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) + continue; + out.print(creatureRaw->creature_id.c_str()); + if (detailed) + { + out.print("\t"); + out.print(creatureRaw->caste[0]->description.c_str()); + } + out.print("\n"); + } + return CR_OK; +} + +command_result save_generated_raw(color_ostream &out, std::vector & parameters) +{ +#ifdef LINUX_BUILD + std::string pathSep = "/"; +#else + std::string pathSep = "\\"; +#endif + int pageWidth = 16; + int pageHeight = (descriptors.size() / pageWidth) + ((descriptors.size() % pageWidth > 0) ? 1 : 0); + int tileWidth = 24; + int tileHeight = 24; + std::string fileName = "graphics_procedural_creatures"; + std::string pageName = "PROCEDURAL_FRIENDLY"; + size_t repeats = 128; + + std::ofstream outputFile(fileName + ".txt", std::ios::out | std::ios::trunc); + + outputFile << fileName << endl << endl; + + outputFile << "[OBJECT:GRAPHICS]" << endl << endl; + + outputFile << "[TILE_PAGE:" << pageName << "]" << endl; + outputFile << " [FILE:procedural_friendly.png]" << endl; + outputFile << " [TILE_DIM:" << tileWidth << ":" << tileHeight << "]" << endl; + outputFile << " [PAGE_DIM:" << pageWidth << ":" << pageHeight << "]" << endl << endl; + + for (size_t descIndex = 0; descIndex < descriptors.size(); descIndex++) + { + for (size_t prefIndex = 0; prefIndex < prefixes.size(); prefIndex++) + { + for (size_t rep = 0; rep < repeats; rep++) + { + auto descriptor = descriptors[descIndex]; + + for (size_t j = 0; j < descriptor.size(); j++) + { + if (descriptor[j] == ' ') + descriptor[j] = '_'; + else + descriptor[j] = toupper(descriptor[j]); + } + + auto prefix = prefixes[prefIndex]; + if (prefix[prefix.length() - 1] != '_') + prefix.append("_"); + + std::string token = prefix + descriptor; + if (rep > 0) + token.append("_" + std::to_string(rep)); + + outputFile << "[CREATURE_GRAPHICS:" << token << "]" << endl; + outputFile << " [DEFAULT:" << pageName << ":" << descIndex % pageWidth << ":" << descIndex / pageWidth << ":ADD_COLOR]" << endl; + } + outputFile << endl; + } + outputFile << endl; + } + + outputFile.close(); + + return CR_OK; +} diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 87fc35960..2cf382d01 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -1,5 +1,7 @@ // (un)designate matching plants for gathering/cutting +#include + #include "Core.h" #include "Console.h" #include "Export.h" @@ -7,18 +9,19 @@ #include "DataDefs.h" #include "TileTypes.h" -#include "df/world.h" #include "df/map_block.h" -#include "df/tile_dig_designation.h" -#include "df/plant_raw.h" #include "df/plant.h" +#include "df/plant_raw.h" +#include "df/tile_dig_designation.h" +#include "df/world.h" +#include "modules/Designations.h" #include "modules/Maps.h" -#include using std::string; using std::vector; using std::set; + using namespace DFHack; using namespace df::enums; @@ -129,20 +132,14 @@ command_result df_getplants (color_ostream &out, vector & parameters) continue; if (cur->designation[x][y].bits.hidden) continue; - if (deselect && cur->designation[x][y].bits.dig == tile_dig_designation::Default) + if (deselect && Designations::unmarkPlant(plant)) { - cur->designation[x][y].bits.dig = tile_dig_designation::No; - dirty = true; ++count; } - if (!deselect && cur->designation[x][y].bits.dig == tile_dig_designation::No) + if (!deselect && Designations::markPlant(plant)) { - cur->designation[x][y].bits.dig = tile_dig_designation::Default; - dirty = true; ++count; } - if (dirty) - cur->flags.bits.designated = true; } if (count) out.print("Updated %d plant designations.\n", count); @@ -171,4 +168,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector current_bindings; static vector sorted_keys; static bool show_usage = false; -static void send_key(const df::interface_key &key) -{ - set< df::interface_key > keys; - keys.insert(key); - Gui::getCurViewscreen(true)->feed(&keys); -} - static bool can_invoke(string cmdline, df::viewscreen *screen) { vector cmd_parts; @@ -116,7 +109,7 @@ static bool close_hotkeys_screen() } -static void invoke_command(const int index) +static void invoke_command(const size_t index) { if (sorted_keys.size() <= index) return; @@ -147,12 +140,12 @@ public: { hotkeys_column.clear(); - int max_key_length = 0; + size_t max_key_length = 0; for_each_(sorted_keys, [&] (const string &sym) { if (sym.length() > max_key_length) { max_key_length = sym.length(); } }); int padding = max_key_length + 2; - for (int i = 0; i < sorted_keys.size(); i++) + for (size_t i = 0; i < sorted_keys.size(); i++) { string text = pad_string(sorted_keys[i], padding, false); text += current_bindings[sorted_keys[i]]; @@ -230,7 +223,7 @@ public: Plugin *plugin = Core::getInstance().getPluginManager()->getPluginByCommand(first); if (plugin) { - for (auto i = 0; i < plugin->size(); i++) + for (size_t i = 0; i < plugin->size(); i++) { auto pc = plugin->operator[](i); if (pc.name == first) @@ -278,7 +271,7 @@ private: { vector result; string excess; - if (str.length() > width) + if (int(str.length()) > width) { auto cut_space = str.rfind(' ', width-1); int excess_start; diff --git a/plugins/isoworld b/plugins/isoworld index 3ff3f05da..fbbf9e464 160000 --- a/plugins/isoworld +++ b/plugins/isoworld @@ -1 +1 @@ -Subproject commit 3ff3f05da53077dd9e67c1eef672427a2bfd95ea +Subproject commit fbbf9e46458e41707c27f2a4438452a579490db1 diff --git a/plugins/isoworldremote.cpp b/plugins/isoworldremote.cpp index 017fb45f6..651f89700 100644 --- a/plugins/isoworldremote.cpp +++ b/plugins/isoworldremote.cpp @@ -225,7 +225,7 @@ bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCac tile->set_current_year(*cur_year); tile->set_current_season(*cur_season); int num_valid_layers = 0; - for(int z = 0; z < MP->maxZ(); z++) + for(uint32_t z = 0; z < MP->maxZ(); z++) { EmbarkTileLayer * tile_layer = tile->add_tile_layer(); num_valid_layers += gather_embark_tile_layer(EmbX, EmbY, z, tile_layer, MP); @@ -351,11 +351,11 @@ static command_result GetRawNames(color_ostream &stream, const MapRequest *in, R } } out->set_available(true); - for(int i = 0; i < world->raws.inorganics.size(); i++){ + for(size_t i = 0; i < world->raws.inorganics.size(); i++){ out->add_inorganic(world->raws.inorganics[i]->id); } - for(int i = 0; i < world->raws.plants.all.size(); i++){ + for(size_t i = 0; i < world->raws.plants.all.size(); i++){ out->add_organic(world->raws.plants.all[i]->id); } return CR_OK; diff --git a/plugins/labormanager/CMakeLists.txt b/plugins/labormanager/CMakeLists.txt new file mode 100644 index 000000000..8337ac786 --- /dev/null +++ b/plugins/labormanager/CMakeLists.txt @@ -0,0 +1,17 @@ +PROJECT (labormanager) +# A list of source files +SET(PROJECT_SRCS + labormanager.cpp + joblabormapper.cpp +) +# A list of headers +SET(PROJECT_HDRS + labormanager.h + joblabormapper.h +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +DFHACK_PLUGIN(labormanager ${PROJECT_SRCS}) diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp new file mode 100644 index 000000000..2328e14ef --- /dev/null +++ b/plugins/labormanager/joblabormapper.cpp @@ -0,0 +1,929 @@ +/*a + * This file contains the logic to attempt to intuit the labor required for + * a given job. This is way more complicated than it should be, but I have + * not figured out how to make it simpler. + * + * Usage: + * Instantiate an instance of the JobLaborMapper class + * Call the find_job_labor method of that class instance, + * passing the job as the only argument, to determine the labor for that job + * When done, destroy the instance + * + * The class should allow you to create multiple instances, although there is + * little benefit to doing so. jlfuncs are not reused across instances. + * + */ + + +#include "DataDefs.h" +#include "MiscUtils.h" +#include "modules/Materials.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +using namespace std; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using df::global::ui; +using df::global::world; + +#include "labormanager.h" +#include "joblabormapper.h" + +static df::unit_labor hauling_labor_map[] = +{ + df::unit_labor::HAUL_ITEM, /* BAR */ + df::unit_labor::HAUL_STONE, /* SMALLGEM */ + df::unit_labor::HAUL_ITEM, /* BLOCKS */ + df::unit_labor::HAUL_STONE, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* BOULDER */ + df::unit_labor::HAUL_WOOD, /* WOOD */ + df::unit_labor::HAUL_FURNITURE, /* DOOR */ + df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ + df::unit_labor::HAUL_FURNITURE, /* BED */ + df::unit_labor::HAUL_FURNITURE, /* CHAIR */ + df::unit_labor::HAUL_ITEM, /* CHAIN */ + df::unit_labor::HAUL_ITEM, /* FLASK */ + df::unit_labor::HAUL_ITEM, /* GOBLET */ + df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ + df::unit_labor::HAUL_ITEM, /* TOY */ + df::unit_labor::HAUL_FURNITURE, /* WINDOW */ + df::unit_labor::HAUL_ANIMALS, /* CAGE */ + df::unit_labor::HAUL_ITEM, /* BARREL */ + df::unit_labor::HAUL_ITEM, /* BUCKET */ + df::unit_labor::HAUL_ANIMALS, /* ANIMALTRAP */ + df::unit_labor::HAUL_FURNITURE, /* TABLE */ + df::unit_labor::HAUL_FURNITURE, /* COFFIN */ + df::unit_labor::HAUL_FURNITURE, /* STATUE */ + df::unit_labor::HAUL_REFUSE, /* CORPSE */ + df::unit_labor::HAUL_ITEM, /* WEAPON */ + df::unit_labor::HAUL_ITEM, /* ARMOR */ + df::unit_labor::HAUL_ITEM, /* SHOES */ + df::unit_labor::HAUL_ITEM, /* SHIELD */ + df::unit_labor::HAUL_ITEM, /* HELM */ + df::unit_labor::HAUL_ITEM, /* GLOVES */ + df::unit_labor::HAUL_FURNITURE, /* BOX */ + df::unit_labor::HAUL_ITEM, /* BIN */ + df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ + df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ + df::unit_labor::HAUL_FURNITURE, /* CABINET */ + df::unit_labor::HAUL_ITEM, /* FIGURINE */ + df::unit_labor::HAUL_ITEM, /* AMULET */ + df::unit_labor::HAUL_ITEM, /* SCEPTER */ + df::unit_labor::HAUL_ITEM, /* AMMO */ + df::unit_labor::HAUL_ITEM, /* CROWN */ + df::unit_labor::HAUL_ITEM, /* RING */ + df::unit_labor::HAUL_ITEM, /* EARRING */ + df::unit_labor::HAUL_ITEM, /* BRACELET */ + df::unit_labor::HAUL_ITEM, /* GEM */ + df::unit_labor::HAUL_FURNITURE, /* ANVIL */ + df::unit_labor::HAUL_REFUSE, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* REMAINS */ + df::unit_labor::HAUL_FOOD, /* MEAT */ + df::unit_labor::HAUL_FOOD, /* FISH */ + df::unit_labor::HAUL_FOOD, /* FISH_RAW */ + df::unit_labor::HAUL_REFUSE, /* VERMIN */ + df::unit_labor::HAUL_ITEM, /* PET */ + df::unit_labor::HAUL_ITEM, /* SEEDS */ + df::unit_labor::HAUL_FOOD, /* PLANT */ + df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ + df::unit_labor::HAUL_FOOD, /* LEAVES */ + df::unit_labor::HAUL_ITEM, /* THREAD */ + df::unit_labor::HAUL_ITEM, /* CLOTH */ + df::unit_labor::HAUL_ITEM, /* TOTEM */ + df::unit_labor::HAUL_ITEM, /* PANTS */ + df::unit_labor::HAUL_ITEM, /* BACKPACK */ + df::unit_labor::HAUL_ITEM, /* QUIVER */ + df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ + df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ + df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ + df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ + df::unit_labor::HAUL_FOOD, /* DRINK */ + df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ + df::unit_labor::HAUL_FOOD, /* CHEESE */ + df::unit_labor::HAUL_FOOD, /* FOOD */ + df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ + df::unit_labor::HAUL_ITEM, /* COIN */ + df::unit_labor::HAUL_FOOD, /* GLOB */ + df::unit_labor::HAUL_STONE, /* ROCK */ + df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ + df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ + df::unit_labor::HAUL_FURNITURE, /* GRATE */ + df::unit_labor::HAUL_FURNITURE, /* QUERN */ + df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ + df::unit_labor::HAUL_ITEM, /* SPLINT */ + df::unit_labor::HAUL_ITEM, /* CRUTCH */ + df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ + df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ + df::unit_labor::HAUL_ITEM, /* TOOL */ + df::unit_labor::HAUL_FURNITURE, /* SLAB */ + df::unit_labor::HAUL_FOOD, /* EGG */ + df::unit_labor::HAUL_ITEM, /* BOOK */ +}; + +static df::unit_labor workshop_build_labor[] = +{ + /* Carpenters */ df::unit_labor::CARPENTER, + /* Farmers */ df::unit_labor::PROCESS_PLANT, + /* Masons */ df::unit_labor::MASON, + /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, + /* Jewelers */ df::unit_labor::CUT_GEM, + /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, + /* MagmaForge */ df::unit_labor::METAL_CRAFT, + /* Bowyers */ df::unit_labor::BOWYER, + /* Mechanics */ df::unit_labor::MECHANIC, + /* Siege */ df::unit_labor::SIEGECRAFT, + /* Butchers */ df::unit_labor::BUTCHER, + /* Leatherworks */ df::unit_labor::LEATHER, + /* Tanners */ df::unit_labor::TANNER, + /* Clothiers */ df::unit_labor::CLOTHESMAKER, + /* Fishery */ df::unit_labor::CLEAN_FISH, + /* Still */ df::unit_labor::BREWER, + /* Loom */ df::unit_labor::WEAVER, + /* Quern */ df::unit_labor::MILLER, + /* Kennels */ df::unit_labor::ANIMALTRAIN, + /* Kitchen */ df::unit_labor::COOK, + /* Ashery */ df::unit_labor::LYE_MAKING, + /* Dyers */ df::unit_labor::DYER, + /* Millstone */ df::unit_labor::MILLER, + /* Custom */ df::unit_labor::NONE, + /* Tool */ df::unit_labor::NONE +}; + +static df::building* get_building_from_job(df::job* j) +{ + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; +} + +static df::unit_labor construction_build_labor(df::building_actual* b) +{ + if (b->getType() == df::building_type::RoadPaved) + return df::unit_labor::BUILD_ROAD; + // Find last item in building with use mode appropriate to the building's constructions state + // For screw pumps contained_items[0] = pipe, 1 corkscrew, 2 block + // For wells 0 mechanism, 1 rope, 2 bucket, 3 block + // Trade depots and bridges use the last one too + // Must check use mode b/c buildings may have items in them that are not part of the building + + df::item* i = 0; + for (auto p = b->contained_items.begin(); p != b->contained_items.end(); p++) + if ((b->construction_stage > 0 && (*p)->use_mode == 2) || + (b->construction_stage == 0 && (*p)->use_mode == 0)) + i = (*p)->item; + + MaterialInfo matinfo; + if (i && matinfo.decode(i)) + { + if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) + return df::unit_labor::METAL_CRAFT; + if (matinfo.material->flags.is_set(df::material_flags::WOOD)) + return df::unit_labor::CARPENTER; + } + return df::unit_labor::MASON; +} + + +class jlfunc +{ +public: + virtual ~jlfunc() {} + virtual df::unit_labor get_labor(df::job* j) = 0; +}; + +class jlfunc_const : public jlfunc +{ +private: + df::unit_labor labor; +public: + df::unit_labor get_labor(df::job* j) + { + return labor; + } + jlfunc_const(df::unit_labor l) : labor(l) {}; +}; + +class jlfunc_hauling : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::item* item = 0; + if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) + return (df::unit_labor) j->item_subtype; + + for (auto i = j->items.begin(); i != j->items.end(); i++) + { + if ((*i)->role == 7) + { + item = (*i)->item; + break; + } + } + + if (item && item->flags.bits.container) + { + for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) + { + if ((*a)->getType() == df::general_ref_type::CONTAINS_ITEM) + { + int item_id = ((df::general_ref_contains_itemst *) (*a))->item_id; + item = binsearch_in_vector(world->items.all, item_id); + break; + } + } + } + + df::unit_labor l = item ? hauling_labor_map[item->getType()] : df::unit_labor::HAUL_ITEM; + if (item && l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) + l = df::unit_labor::HAUL_BODY; + return l; + } + jlfunc_hauling() {}; +}; + +class jlfunc_construct_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + if (j->flags.bits.item_lost) + return df::unit_labor::NONE; + + df::building* bld = get_building_from_job(j); + switch (bld->getType()) + { + case df::building_type::NONE: + return df::unit_labor::NONE; + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; + case df::building_type::Workshop: + { + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->design && !ws->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } + break; + case df::building_type::Construction: + return df::unit_labor::BUILD_CONSTRUCTION; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Bridge: + case df::building_type::ArcheryTarget: + case df::building_type::WaterWheel: + case df::building_type::RoadPaved: + case df::building_type::Well: + case df::building_type::ScrewPump: + case df::building_type::Wagon: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::Windmill: + { + df::building_actual* b = (df::building_actual*) bld; + if (b->design && !b->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + return construction_build_labor(b); + } + break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: + case df::building_type::Cage: + case df::building_type::NestBox: + case df::building_type::TractionBench: + case df::building_type::Slab: + case df::building_type::Chain: + case df::building_type::GrateFloor: + case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateWall: + case df::building_type::Bookcase: + case df::building_type::Instrument: + case df::building_type::DisplayFurniture: + return df::unit_labor::HAUL_FURNITURE; + case df::building_type::Trap: + case df::building_type::GearAssembly: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: + case df::building_type::Rollers: + return df::unit_labor::MECHANIC; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; + case df::building_type::RoadDirt: + return df::unit_labor::BUILD_ROAD; + } + + debug("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); + + return df::unit_labor::NONE; + } + jlfunc_construct_bld() {} +}; + +class jlfunc_destroy_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + df::building_type type = bld->getType(); + + switch (bld->getType()) + { + case df::building_type::NONE: + return df::unit_labor::NONE; + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; + case df::building_type::Workshop: + { + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } + break; + case df::building_type::Construction: + return df::unit_labor::REMOVE_CONSTRUCTION; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Wagon: + case df::building_type::Bridge: + case df::building_type::ScrewPump: + case df::building_type::ArcheryTarget: + case df::building_type::RoadPaved: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::WaterWheel: + case df::building_type::Well: + case df::building_type::Windmill: + { + auto b = (df::building_actual*) bld; + return construction_build_labor(b); + } + break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; + case df::building_type::Trap: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: + case df::building_type::GearAssembly: + case df::building_type::Rollers: + return df::unit_labor::MECHANIC; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: + case df::building_type::Cage: + case df::building_type::NestBox: + case df::building_type::TractionBench: + case df::building_type::Slab: + case df::building_type::Chain: + case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateFloor: + case df::building_type::GrateWall: + case df::building_type::Bookcase: + case df::building_type::Instrument: + case df::building_type::DisplayFurniture: + return df::unit_labor::HAUL_FURNITURE; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::RoadDirt: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; + } + + debug("LABORMANAGER: Cannot deduce labor for destroy building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); + + return df::unit_labor::NONE; + } + jlfunc_destroy_bld() {} +}; + +class jlfunc_make : public jlfunc +{ +private: + df::unit_labor metaltype; +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + if (bld->getType() == df::building_type::Workshop) + { + df::workshop_type type = ((df::building_workshopst*)(bld))->type; + switch (type) + { + case df::workshop_type::Craftsdwarfs: + { + df::item_type jobitem = j->job_items[0]->item_type; + switch (jobitem) + { + case df::item_type::BOULDER: + return df::unit_labor::STONE_CRAFT; + case df::item_type::NONE: + if (j->material_category.bits.bone || + j->material_category.bits.horn || + j->material_category.bits.tooth || + j->material_category.bits.shell) + return df::unit_labor::BONE_CARVE; + else + { + debug("LABORMANAGER: Cannot deduce labor for make crafts job (not bone)\n"); + debug_pause(); + return df::unit_labor::NONE; + } + case df::item_type::WOOD: + return df::unit_labor::WOOD_CRAFT; + case df::item_type::CLOTH: + return df::unit_labor::CLOTHESMAKER; + case df::item_type::SKIN_TANNED: + return df::unit_labor::LEATHER; + default: + debug("LABORMANAGER: Cannot deduce labor for make crafts job, item type %s\n", + ENUM_KEY_STR(item_type, jobitem).c_str()); + debug_pause(); + return df::unit_labor::NONE; + } + } + case df::workshop_type::Masons: + return df::unit_labor::MASON; + case df::workshop_type::Carpenters: + return df::unit_labor::CARPENTER; + case df::workshop_type::Leatherworks: + return df::unit_labor::LEATHER; + case df::workshop_type::Clothiers: + return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::Bowyers: + return df::unit_labor::BOWYER; + case df::workshop_type::MagmaForge: + case df::workshop_type::MetalsmithsForge: + return metaltype; + default: + debug("LABORMANAGER: Cannot deduce labor for make job, workshop type %s\n", + ENUM_KEY_STR(workshop_type, type).c_str()); + debug_pause(); + return df::unit_labor::NONE; + } + } + else if (bld->getType() == df::building_type::Furnace) + { + df::furnace_type type = ((df::building_furnacest*)(bld))->type; + switch (type) + { + case df::furnace_type::MagmaGlassFurnace: + case df::furnace_type::GlassFurnace: + return df::unit_labor::GLASSMAKER; + default: + debug("LABORMANAGER: Cannot deduce labor for make job, furnace type %s\n", + ENUM_KEY_STR(furnace_type, type).c_str()); + debug_pause(); + return df::unit_labor::NONE; + } + } + + debug("LABORMANAGER: Cannot deduce labor for make job, building type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + debug_pause(); + + return df::unit_labor::NONE; + } + + jlfunc_make(df::unit_labor mt) : metaltype(mt) {} +}; + +class jlfunc_custom : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + for (auto r : df::reaction::get_vector()) + { + if (r->code == j->reaction_name) + { + df::job_skill skill = r->skill; + df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); + return labor; + } + } + return df::unit_labor::NONE; + } + jlfunc_custom() {} +}; + +jlfunc* JobLaborMapper::jlf_const(df::unit_labor l) { + jlfunc* jlf; + if (jlf_cache.count(l) == 0) + { + jlf = new jlfunc_const(l); + jlf_cache[l] = jlf; + } + else + jlf = jlf_cache[l]; + + return jlf; +} + +JobLaborMapper::~JobLaborMapper() +{ + std::set log; + + for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) + { + if (!log.count(i->second)) + { + log.insert(i->second); + delete i->second; + } + i->second = 0; + } + + FOR_ENUM_ITEMS(job_type, j) + { + if (j < 0) + continue; + + jlfunc* p = job_to_labor_table[j]; + if (!log.count(p)) + { + log.insert(p); + delete p; + } + job_to_labor_table[j] = 0; + } + +} + +JobLaborMapper::JobLaborMapper() +{ + jlfunc* jlf_hauling = new jlfunc_hauling(); + jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); + jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); + jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); + jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + + jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); + + job_to_labor_table[df::job_type::CarveFortification] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailWall] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailFloor] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::Dig] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveDownwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpDownStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveRamp] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); + job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); + job_to_labor_table[df::job_type::RemoveConstruction] = jlf_const(df::unit_labor::REMOVE_CONSTRUCTION); + job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::BringItemToDepot] = jlf_const(df::unit_labor::HAUL_TRADE); + job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; + job_to_labor_table[df::job_type::Eat] = jlf_no_labor; + job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink2] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; + job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::HAUL_ITEM); + job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; + job_to_labor_table[df::job_type::Kidnap] = jlf_no_labor; + job_to_labor_table[df::job_type::BeatCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::StartingFistFight] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectTaxes] = jlf_no_labor; + job_to_labor_table[df::job_type::GuardTaxCollector] = jlf_no_labor; + job_to_labor_table[df::job_type::CatchLiveLandAnimal] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::CatchLiveFish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::ReturnKill] = jlf_no_labor; + job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; + job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; + job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; + job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; + job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; + job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; + job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; + job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping2] = jlf_no_labor; + job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); + job_to_labor_table[df::job_type::Rest] = jlf_no_labor; + job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; + job_to_labor_table[df::job_type::DumpItem] = jlf_const(df::unit_labor::HAUL_REFUSE); + job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMagmaForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBrooding] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodFell] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodCarpenter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMason] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBowyer] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodTanner] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodWeaver] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodGlassmaker] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMechanics] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructBuilding] = new jlfunc_construct_bld(); + job_to_labor_table[df::job_type::ConstructDoor] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructFloodgate] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBed] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructThrone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCoffin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructTable] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructChest] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructArmorStand] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructWeaponRack] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCabinet] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; + job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); + job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::EncrustWithGlass] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::DestroyBuilding] = new jlfunc_destroy_bld(); + job_to_labor_table[df::job_type::SmeltOre] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::MeltMetalObject] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::ExtractMetalStrands] = jlf_const(df::unit_labor::EXTRACT_STRAND); + job_to_labor_table[df::job_type::PlantSeeds] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::HarvestPlants] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::TrainHuntingAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::TrainWarAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::MakeWeapon] = jlf_make_weapon; + job_to_labor_table[df::job_type::ForgeAnvil] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCatapultParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::ConstructBallistaParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::MakeArmor] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeHelm] = jlf_make_armor; + job_to_labor_table[df::job_type::MakePants] = jlf_make_armor; + job_to_labor_table[df::job_type::StudWith] = jlf_make_object; + job_to_labor_table[df::job_type::ButcherAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::PrepareRawFish] = jlf_const(df::unit_labor::CLEAN_FISH); + job_to_labor_table[df::job_type::MillPlants] = jlf_const(df::unit_labor::MILLER); + job_to_labor_table[df::job_type::BaitTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); + job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); + job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); + job_to_labor_table[df::job_type::WeaveCloth] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::MakeGloves] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShoes] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShield] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeCage] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeChain] = jlf_make_object; + job_to_labor_table[df::job_type::MakeFlask] = jlf_make_object; + job_to_labor_table[df::job_type::MakeGoblet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeToy] = jlf_make_object; + job_to_labor_table[df::job_type::MakeAnimalTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MakeBarrel] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeBucket] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeWindow] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeTotem] = jlf_const(df::unit_labor::BONE_CARVE); + job_to_labor_table[df::job_type::MakeAmmo] = jlf_make_weapon; + job_to_labor_table[df::job_type::DecorateWith] = jlf_make_object; + job_to_labor_table[df::job_type::MakeBackpack] = jlf_make_object; + job_to_labor_table[df::job_type::MakeQuiver] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeBallistaArrowHead] = jlf_make_weapon; + job_to_labor_table[df::job_type::AssembleSiegeAmmo] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::LoadCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::LoadBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_make_weapon; + job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; + job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::PullLever] = jlf_const(df::unit_labor::PULL_LEVER); + job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST); + job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH); + job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN); + job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::ChainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::UnchainAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::CageLargeCreature] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); + job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE); + job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS); + job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::DRESSING_WOUNDS); + job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY); + job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); + job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::DrainAquarium] = jlf_const(df::unit_labor::HAUL_WATER); + job_to_labor_table[df::job_type::FillAquarium] = jlf_const(df::unit_labor::HAUL_WATER); + job_to_labor_table[df::job_type::FillPond] = jlf_const(df::unit_labor::HAUL_WATER); + job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::GiveWater2] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::GiveFood2] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS); + job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; + job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeAsh] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeLye] = jlf_const(df::unit_labor::LYE_MAKING); + job_to_labor_table[df::job_type::MakePotashFromLye] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::FertilizeField] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::MakePotashFromAsh] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::DyeThread] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::DyeCloth] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::SewImage] = jlf_make_object; + job_to_labor_table[df::job_type::MakePipeSection] = jlf_make_furniture; + job_to_labor_table[df::job_type::OperatePump] = jlf_const(df::unit_labor::OPERATE_PUMP); + job_to_labor_table[df::job_type::ManageWorkOrders] = jlf_no_labor; + job_to_labor_table[df::job_type::UpdateStockpileRecords] = jlf_no_labor; + job_to_labor_table[df::job_type::TradeAtDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructHatchCover] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; + job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; + job_to_labor_table[df::job_type::BringCrutch] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); + job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; + job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); + job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); + job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_const(df::unit_labor::HAUL_ANIMALS); + job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::MakeTool] = jlf_make_object; + job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); + job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CauseTrouble] = jlf_no_labor; + job_to_labor_table[df::job_type::DrinkBlood] = jlf_no_labor; + job_to_labor_table[df::job_type::ReportCrime] = jlf_no_labor; + job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::HANDLE_VEHICLES); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_hauling; + job_to_labor_table[df::job_type::GeldAnimal] = jlf_const(df::unit_labor::GELD); + job_to_labor_table[df::job_type::MakeFigurine] = jlf_make_object; + job_to_labor_table[df::job_type::MakeAmulet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeScepter] = jlf_make_object; + job_to_labor_table[df::job_type::MakeCrown] = jlf_make_object; + job_to_labor_table[df::job_type::MakeRing] = jlf_make_object; + job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object; + job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeGem] = jlf_make_object; + job_to_labor_table[df::job_type::PutItemOnDisplay] = jlf_const(df::unit_labor::HAUL_ITEM); + + job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation +}; + +df::unit_labor JobLaborMapper::find_job_labor(df::job* j) +{ + if (j->job_type == df::job_type::CustomReaction) + { + for (auto r : df::reaction::get_vector()) + { + if (r->code == j->reaction_name) + { + df::job_skill skill = r->skill; + return ENUM_ATTR(job_skill, labor, skill); + } + } + return df::unit_labor::NONE; + } + + + df::unit_labor labor; + if (job_to_labor_table.count(j->job_type) == 0) + { + debug("LABORMANAGER: job has no job to labor table entry: %s (%d)\n", ENUM_KEY_STR(job_type, j->job_type).c_str(), j->job_type); + debug_pause(); + labor = df::unit_labor::NONE; + } + else { + + labor = job_to_labor_table[j->job_type]->get_labor(j); + } + + return labor; +} + +/* End of labor deducer */ diff --git a/plugins/labormanager/joblabormapper.h b/plugins/labormanager/joblabormapper.h new file mode 100644 index 000000000..37d405f54 --- /dev/null +++ b/plugins/labormanager/joblabormapper.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +using namespace DFHack; +using namespace df::enums; + +#include "df/job.h" +#include "df/job_type.h" +#include "df/unit_labor.h" + + +class jlfunc; + +class JobLaborMapper { + +private: + std::map job_to_labor_table; + std::map jlf_cache; + + jlfunc* jlf_const(df::unit_labor l); + +public: + ~JobLaborMapper(); + JobLaborMapper(); + + df::unit_labor find_job_labor(df::job* j); + + +}; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp new file mode 100644 index 000000000..7c749edce --- /dev/null +++ b/plugins/labormanager/labormanager.cpp @@ -0,0 +1,2334 @@ +/* +* Labor manager (formerly Autolabor 2) module for dfhack +* +* */ + + +#include "Core.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "modules/Units.h" +#include "modules/World.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" +#include "modules/Items.h" + +// DF data structure definition headers +#include "DataDefs.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "labormanager.h" +#include "joblabormapper.h" + +using namespace std; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using df::global::ui; +using df::global::world; + +#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) + +DFHACK_PLUGIN_IS_ENABLED(enable_labormanager); + +static bool print_debug = 0; +static bool pause_on_error = 1; + +static std::vector state_count(5); + +static PersistentDataItem config; + +enum ConfigFlags { + CF_ENABLED = 1, + CF_ALLOW_FISHING = 2, + CF_ALLOW_HUNTING = 4, +}; + + +// Here go all the command declarations... +// mostly to allow having the mandatory stuff on top of the file and commands on the bottom +command_result labormanager(color_ostream &out, std::vector & parameters); + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - labormanager.plug.so or labormanager.plug.dll in this case +DFHACK_PLUGIN("labormanager"); + +static void generate_labor_to_skill_map(); + +enum dwarf_state { + // Ready for a new task + IDLE, + + // Busy with a useful task + BUSY, + + // In the military, can't work + MILITARY, + + // Child or noble, can't work + CHILD, + + // Doing something that precludes working, may be busy for a while + OTHER +}; + +const int NUM_STATE = 5; + +static const char *state_names[] = { + "IDLE", + "BUSY", + "MILITARY", + "CHILD", + "OTHER", +}; + +static const dwarf_state dwarf_states[] = { + BUSY /* CarveFortification */, + BUSY /* DetailWall */, + BUSY /* DetailFloor */, + BUSY /* Dig */, + BUSY /* CarveUpwardStaircase */, + BUSY /* CarveDownwardStaircase */, + BUSY /* CarveUpDownStaircase */, + BUSY /* CarveRamp */, + BUSY /* DigChannel */, + BUSY /* FellTree */, + BUSY /* GatherPlants */, + BUSY /* RemoveConstruction */, + BUSY /* CollectWebs */, + BUSY /* BringItemToDepot */, + BUSY /* BringItemToShop */, + OTHER /* Eat */, + OTHER /* GetProvisions */, + OTHER /* Drink */, + OTHER /* Drink2 */, + OTHER /* FillWaterskin */, + OTHER /* FillWaterskin2 */, + OTHER /* Sleep */, + BUSY /* CollectSand */, + BUSY /* Fish */, + BUSY /* Hunt */, + OTHER /* HuntVermin */, + BUSY /* Kidnap */, + BUSY /* BeatCriminal */, + BUSY /* StartingFistFight */, + BUSY /* CollectTaxes */, + BUSY /* GuardTaxCollector */, + BUSY /* CatchLiveLandAnimal */, + BUSY /* CatchLiveFish */, + BUSY /* ReturnKill */, + BUSY /* CheckChest */, + BUSY /* StoreOwnedItem */, + BUSY /* PlaceItemInTomb */, + BUSY /* StoreItemInStockpile */, + BUSY /* StoreItemInBag */, + BUSY /* StoreItemInHospital */, + BUSY /* StoreItemInChest */, + BUSY /* StoreItemInCabinet */, + BUSY /* StoreWeapon */, + BUSY /* StoreArmor */, + BUSY /* StoreItemInBarrel */, + BUSY /* StoreItemInBin */, + BUSY /* SeekArtifact */, + BUSY /* SeekInfant */, + OTHER /* AttendParty */, + OTHER /* GoShopping */, + OTHER /* GoShopping2 */, + BUSY /* Clean */, + OTHER /* Rest */, + OTHER /* PickupEquipment */, + BUSY /* DumpItem */, + OTHER /* StrangeMoodCrafter */, + OTHER /* StrangeMoodJeweller */, + OTHER /* StrangeMoodForge */, + OTHER /* StrangeMoodMagmaForge */, + OTHER /* StrangeMoodBrooding */, + OTHER /* StrangeMoodFell */, + OTHER /* StrangeMoodCarpenter */, + OTHER /* StrangeMoodMason */, + OTHER /* StrangeMoodBowyer */, + OTHER /* StrangeMoodTanner */, + OTHER /* StrangeMoodWeaver */, + OTHER /* StrangeMoodGlassmaker */, + OTHER /* StrangeMoodMechanics */, + BUSY /* ConstructBuilding */, + BUSY /* ConstructDoor */, + BUSY /* ConstructFloodgate */, + BUSY /* ConstructBed */, + BUSY /* ConstructThrone */, + BUSY /* ConstructCoffin */, + BUSY /* ConstructTable */, + BUSY /* ConstructChest */, + BUSY /* ConstructBin */, + BUSY /* ConstructArmorStand */, + BUSY /* ConstructWeaponRack */, + BUSY /* ConstructCabinet */, + BUSY /* ConstructStatue */, + BUSY /* ConstructBlocks */, + BUSY /* MakeRawGlass */, + BUSY /* MakeCrafts */, + BUSY /* MintCoins */, + BUSY /* CutGems */, + BUSY /* CutGlass */, + BUSY /* EncrustWithGems */, + BUSY /* EncrustWithGlass */, + BUSY /* DestroyBuilding */, + BUSY /* SmeltOre */, + BUSY /* MeltMetalObject */, + BUSY /* ExtractMetalStrands */, + BUSY /* PlantSeeds */, + BUSY /* HarvestPlants */, + BUSY /* TrainHuntingAnimal */, + BUSY /* TrainWarAnimal */, + BUSY /* MakeWeapon */, + BUSY /* ForgeAnvil */, + BUSY /* ConstructCatapultParts */, + BUSY /* ConstructBallistaParts */, + BUSY /* MakeArmor */, + BUSY /* MakeHelm */, + BUSY /* MakePants */, + BUSY /* StudWith */, + BUSY /* ButcherAnimal */, + BUSY /* PrepareRawFish */, + BUSY /* MillPlants */, + BUSY /* BaitTrap */, + BUSY /* MilkCreature */, + BUSY /* MakeCheese */, + BUSY /* ProcessPlants */, + BUSY /* ProcessPlantsBag */, + BUSY /* ProcessPlantsVial */, + BUSY /* ProcessPlantsBarrel */, + BUSY /* PrepareMeal */, + BUSY /* WeaveCloth */, + BUSY /* MakeGloves */, + BUSY /* MakeShoes */, + BUSY /* MakeShield */, + BUSY /* MakeCage */, + BUSY /* MakeChain */, + BUSY /* MakeFlask */, + BUSY /* MakeGoblet */, + BUSY /* MakeInstrument */, + BUSY /* MakeToy */, + BUSY /* MakeAnimalTrap */, + BUSY /* MakeBarrel */, + BUSY /* MakeBucket */, + BUSY /* MakeWindow */, + BUSY /* MakeTotem */, + BUSY /* MakeAmmo */, + BUSY /* DecorateWith */, + BUSY /* MakeBackpack */, + BUSY /* MakeQuiver */, + BUSY /* MakeBallistaArrowHead */, + BUSY /* AssembleSiegeAmmo */, + BUSY /* LoadCatapult */, + BUSY /* LoadBallista */, + BUSY /* FireCatapult */, + BUSY /* FireBallista */, + BUSY /* ConstructMechanisms */, + BUSY /* MakeTrapComponent */, + BUSY /* LoadCageTrap */, + BUSY /* LoadStoneTrap */, + BUSY /* LoadWeaponTrap */, + BUSY /* CleanTrap */, + BUSY /* CastSpell */, + BUSY /* LinkBuildingToTrigger */, + BUSY /* PullLever */, + BUSY /* BrewDrink */, + BUSY /* ExtractFromPlants */, + BUSY /* ExtractFromRawFish */, + BUSY /* ExtractFromLandAnimal */, + BUSY /* TameVermin */, + BUSY /* TameAnimal */, + BUSY /* ChainAnimal */, + BUSY /* UnchainAnimal */, + BUSY /* UnchainPet */, + BUSY /* ReleaseLargeCreature */, + BUSY /* ReleasePet */, + BUSY /* ReleaseSmallCreature */, + BUSY /* HandleSmallCreature */, + BUSY /* HandleLargeCreature */, + BUSY /* CageLargeCreature */, + BUSY /* CageSmallCreature */, + BUSY /* RecoverWounded */, + BUSY /* DiagnosePatient */, + BUSY /* ImmobilizeBreak */, + BUSY /* DressWound */, + BUSY /* CleanPatient */, + BUSY /* Surgery */, + BUSY /* Suture */, + BUSY /* SetBone */, + BUSY /* PlaceInTraction */, + BUSY /* DrainAquarium */, + BUSY /* FillAquarium */, + BUSY /* FillPond */, + BUSY /* GiveWater */, + BUSY /* GiveFood */, + BUSY /* GiveWater2 */, + BUSY /* GiveFood2 */, + BUSY /* RecoverPet */, + BUSY /* PitLargeAnimal */, + BUSY /* PitSmallAnimal */, + BUSY /* SlaughterAnimal */, + BUSY /* MakeCharcoal */, + BUSY /* MakeAsh */, + BUSY /* MakeLye */, + BUSY /* MakePotashFromLye */, + BUSY /* FertilizeField */, + BUSY /* MakePotashFromAsh */, + BUSY /* DyeThread */, + BUSY /* DyeCloth */, + BUSY /* SewImage */, + BUSY /* MakePipeSection */, + BUSY /* OperatePump */, + OTHER /* ManageWorkOrders */, + OTHER /* UpdateStockpileRecords */, + OTHER /* TradeAtDepot */, + BUSY /* ConstructHatchCover */, + BUSY /* ConstructGrate */, + BUSY /* RemoveStairs */, + BUSY /* ConstructQuern */, + BUSY /* ConstructMillstone */, + BUSY /* ConstructSplint */, + BUSY /* ConstructCrutch */, + BUSY /* ConstructTractionBench */, + BUSY /* CleanSelf */, + BUSY /* BringCrutch */, + BUSY /* ApplyCast */, + BUSY /* CustomReaction */, + BUSY /* ConstructSlab */, + BUSY /* EngraveSlab */, + BUSY /* ShearCreature */, + BUSY /* SpinThread */, + BUSY /* PenLargeAnimal */, + BUSY /* PenSmallAnimal */, + BUSY /* MakeTool */, + BUSY /* CollectClay */, + BUSY /* InstallColonyInHive */, + BUSY /* CollectHiveProducts */, + OTHER /* CauseTrouble */, + OTHER /* DrinkBlood */, + OTHER /* ReportCrime */, + OTHER /* ExecuteCriminal */, + BUSY /* TrainAnimal */, + BUSY /* CarveTrack */, + BUSY /* PushTrackVehicle */, + BUSY /* PlaceTrackVehicle */, + BUSY /* StoreItemInVehicle */, + BUSY /* GeldAnimal */, + BUSY /* MakeFigurine */, + BUSY /* MakeAmulet */, + BUSY /* MakeScepter */, + BUSY /* MakeCrown */, + BUSY /* MakeRing */, + BUSY /* MakeEarring */, + BUSY /* MakeBracelet */, + BUSY /* MakeGem */, + BUSY /* PutItemOnDisplay */, +}; + +struct labor_info +{ + PersistentDataItem config; + + int active_dwarfs; + int idle_dwarfs; + int busy_dwarfs; + + int priority() { return config.ival(1); } + void set_priority(int priority) { config.ival(1) = priority; } + + int maximum_dwarfs() { return config.ival(2); } + void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } + + int time_since_last_assigned() + { + return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); + } + void mark_assigned() { + config.ival(3) = (*df::global::cur_year); + config.ival(4) = (*df::global::cur_year_tick); + } + +}; + +enum tools_enum { + TOOL_NONE, TOOL_PICK, TOOL_AXE, TOOL_CROSSBOW, + TOOLS_MAX +}; + + +struct labor_default +{ + int priority; + int maximum_dwarfs; + tools_enum tool; +}; + +static std::vector labor_infos; + +static const struct labor_default default_labor_infos[] = { + /* MINE */ {100, 0, TOOL_PICK}, + /* HAUL_STONE */ {100, 0, TOOL_NONE}, + /* HAUL_WOOD */ {100, 0, TOOL_NONE}, + /* HAUL_BODY */ {1000, 0, TOOL_NONE}, + /* HAUL_FOOD */ {500, 0, TOOL_NONE}, + /* HAUL_REFUSE */ {200, 0, TOOL_NONE}, + /* HAUL_ITEM */ {100, 0, TOOL_NONE}, + /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, + /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, + /* CLEAN */ {100, 0, TOOL_NONE}, + /* CUTWOOD */ {100, 0, TOOL_AXE}, + /* CARPENTER */ {100, 0, TOOL_NONE}, + /* DETAIL */ {100, 0, TOOL_NONE}, + /* MASON */ {100, 0, TOOL_NONE}, + /* ARCHITECT */ {100, 0, TOOL_NONE}, + /* ANIMALTRAIN */ {100, 0, TOOL_NONE}, + /* ANIMALCARE */ {100, 0, TOOL_NONE}, + /* DIAGNOSE */ {1000, 0, TOOL_NONE}, + /* SURGERY */ {1000, 0, TOOL_NONE}, + /* BONE_SETTING */ {1000, 0, TOOL_NONE}, + /* SUTURING */ {1000, 0, TOOL_NONE}, + /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, + /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, + /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, + /* BUTCHER */ {500, 0, TOOL_NONE}, + /* TRAPPER */ {100, 0, TOOL_NONE}, + /* DISSECT_VERMIN */ {100, 0, TOOL_NONE}, + /* LEATHER */ {100, 0, TOOL_NONE}, + /* TANNER */ {100, 0, TOOL_NONE}, + /* BREWER */ {100, 0, TOOL_NONE}, + /* ALCHEMIST */ {100, 0, TOOL_NONE}, + /* SOAP_MAKER */ {100, 0, TOOL_NONE}, + /* WEAVER */ {100, 0, TOOL_NONE}, + /* CLOTHESMAKER */ {100, 0, TOOL_NONE}, + /* MILLER */ {100, 0, TOOL_NONE}, + /* PROCESS_PLANT */ {100, 0, TOOL_NONE}, + /* MAKE_CHEESE */ {100, 0, TOOL_NONE}, + /* MILK */ {100, 0, TOOL_NONE}, + /* COOK */ {100, 0, TOOL_NONE}, + /* PLANT */ {100, 0, TOOL_NONE}, + /* HERBALIST */ {100, 0, TOOL_NONE}, + /* FISH */ {100, 0, TOOL_NONE}, + /* CLEAN_FISH */ {100, 0, TOOL_NONE}, + /* DISSECT_FISH */ {100, 0, TOOL_NONE}, + /* HUNT */ {100, 0, TOOL_CROSSBOW}, + /* SMELT */ {100, 0, TOOL_NONE}, + /* FORGE_WEAPON */ {100, 0, TOOL_NONE}, + /* FORGE_ARMOR */ {100, 0, TOOL_NONE}, + /* FORGE_FURNITURE */ {100, 0, TOOL_NONE}, + /* METAL_CRAFT */ {100, 0, TOOL_NONE}, + /* CUT_GEM */ {100, 0, TOOL_NONE}, + /* ENCRUST_GEM */ {100, 0, TOOL_NONE}, + /* WOOD_CRAFT */ {100, 0, TOOL_NONE}, + /* STONE_CRAFT */ {100, 0, TOOL_NONE}, + /* BONE_CARVE */ {100, 0, TOOL_NONE}, + /* GLASSMAKER */ {100, 0, TOOL_NONE}, + /* EXTRACT_STRAND */ {100, 0, TOOL_NONE}, + /* SIEGECRAFT */ {100, 0, TOOL_NONE}, + /* SIEGEOPERATE */ {100, 0, TOOL_NONE}, + /* BOWYER */ {100, 0, TOOL_NONE}, + /* MECHANIC */ {100, 0, TOOL_NONE}, + /* POTASH_MAKING */ {100, 0, TOOL_NONE}, + /* LYE_MAKING */ {100, 0, TOOL_NONE}, + /* DYER */ {100, 0, TOOL_NONE}, + /* BURN_WOOD */ {100, 0, TOOL_NONE}, + /* OPERATE_PUMP */ {100, 0, TOOL_NONE}, + /* SHEARER */ {100, 0, TOOL_NONE}, + /* SPINNER */ {100, 0, TOOL_NONE}, + /* POTTERY */ {100, 0, TOOL_NONE}, + /* GLAZING */ {100, 0, TOOL_NONE}, + /* PRESSING */ {100, 0, TOOL_NONE}, + /* BEEKEEPING */ {100, 0, TOOL_NONE}, + /* WAX_WORKING */ {100, 0, TOOL_NONE}, + /* PUSH_HAUL_VEHICLES */ {100, 0, TOOL_NONE}, + /* HAUL_TRADE */ {1000, 0, TOOL_NONE}, + /* PULL_LEVER */ {1000, 0, TOOL_NONE}, + /* REMOVE_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* HAUL_WATER */ {100, 0, TOOL_NONE}, + /* GELD */ {100, 0, TOOL_NONE}, + /* BUILD_ROAD */ {100, 0, TOOL_NONE}, + /* BUILD_CONSTRUCTION */ {100, 0, TOOL_NONE}, + /* PAPERMAKING */ {100, 0, TOOL_NONE}, + /* BOOKBINDING */ {100, 0, TOOL_NONE} +}; + + + +struct dwarf_info_t +{ + df::unit* dwarf; + dwarf_state state; + + bool clear_all; + + bool has_tool[TOOLS_MAX]; + + int high_skill; + + bool has_children; + bool armed; + + df::unit_labor using_labor; + + dwarf_info_t(df::unit* dw) : dwarf(dw), state(OTHER), + clear_all(false), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) + { + for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + has_tool[e] = false; + } + + ~dwarf_info_t() + { + } + +}; + +color_ostream* debug_stream; + +void debug(const char* fmt, ...) +{ + if (debug_stream) + { + va_list args; + va_start(args, fmt); + debug_stream->vprint(fmt, args); + va_end(args); + } +} + +void debug_pause() +{ + if (pause_on_error) + { + debug("LABORMANAGER: Game paused so you can investigate the above message.\nUse 'labormanager pause-on-error no' to disable autopausing.\n"); + *df::global::pause_state = true; + } +} + +static JobLaborMapper* labor_mapper = 0; + +static bool initialized = false; + +static bool isOptionEnabled(unsigned flag) +{ + return config.isValid() && (config.ival(0) & flag) != 0; +} + +static void setOptionEnabled(ConfigFlags flag, bool on) +{ + if (!config.isValid()) + return; + + if (on) + config.ival(0) |= flag; + else + config.ival(0) &= ~flag; +} + +static void cleanup_state() +{ + enable_labormanager = false; + labor_infos.clear(); + initialized = false; +} + +static void reset_labor(df::unit_labor labor) +{ + labor_infos[labor].set_priority(default_labor_infos[labor].priority); + labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); +} + +static void init_state() +{ + config = World::GetPersistentData("labormanager/2.0/config"); + if (config.isValid() && config.ival(0) == -1) + config.ival(0) = 0; + + enable_labormanager = isOptionEnabled(CF_ENABLED); + + if (!enable_labormanager) + return; + + // Load labors from save + labor_infos.resize(ARRAY_COUNT(default_labor_infos)); + + std::vector items; + World::GetPersistentData(&items, "labormanager/2.0/labors/", true); + + for (auto p = items.begin(); p != items.end(); p++) + { + string key = p->key(); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("labormanager/2.0/labors/")).c_str()); + if (labor >= 0 && size_t(labor) < labor_infos.size()) + { + labor_infos[labor].config = *p; + labor_infos[labor].active_dwarfs = 0; + } + } + + // Add default labors for those not in save + for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + if (labor_infos[i].config.isValid()) + continue; + + std::stringstream name; + name << "labormanager/2.0/labors/" << i; + + labor_infos[i].config = World::AddPersistentData(name.str()); + labor_infos[i].mark_assigned(); + labor_infos[i].active_dwarfs = 0; + reset_labor((df::unit_labor) i); + } + + initialized = true; + +} + +static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; + +static void generate_labor_to_skill_map() +{ + // Generate labor -> skill mapping + + for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) + labor_to_skill[i] = job_skill::NONE; + + FOR_ENUM_ITEMS(job_skill, skill) + { + int labor = ENUM_ATTR(job_skill, labor, skill); + if (labor != unit_labor::NONE) + { + /* + assert(labor >= 0); + assert(labor < ARRAY_COUNT(labor_to_skill)); + */ + + labor_to_skill[labor] = skill; + } + } +} + +struct skill_attr_weight { + int phys_attr_weights[6]; + int mental_attr_weights[13]; +}; + +static struct skill_attr_weight skill_attr_weights[ENUM_LAST_ITEM(job_skill) + 1] = +{ + // S A T E R D AA F W C I P M LA SS M KS E SA + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MINING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCUTTING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CARPENTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DETAILSTONE */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MASONRY */, + { { 0, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 } } /* ANIMALTRAIN */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 } } /* ANIMALCARE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_FISH */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISSECT_VERMIN */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSFISH */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BUTCHER */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* TRAPPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TANNER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WEAVING */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BREWING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ALCHEMY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CLOTHESMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILLING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROCESSPLANTS */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEESEMAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILK */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COOK */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLANT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* HERBALISM */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* FISH */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SMELT */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* EXTRACT_STRAND */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_WEAPON */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FORGE_FURNITURE */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CUTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ENCRUSTGEM */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WOODCRAFT */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STONECRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* METALCRAFT */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLASSMAKER */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* LEATHERWORK */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BONECARVE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* AXE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWORD */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DAGGER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MACE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* HAMMER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPEAR */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CROSSBOW */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SHIELD */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* ARMOR */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SIEGECRAFT */, + { { 1, 0, 1, 1, 0, 0 }, { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SIEGEOPERATE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOWYER */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* PIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WHIP */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BOW */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BLOWGUN */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* THROW */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MECHANICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAGIC_NATURE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SNEAK */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DESIGNBUILDING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0 } } /* DRESS_WOUNDS */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* DIAGNOSE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SURGERY */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SET_BONE */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SUTURE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* CRUTCH_WALK */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WOOD_BURNING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LYE_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SOAP_MAKING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POTASH_MAKING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DYER */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPERATE_PUMP */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SWIMMING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PERSUASION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* NEGOTIATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1 } } /* JUDGING_INTENT */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 } } /* APPRAISAL */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 } } /* ORGANIZATION */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* RECORD_KEEPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 } } /* LYING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* INTIMIDATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* CONVERSATION */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 } } /* COMEDY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* FLATTERY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 } } /* CONSOLE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* PACIFY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* TRACKING */, + { { 0, 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } /* KNOWLEDGE_ACQUISITION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } } /* CONCENTRATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DISCIPLINE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SITUATIONAL_AWARENESS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* WRITING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PROSE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* POETRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* READING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SPEAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* COORDINATION */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BALANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* LEADERSHIP */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 } } /* TEACHING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MELEE_COMBAT */, + { { 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* RANGED_COMBAT */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WRESTLING */, + { { 1, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* BITE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GRASP_STRIKE */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* STANCE_STRIKE */, + { { 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* DODGING */, + { { 1, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* MISC_WEAPON */, + { { 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* KNAPPING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MILITARY_TACTICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SHEARING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* SPINNING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* POTTERY */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* GLAZING */, + { { 1, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PRESSING */, + { { 1, 1, 0, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BEEKEEPING */, + { { 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 } } /* WAX_WORKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CLIMBING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GELD */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* DANCE */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MAKE_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* SING_MUSIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_KEYBOARD_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_STRINGED_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_WIND_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PLAY_PERCUSSION_INSTRUMENT */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CRITICAL_THINKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* LOGIC */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* MATHEMATICS */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* ASTRONOMY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* CHEMISTRY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* GEOGRAPHY */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* OPTICS_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* FLUID_ENGINEER */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* PAPERMAKING */, + { { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } /* BOOKBINDING */ +}; + +static void enable_plugin(color_ostream &out) +{ + if (!config.isValid()) + { + config = World::AddPersistentData("labormanager/2.0/config"); + config.ival(0) = 0; + } + + setOptionEnabled(CF_ENABLED, true); + enable_labormanager = true; + out << "Enabling the plugin." << endl; + + cleanup_state(); + init_state(); +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + // initialize labor infos table from default table + if (ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) + return CR_FAILURE; + + // Fill the command list with your commands. + commands.push_back(PluginCommand( + "labormanager", "Automatically manage dwarf labors.", + labormanager, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " labormanager enable\n" + " labormanager disable\n" + " Enables or disables the plugin.\n" + " labormanager max \n" + " Set max number of dwarves assigned to a labor.\n" + " labormanager max none\n" + " Unrestrict the number of dwarves assigned to a labor.\n" + " labormanager priority \n" + " Change the assignment priority of a labor (default is 100)\n" + " labormanager reset \n" + " Return a labor to the default handling.\n" + " labormanager reset-all\n" + " Return all labors to the default handling.\n" + " labormanager list\n" + " List current status of all labors.\n" + " labormanager status\n" + " Show basic status information.\n" + "Function:\n" + " When enabled, labormanager periodically checks your dwarves and enables or\n" + " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" + " Warning: labormanager will override any manual changes you make to labors\n" + " while it is enabled. Do not try to run both autolabor and labormanager at\n" + " the same time.\n" + )); + + generate_labor_to_skill_map(); + + labor_mapper = new JobLaborMapper(); + + init_state(); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + cleanup_state(); + + delete labor_mapper; + + return CR_OK; +} + +class AutoLaborManager { + color_ostream& out; + +public: + AutoLaborManager(color_ostream& o) : out(o) + { + dwarf_info.clear(); + } + + ~AutoLaborManager() + { + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + delete (*d); + } + } + + dwarf_info_t* add_dwarf(df::unit* u) + { + dwarf_info_t* dwarf = new dwarf_info_t(u); + dwarf_info.push_back(dwarf); + return dwarf; + } + + +private: + bool has_butchers; + bool has_fishery; + bool trader_requested; + + int dig_count; + int tree_count; + int plant_count; + int detail_count; + + bool labors_changed; + + int tool_count[TOOLS_MAX]; + int tool_in_use[TOOLS_MAX]; + + int cnt_recover_wounded; + int cnt_diagnosis; + int cnt_immobilize; + int cnt_dressing; + int cnt_cleaning; + int cnt_surgery; + int cnt_suture; + int cnt_setting; + int cnt_traction; + int cnt_crutch; + + int need_food_water; + + int priority_food; + + std::map labor_needed; + std::map labor_in_use; + std::map labor_outside; + std::vector dwarf_info; + std::list available_dwarfs; + std::list busy_dwarfs; + +private: + void set_labor(dwarf_info_t* dwarf, df::unit_labor labor, bool value) + { + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + bool old = dwarf->dwarf->status.labors[labor]; + dwarf->dwarf->status.labors[labor] = value; + if (old != value) + { + labors_changed = true; + + tools_enum tool = default_labor_infos[labor].tool; + if (tool != TOOL_NONE) + tool_in_use[tool] += value ? 1 : -1; + } + } + } + + void process_job(df::job* j) + { + if (j->flags.bits.suspend || j->flags.bits.item_lost) + return; + + int worker = -1; + int bld = -1; + + for (auto ref : j->general_refs) + { + if (ref->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)ref)->unit_id; + if (ref->getType() == df::general_ref_type::BUILDING_HOLDER) + bld = ((df::general_ref_building_holderst *)ref)->building_id; + } + + if (bld != -1) + { + df::building* b = binsearch_in_vector(world->buildings.all, bld); + + // check if this job is the first nonsuspended job on this building; if not, ignore it + // (except for farms and trade depots) + + if (b->getType() != df::building_type::FarmPlot && + b->getType() != df::building_type::TradeDepot) + { + int fjid = -1; + for (size_t jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + if (fjid != j->id) + return; + } + } + + df::unit_labor labor = labor_mapper->find_job_labor(j); + + if (labor != df::unit_labor::NONE) + { + labor_needed[labor]++; + if (worker == -1) + { + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } + } + else { + labor_infos[labor].mark_assigned(); + labor_in_use[labor]++; + } + + } + } + + void scan_buildings() + { + has_butchers = false; + has_fishery = false; + trader_requested = false; + labors_changed = false; + + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) + { + df::building *build = *b; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = depot->trade_flags.bits.trader_requested; + + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + + } + } + } + + void count_map_designations() + { + dig_count = 0; + tree_count = 0; + plant_count = 0; + detail_count = 0; + + for (size_t i = 0; i < world->map.map_blocks.size(); ++i) + { + df::map_block* bl = world->map.map_blocks[i]; + + if (!bl->flags.bits.designated) + continue; + + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + if (bl->designation[x][y].bits.hidden) + { + df::coord p = bl->map_pos; + if (! Maps::isTileVisible(p.x, p.y, p.z-1)) + continue; + } + + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; + if (dig != df::enums::tile_dig_designation::No) + { + df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_material ttm = ENUM_ATTR(tiletype, material, tt); + df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); + if (ttm == df::enums::tiletype_material::TREE) + tree_count++; + else if (tts == df::enums::tiletype_shape::SHRUB) + plant_count++; + else + dig_count++; + } + if (bl->designation[x][y].bits.smooth != 0) + detail_count++; + } + } + + if (print_debug) + out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); + + } + + void count_tools() + { + for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + { + tool_count[e] = 0; + tool_in_use[e] = 0; + } + + priority_food = 0; + + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); +#undef F + + auto& v = world->items.all; + for (auto i = v.begin(); i != v.end(); i++) + { + df::item* item = *i; + + if (item->flags.bits.dump) + labor_needed[df::unit_labor::HAUL_REFUSE]++; + + if (item->flags.whole & bad_flags.whole) + continue; + + df::item_type t = item->getType(); + + if (item->materialRots() && t != df::item_type::CORPSEPIECE && t != df::item_type::CORPSE && item->getRotTimer() > 1) + priority_food++; + + if (!item->isWeapon()) + continue; + + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill weaponsk2 = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + tool_count[TOOL_AXE]++; + else if (weaponsk == df::job_skill::MINING) + tool_count[TOOL_PICK]++; + else if (weaponsk2 == df::job_skill::CROSSBOW) + tool_count[TOOL_CROSSBOW]++; + } + + } + + void collect_job_list() + { + for (df::job_list_link* jll = world->jobs.list.next; jll; jll = jll->next) + { + df::job* j = jll->item; + if (!j) + continue; + process_job(j); + } + + for (auto jp = world->jobs.postings.begin(); jp != world->jobs.postings.end(); jp++) + { + if ((*jp)->flags.bits.dead) + continue; + + process_job((*jp)->job); + } + + } + + void collect_dwarf_list() + { + state_count.clear(); + state_count.resize(NUM_STATE); + + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + df::unit* cre = *u; + + if (Units::isCitizen(cre)) + { + dwarf_info_t* dwarf = add_dwarf(cre); + + df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); + for (size_t i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + if (trader_requested) + dwarf->clear_all = true; + } + + } + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (size_t i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + + bool p1 = act->unit_actor == dwarf->dwarf; + bool p2 = act->unit_noble == dwarf->dwarf; + + if (p1 || p2) + { + df::unit* other = p1 ? act->unit_noble : act->unit_actor; + if (other && !(other->flags1.bits.dead || + (other->job.current_job && + (other->job.current_job->job_type == df::job_type::Sleep || + other->job.current_job->job_type == df::job_type::Rest)) || + ENUM_ATTR(profession, military, other->profession))) { + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + else + { + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, but with someone who can't make the meeting.\n", dwarf->dwarf->name.first_name.c_str()); + } + } + } + + // check to see if dwarf has minor children + + for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2) + { + if ((*u2)->relationship_ids[df::unit_relationship_type::Mother] == dwarf->dwarf->id && + !(*u2)->flags1.bits.dead && + ((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY)) + { + dwarf->has_children = true; + if (print_debug) + out.print("Dwarf %s has minor children\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + + // check if dwarf has an axe, pick, or crossbow + + for (size_t j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + dwarf->armed = true; + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_tool[TOOL_AXE] = true; + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_tool[TOOL_PICK] = true; + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_tool[TOOL_CROSSBOW] = true; + } + } + } + + // Find the activity state for each dwarf + + bool is_migrant = false; + dwarf_state state = OTHER; + + for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant) + is_migrant = true; + } + + if (dwarf->dwarf->social_activities.size() > 0) + { + if (print_debug) + out.print("Dwarf %s is engaged in a social activity. Info only.\n", dwarf->dwarf->name.first_name.c_str()); + } + + if (dwarf->dwarf->profession == profession::BABY || + dwarf->dwarf->profession == profession::CHILD || + dwarf->dwarf->profession == profession::DRUNK) + { + state = CHILD; + } + + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + state = MILITARY; + + else if (dwarf->dwarf->burrows.size() > 0) + state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + + else if (dwarf->dwarf->job.current_job == NULL) + { + if (is_migrant || dwarf->dwarf->flags1.bits.chained || dwarf->dwarf->flags1.bits.caged) + { + state = OTHER; + dwarf->clear_all = true; + } + else if (dwarf->dwarf->status2.limbs_grasp_count == 0) + { + state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); + } + else + { + state = IDLE; + } + } + else + { + df::job_type job = dwarf->dwarf->job.current_job->job_type; + if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) + state = dwarf_states[job]; + else + { + out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + debug_pause(); + state = OTHER; + } + if (state == BUSY) + { + df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); + + dwarf->using_labor = labor; + + if (labor != df::unit_labor::NONE) + { + labor_infos[labor].busy_dwarfs++; + if (default_labor_infos[labor].tool != TOOL_NONE) + { + tool_in_use[default_labor_infos[labor].tool]++; + } + } + } + } + + dwarf->state = state; + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (dwarf->dwarf->status.labors[l]) + if (state == IDLE) + labor_infos[l].idle_dwarfs++; + } + + + if (print_debug) + out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); + + state_count[dwarf->state]++; + + // determine if dwarf has medical needs + if (dwarf->dwarf->health && !( + // on-duty military will not necessarily break to get minor injuries attended + ENUM_ATTR(profession, military, dwarf->dwarf->profession) || + // babies cannot currently receive health care even if they need it + dwarf->dwarf->profession == profession::BABY) + ) + { + if (dwarf->dwarf->health->flags.bits.needs_recovery) + cnt_recover_wounded++; + if (dwarf->dwarf->health->flags.bits.rq_diagnosis) + cnt_diagnosis++; + if (dwarf->dwarf->health->flags.bits.rq_immobilize) + cnt_immobilize++; + if (dwarf->dwarf->health->flags.bits.rq_dressing) + cnt_dressing++; + if (dwarf->dwarf->health->flags.bits.rq_cleaning) + cnt_cleaning++; + if (dwarf->dwarf->health->flags.bits.rq_surgery) + cnt_surgery++; + if (dwarf->dwarf->health->flags.bits.rq_suture) + cnt_suture++; + if (dwarf->dwarf->health->flags.bits.rq_setting) + cnt_setting++; + if (dwarf->dwarf->health->flags.bits.rq_traction) + cnt_traction++; + if (dwarf->dwarf->health->flags.bits.rq_crutch) + cnt_crutch++; + } + + if (dwarf->dwarf->counters2.hunger_timer > 60000 || dwarf->dwarf->counters2.thirst_timer > 40000) + need_food_water++; + + // find dwarf's highest effective skill + + int high_skill = 0; + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == df::unit_labor::NONE) + continue; + + df::job_skill skill = labor_to_skill[labor]; + if (skill != df::job_skill::NONE) + { + int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); + high_skill = std::max(high_skill, skill_level); + } + } + + dwarf->high_skill = high_skill; + + + // clear labors of dwarfs with clear_all set + + if (dwarf->clear_all) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + set_labor(dwarf, labor, false); + } + } + else { + if (state == IDLE) + available_dwarfs.push_back(dwarf); + + if (state == BUSY) + busy_dwarfs.push_back(dwarf); + } + } + + } + } + + void release_dwarf_list() + { + while (!dwarf_info.empty()) { + auto d = dwarf_info.begin(); + delete *d; + dwarf_info.erase(d); + } + available_dwarfs.clear(); + busy_dwarfs.clear(); + } + + int score_labor(dwarf_info_t* d, df::unit_labor labor) + { + int skill_level = 0; + int xp = 0; + int attr_weight = 0; + + if (labor != df::unit_labor::NONE) + { + df::job_skill skill = labor_to_skill[labor]; + if (skill != df::job_skill::NONE) + { + skill_level = Units::getEffectiveSkill(d->dwarf, skill); + xp = Units::getExperience(d->dwarf, skill, false); + + for (int pa = 0; pa < 6; pa++) + attr_weight += (skill_attr_weights[skill].phys_attr_weights[pa]) * (d->dwarf->body.physical_attrs[pa].value - 1000); + + for (int ma = 0; ma < 13; ma++) + attr_weight += (skill_attr_weights[skill].mental_attr_weights[ma]) * (d->dwarf->status.current_soul->mental_attrs[ma].value - 1000); + } + + } + + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10) + attr_weight; + + if (labor != df::unit_labor::NONE) + { + if (d->dwarf->status.labors[labor]) + { + if (labor == df::unit_labor::OPERATE_PUMP) + score += 50000; + else + score += 25000; + } + if (default_labor_infos[labor].tool != TOOL_NONE && + d->has_tool[default_labor_infos[labor].tool]) + score += 10000000; + if (d->has_children && labor_outside[labor]) + score -= 15000; + if (d->armed && labor_outside[labor]) + score += 5000; + } + + // Favor/disfavor RECOVER_WOUNDED based on ALTRUISM personality facet + + if (labor == df::unit_labor::RECOVER_WOUNDED) + { + int altruism = d->dwarf->status.current_soul->personality.traits[df::personality_facet_type::ALTRUISM]; + if (altruism >= 61) + score += 5000; + else if (altruism <= 24) + score -= 50000; + } + // This should reweight assigning CUTWOOD jobs based on a citizen's ethic toward killing plants + + if (labor == df::unit_labor::CUTWOOD) + { + if (auto culture = df::cultural_identity::find(d->dwarf->cultural_identity)) + { + auto ethics = culture->ethic[df::ethic_type::KILL_PLANT]; + if (ethics != df::ethic_response::NOT_APPLICABLE && ethics != df::ethic_response::REQUIRED) + score += 10000 * (df::ethic_response::ACCEPTABLE - ethics); + } + } + + score -= Units::computeMovementSpeed(d->dwarf); + + return score; + } + +public: + void process() + { + if (*df::global::process_dig || *df::global::process_jobs) + return; + + release_dwarf_list(); + + dig_count = tree_count = plant_count = detail_count = 0; + cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = + cnt_setting = cnt_traction = cnt_crutch = 0; + need_food_water = 0; + + labor_needed.clear(); + + for (int e = 0; e < TOOLS_MAX; e++) + tool_count[e] = 0; + + trader_requested = false; + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + labor_infos[l].active_dwarfs = labor_infos[l].busy_dwarfs = labor_infos[l].idle_dwarfs = 0; + } + + // scan for specific buildings of interest + + scan_buildings(); + + // count number of squares designated for dig, wood cutting, detailing, and plant harvesting + + count_map_designations(); + + // collect current job list + + collect_job_list(); + + // count number of picks and axes available for use + + count_tools(); + + // collect list of dwarfs + + collect_dwarf_list(); + + // add job entries for designation-related jobs + + labor_needed[df::unit_labor::MINE] += dig_count; + labor_needed[df::unit_labor::CUTWOOD] += tree_count; + labor_needed[df::unit_labor::DETAIL] += detail_count; + labor_needed[df::unit_labor::HERBALIST] += plant_count; + + // add job entries for health care + + labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; + labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; + labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; + labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; + labor_needed[df::unit_labor::SURGERY] += cnt_surgery; + labor_needed[df::unit_labor::SUTURING] += cnt_suture; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_crutch; + + labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; + + // add entries for hauling jobs + + labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; + labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[4]; + labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; + labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; + labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; + labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; + labor_needed[df::unit_labor::HAUL_ANIMALS] += world->stockpile.num_jobs[9]; + + labor_needed[df::unit_labor::HAUL_STONE] += (world->stockpile.num_jobs[1] >= world->stockpile.num_haulers[1]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_WOOD] += (world->stockpile.num_jobs[2] >= world->stockpile.num_haulers[2]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ITEM] += (world->stockpile.num_jobs[3] >= world->stockpile.num_haulers[3]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_BODY] += (world->stockpile.num_jobs[5] >= world->stockpile.num_haulers[5]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FOOD] += (world->stockpile.num_jobs[6] >= world->stockpile.num_haulers[6]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_REFUSE] += (world->stockpile.num_jobs[7] >= world->stockpile.num_haulers[7]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_FURNITURE] += (world->stockpile.num_jobs[8] >= world->stockpile.num_haulers[8]) ? 1 : 0; + labor_needed[df::unit_labor::HAUL_ANIMALS] += (world->stockpile.num_jobs[9] >= world->stockpile.num_haulers[9]) ? 1 : 0; + + int binjobs = world->stockpile.num_jobs[4] + ((world->stockpile.num_jobs[4] >= world->stockpile.num_haulers[4]) ? 1 : 0); + + labor_needed[df::unit_labor::HAUL_ITEM] += binjobs; + labor_needed[df::unit_labor::HAUL_FOOD] += priority_food; + + // add entries for vehicle hauling + + for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) + if ((*v)->route_id != -1) + labor_needed[df::unit_labor::HANDLE_VEHICLES]++; + + // add fishing & hunting + + labor_needed[df::unit_labor::FISH] = + (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) ? 1 : 0; + + labor_needed[df::unit_labor::HUNT] = + (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) ? 1 : 0; + + /* add animal trainers */ + for (auto a = df::global::ui->equipment.training_assignments.begin(); + a != df::global::ui->equipment.training_assignments.end(); + a++) + { + labor_needed[df::unit_labor::ANIMALTRAIN]++; + // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. + } + + /* set requirements to zero for labors with currently idle dwarfs, and remove from requirement dwarfs actually working */ + + FOR_ENUM_ITEMS(unit_labor, l) { + if (l == df::unit_labor::NONE) + continue; + + int before = labor_needed[l]; + + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + + if (default_labor_infos[l].tool != TOOL_NONE) + labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); + + if (print_debug && before != labor_needed[l]) + out.print("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); + + } + + /* assign food haulers for rotting food items */ + + if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) + priority_food = 1; + + if (print_debug) + out.print("priority food count = %d\n", priority_food); + + while (!available_dwarfs.empty() && priority_food > 0) + { + std::list::iterator bestdwarf = available_dwarfs.begin(); + + int best_score = INT_MIN; + + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + + int score = score_labor(d, df::unit_labor::HAUL_FOOD); + + if (score > best_score) + { + bestdwarf = k; + best_score = score; + } + } + + if (best_score > INT_MIN) + { + if (print_debug) + out.print("LABORMANAGER: assign \"%s\" labor %s score=%d (priority food)\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, df::unit_labor::HAUL_FOOD).c_str(), best_score); + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + set_labor(*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); + } + + available_dwarfs.erase(bestdwarf); + priority_food--; + } + else + break; + + } + + if (print_debug) + { + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) + { + out.print("labor_needed [%s] = %d, busy = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_infos[i->first].busy_dwarfs, labor_outside[i->first], labor_infos[i->first].idle_dwarfs); + } + } + + std::map base_priority; + priority_queue> pq; + priority_queue> pq2; + + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) + { + df::unit_labor l = i->first; + if (l == df::unit_labor::NONE) + continue; + + if (labor_infos[l].maximum_dwarfs() > 0 && + i->second > labor_infos[l].maximum_dwarfs()) + i->second = labor_infos[l].maximum_dwarfs(); + + int priority = labor_infos[l].priority(); + + priority += labor_infos[l].time_since_last_assigned() / 12; + priority -= labor_infos[l].busy_dwarfs; + + base_priority[l] = priority; + + if (i->second > 0) + { + pq.push(make_pair(priority, l)); + } + } + + if (print_debug) + out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); + + std::map to_assign; + + to_assign.clear(); + + size_t av = available_dwarfs.size(); + + while (!pq.empty() && av > 0) + { + df::unit_labor labor = pq.top().second; + int priority = pq.top().first; + to_assign[labor]++; + pq.pop(); + av--; + + if (print_debug) + out.print("Will assign: %s priority %d (%d)\n", ENUM_KEY_STR(unit_labor, labor).c_str(), priority, to_assign[labor]); + + if (--labor_needed[labor] > 0) + { + priority -= 10; + pq2.push(make_pair(priority, labor)); + } + + if (pq.empty()) + while (!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); + } + } + + while (!pq2.empty()) + { + pq.push(pq2.top()); + pq2.pop(); + } + + int canary = (1 << df::unit_labor::HAUL_STONE) | + (1 << df::unit_labor::HAUL_WOOD) | + (1 << df::unit_labor::HAUL_BODY) | + (1 << df::unit_labor::HAUL_FOOD) | + (1 << df::unit_labor::HAUL_REFUSE) | + (1 << df::unit_labor::HAUL_ITEM) | + (1 << df::unit_labor::HAUL_FURNITURE) | + (1 << df::unit_labor::HAUL_ANIMALS); + + while (!available_dwarfs.empty()) + { + std::list::iterator bestdwarf = available_dwarfs.begin(); + + int best_score = INT_MIN; + df::unit_labor best_labor = df::unit_labor::NONE; + + for (auto j = to_assign.begin(); j != to_assign.end(); j++) + { + if (j->second <= 0) + continue; + + df::unit_labor labor = j->first; + + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int score = score_labor(d, labor); + if (score > best_score) + { + bestdwarf = k; + best_score = score; + best_labor = labor; + } + } + } + + if (best_labor == df::unit_labor::NONE) + break; + + if (print_debug) + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + tools_enum t = default_labor_infos[l].tool; + + if (l == best_labor && (t == TOOL_NONE || tool_in_use[t] < tool_count[t])) + { + set_labor(*bestdwarf, l, true); + if (t != TOOL_NONE && !((*bestdwarf)->has_tool[t])) + { + df::job_type j; + j = df::job_type::NONE; + + if ((*bestdwarf)->dwarf->job.current_job) + j = (*bestdwarf)->dwarf->job.current_job->job_type; + + if (print_debug) + out.print("LABORMANAGER: asking %s to pick up tools, current job %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(job_type, j).c_str()); + + (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + labors_changed = true; + } + } + else if ((*bestdwarf)->state == IDLE) + { + set_labor(*bestdwarf, l, false); + } + } + + if (best_labor == df::unit_labor::HAUL_FOOD && priority_food > 0) + priority_food--; + + if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMALS) + canary &= ~(1 << best_labor); + + if (best_labor != df::unit_labor::NONE) + { + labor_infos[best_labor].active_dwarfs++; + to_assign[best_labor]--; + } + + busy_dwarfs.push_back(*bestdwarf); + available_dwarfs.erase(bestdwarf); + } + + for (auto d = busy_dwarfs.begin(); d != busy_dwarfs.end(); d++) + { + int current_score = score_labor(*d, (*d)->using_labor); + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (l == (*d)->using_labor) + continue; + if (labor_needed[l] <= 0) + continue; + + int score = score_labor(*d, l); + + if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) + score += 1000000; + + if (score > current_score) + { + tools_enum t = default_labor_infos[l].tool; + if (t == TOOL_NONE || (*d)->has_tool[t]) + { + set_labor(*d, l, true); + } + if ((*d)->using_labor != df::unit_labor::NONE && + (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && + default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) + set_labor(*d, (*d)->using_labor, false); + } + } + } + + + dwarf_info_t* canary_dwarf = 0; + + for (auto di = busy_dwarfs.begin(); di != busy_dwarfs.end(); di++) + if (!(*di)->clear_all) + { + canary_dwarf = *di; + break; + } + + if (canary_dwarf) + { + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && + canary & (1 << l)) + set_labor(canary_dwarf, l, true); + } + + /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ + + set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); + + /* Set HAUL_WATER so we can detect ponds that need to be filled ponds. */ + + set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true); + + if (print_debug) + out.print("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); + } + else + { + if (print_debug) + out.print("No dwarf available to set as the hauling canary!\n"); + } + + /* Assign any leftover dwarfs to "standard" labors */ + + if (print_debug) + out.print("After assignment, %d dwarfs left over\n", available_dwarfs.size()); + + for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) + { + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + set_labor(*d, l, + (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) || + l == df::unit_labor::CLEAN || + l == df::unit_labor::HAUL_WATER || + l == df::unit_labor::REMOVE_CONSTRUCTION || + l == df::unit_labor::PULL_LEVER || + l == df::unit_labor::HAUL_TRADE); + } + } + + /* check for dwarfs assigned no labors and assign them the bucket list if there are */ + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + if ((*d)->state == CHILD) + continue; + + bool any = false; + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if ((*d)->dwarf->status.labors[l]) + { + any = true; + break; + } + } + + set_labor(*d, df::unit_labor::PULL_LEVER, true); + + if (any) continue; + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + if (to_assign[l] > 0 || l == df::unit_labor::CLEAN) + set_labor(*d, l, true); + } + } + + + /* set reequip on any dwarfs who are carrying tools needed by others */ + + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + if ((*d)->dwarf->job.current_job && (*d)->dwarf->job.current_job->job_type == df::job_type::PickupEquipment) + continue; + + if ((*d)->dwarf->military.pickup_flags.bits.update) + continue; + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + tools_enum t = default_labor_infos[l].tool; + if (t == TOOL_NONE) + continue; + + bool has_tool = (*d)->has_tool[t]; + bool needs_tool = (*d)->dwarf->status.labors[l]; + + if ((needs_tool && !has_tool) || + (has_tool && !needs_tool && tool_in_use[t] >= tool_count[t])) + { + df::job_type j = df::job_type::NONE; + + if ((*d)->dwarf->job.current_job) + j = (*d)->dwarf->job.current_job->job_type; + + if (print_debug) + out.print("LABORMANAGER: asking %s to %s tools, current job %s, %d %d \n", (*d)->dwarf->name.first_name.c_str(), (has_tool) ? "drop" : "pick up", ENUM_KEY_STR(job_type, j).c_str(), has_tool, needs_tool); + + (*d)->dwarf->military.pickup_flags.bits.update = true; + labors_changed = true; + + if (needs_tool) + tool_in_use[t]++; + } + } + } + + release_dwarf_list(); + + if (labors_changed) + { + *df::global::process_dig = true; + *df::global::process_jobs = true; + } + + if (print_debug) { + *df::global::pause_state = true; + } + + print_debug = 0; + + } + +}; + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + cleanup_state(); + init_state(); + break; + case SC_MAP_UNLOADED: + cleanup_state(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + // static int step_count = 0; + // check run conditions + if (!initialized || !world || !world->map.block_index || !enable_labormanager) + { + // give up if we shouldn't be running' + return CR_OK; + } + + // if (++step_count < 60) + // return CR_OK; + + if (*df::global::process_jobs) + return CR_OK; + + // step_count = 0; + + debug_stream = &out; + AutoLaborManager alm(out); + alm.process(); + + return CR_OK; +} + +void print_labor(df::unit_labor labor, color_ostream &out) +{ + string labor_name = ENUM_KEY_STR(unit_labor, labor); + out << labor_name << ": "; + for (int i = 0; i < 20 - (int)labor_name.length(); i++) + out << ' '; + out << "priority " << labor_infos[labor].priority() + << ", maximum " << labor_infos[labor].maximum_dwarfs() + << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs (" + << labor_infos[labor].busy_dwarfs << " busy, " + << labor_infos[labor].idle_dwarfs << " idle)" + << endl; +} + +df::unit_labor lookup_labor_by_name(std::string& name) +{ + df::unit_labor labor = df::unit_labor::NONE; + + FOR_ENUM_ITEMS(unit_labor, test_labor) + { + if (name == ENUM_KEY_STR(unit_labor, test_labor)) + labor = test_labor; + } + + return labor; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("World is not loaded: please load a fort first.\n"); + return CR_FAILURE; + } + + if (enable && !enable_labormanager) + { + enable_plugin(out); + } + else if (!enable && enable_labormanager) + { + enable_labormanager = false; + setOptionEnabled(CF_ENABLED, false); + + out << "LaborManager is disabled." << endl; + } + + return CR_OK; +} + +command_result labormanager(color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("World is not loaded: please load a game first.\n"); + return CR_FAILURE; + } + + if (parameters.size() == 1 && + (parameters[0] == "enable" || parameters[0] == "disable")) + { + bool enable = (parameters[0] == "enable"); + return plugin_enable(out, enable); + } + else if (parameters.size() == 3 && + (parameters[0] == "max" || parameters[0] == "priority")) + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + df::unit_labor labor = lookup_labor_by_name(parameters[1]); + + if (labor == df::unit_labor::NONE) + { + out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + + int v; + + if (parameters[2] == "none") + v = 0; + else + v = atoi(parameters[2].c_str()); + + if (parameters[0] == "max") + labor_infos[labor].set_maximum_dwarfs(v); + else if (parameters[0] == "priority") + labor_infos[labor].set_priority(v); + + print_labor(labor, out); + return CR_OK; + } + else if (parameters.size() == 2 && parameters[0] == "reset") + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + df::unit_labor labor = lookup_labor_by_name(parameters[1]); + + if (labor == df::unit_labor::NONE) + { + out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + reset_labor(labor); + print_labor(labor, out); + return CR_OK; + } + else if (parameters.size() == 1 && (parameters[0] == "allow-fishing" || parameters[0] == "forbid-fishing")) + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + setOptionEnabled(CF_ALLOW_FISHING, (parameters[0] == "allow-fishing")); + return CR_OK; + } + else if (parameters.size() == 1 && (parameters[0] == "allow-hunting" || parameters[0] == "forbid-hunting")) + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + setOptionEnabled(CF_ALLOW_HUNTING, (parameters[0] == "allow-hunting")); + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "reset-all") + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + for (size_t i = 0; i < labor_infos.size(); i++) + { + reset_labor((df::unit_labor) i); + } + out << "All labors reset." << endl; + return CR_OK; + } + else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + bool need_comma = 0; + for (int i = 0; i < NUM_STATE; i++) + { + if (state_count[i] == 0) + continue; + if (need_comma) + out << ", "; + out << state_count[i] << ' ' << state_names[i]; + need_comma = 1; + } + out << endl; + + if (parameters[0] == "list") + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + print_labor(labor, out); + } + } + + return CR_OK; + } + else if (parameters.size() == 2 && parameters[0] == "pause-on-error") + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + pause_on_error = parameters[1] == "yes" || parameters[1] == "true"; + + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "debug") + { + if (!enable_labormanager) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + print_debug = 1; + + return CR_OK; + } + else + { + out.print("Automatically assigns labors to dwarves.\n" + "Activate with 'labormanager enable', deactivate with 'labormanager disable'.\n" + "Current state: %s.\n", enable_labormanager ? "enabled" : "disabled"); + + return CR_OK; + } +} + diff --git a/plugins/labormanager/labormanager.h b/plugins/labormanager/labormanager.h new file mode 100644 index 000000000..c958900b1 --- /dev/null +++ b/plugins/labormanager/labormanager.h @@ -0,0 +1,4 @@ +#pragma once + +void debug(const char* fmt, ...); +void debug_pause(); diff --git a/plugins/lair.cpp b/plugins/lair.cpp index 96a178485..6fb167988 100644 --- a/plugins/lair.cpp +++ b/plugins/lair.cpp @@ -10,6 +10,9 @@ #include "modules/World.h" #include "modules/MapCache.h" #include "modules/Gui.h" + +#include "df/world.h" + using namespace DFHack; using namespace df::enums; diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 1953728a4..f0dcf1ce3 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -20,29 +20,34 @@ // - grab the code from digcircle to get a circle brush - could be nice when painting with obsidian // - maybe store the last parameters in a file to make them persistent after dfhack is closed? +#include #include -#include -#include #include +#include #include -#include #include -#include +#include +#include using std::vector; using std::string; using std::endl; using std::set; -#include "Core.h" #include "Console.h" +#include "Core.h" #include "Export.h" +#include "LuaTools.h" #include "PluginManager.h" -#include "modules/Maps.h" -#include "modules/Gui.h" #include "TileTypes.h" + +#include "modules/Gui.h" #include "modules/MapCache.h" -#include "LuaTools.h" +#include "modules/Maps.h" + +#include "df/world.h" + #include "Brushes.h" + using namespace MapExtras; using namespace DFHack; using namespace df::enums; @@ -266,7 +271,7 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "range" || command == "r") { - int width, height, z_levels; + int width = 1, height = 1, z_levels = 1; command_result res = parseRectangle(out, commands, 1, commands.size(), width, height, z_levels); if (res != CR_OK) @@ -403,34 +408,32 @@ command_result df_liquids_execute(color_ostream &out) command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, df::coord cursor) { // create brush type depending on old parameters - Brush *brush; + std::unique_ptr brush; switch (cur_mode.brush) { case B_POINT: - brush = new RectangleBrush(1,1,1,0,0,0); + brush.reset(new RectangleBrush(1,1,1,0,0,0)); break; case B_RANGE: - brush = new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0); + brush.reset(new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0)); break; case B_BLOCK: - brush = new BlockBrush(); + brush.reset(new BlockBrush()); break; case B_COLUMN: - brush = new ColumnBrush(); + brush.reset(new ColumnBrush()); break; case B_FLOOD: - brush = new FloodBrush(&Core::getInstance()); + brush.reset(new FloodBrush(&Core::getInstance())); break; default: // this should never happen! out << "Old brushtype is invalid! Resetting to point brush.\n"; cur_mode.brush = B_POINT; - brush = new RectangleBrush(1,1,1,0,0,0); + brush.reset(new RectangleBrush(1,1,1,0,0,0)); } - std::auto_ptr brush_ref(brush); - if (!Maps::IsValid()) { out << "Can't see any DF map loaded." << endl; diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 5c96b4594..0abe07b9d 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -16,7 +16,7 @@ public: UIColor color; ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) : - elem(elem), text(text), selected(false), keywords(keywords), color(color) + elem(elem), text(text), keywords(keywords), selected(false), color(color) { } }; @@ -70,17 +70,17 @@ public: display_max_rows = gps->dimy - 4 - bottom_margin; } - void add(ListEntry &entry) + void add(const ListEntry &entry) { list.push_back(entry); - if (entry.text.length() > max_item_width) + if (entry.text.length() > size_t(max_item_width)) max_item_width = entry.text.length(); } void add(const string &text, const T &elem) { list.push_back(ListEntry(text, elem)); - if (text.length() > max_item_width) + if (text.length() > size_t(max_item_width)) max_item_width = text.length(); } @@ -110,10 +110,10 @@ public: paint_text(COLOR_TITLE, left_margin, y, title); int last_index_able_to_display = display_start_offset + display_max_rows; - for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) + for (int i = display_start_offset; size_t(i) < display_list.size() && i < last_index_able_to_display; i++) { ++y; - UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; + UIColor fg_color = (is_selected_column && display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color; UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; string item_label = display_list[i]->text; @@ -317,6 +317,18 @@ public: return results[0]; } + bool hasSelection() + { + for (auto item : list) + { + if (item.selected) + { + return true; + } + } + return false; + } + void clearSelection() { for_each_(list, clear_fn); @@ -324,8 +336,7 @@ public: void selectItem(const T elem) { - int i = 0; - for (; i < display_list.size(); i++) + for (size_t i = 0; i < display_list.size(); i++) { if (display_list[i]->elem == elem) { @@ -435,7 +446,7 @@ public: gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) { int new_index = display_start_offset + gps->mouse_y - 3; - if (new_index < display_list.size()) + if (size_t(new_index) < display_list.size()) { setHighlight(new_index); feed_mouse_set_highlight = true; @@ -460,7 +471,7 @@ public: void setTitle(const string t) { title = t; - if (title.length() > max_item_width) + if (title.length() > size_t(max_item_width)) max_item_width = title.length(); } diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua new file mode 100644 index 000000000..98f24f487 --- /dev/null +++ b/plugins/lua/blueprint.lua @@ -0,0 +1,14 @@ +local _ENV = mkmodule('plugins.blueprint') + +--[[ + + Native functions: + + * dig(start, end, name) + * build(start, end, name) + * place(start, end, name) + * query(start, end, name) + +--]] + +return _ENV diff --git a/plugins/lua/dwarfmonitor.lua b/plugins/lua/dwarfmonitor.lua index 35a667bd4..7637c50f7 100644 --- a/plugins/lua/dwarfmonitor.lua +++ b/plugins/lua/dwarfmonitor.lua @@ -162,7 +162,7 @@ function load_config() end widgets = {} for _, opts in pairs(config.widgets) do - if type(opts) ~= 'table' then qerror('"widgets" is not an array') end + if type(opts) ~= 'table' then dmerror('"widgets" is not an array') end if not opts.type then dmerror('Widget missing type field') end local cls = _ENV['Widget_' .. opts.type] if not cls then diff --git a/plugins/lua/luasocket.lua b/plugins/lua/luasocket.lua index 1debb4dd9..bf1e919d4 100644 --- a/plugins/lua/luasocket.lua +++ b/plugins/lua/luasocket.lua @@ -47,7 +47,7 @@ function client:receive( pattern ) local pattern=pattern or "*l" local bytes=-1 - if type(pattern)== number then + if type(pattern)== 'number' then bytes=pattern end diff --git a/plugins/lua/pathable.lua b/plugins/lua/pathable.lua new file mode 100644 index 000000000..b5f5addfc --- /dev/null +++ b/plugins/lua/pathable.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.pathable') + +--[[ + +Native functions: (see Plugins.rst for details) + +- paintScreen(cursor[,skip_unrevealed]) + +]] + +return _ENV diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 7b038d34c..ca484d831 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -24,9 +24,9 @@ entry_ints = { trigger_number = 3, } -PageSize = 16 -FirstRow = 4 +FirstRow = 3 CenterCol = 38 +ExtraLines = 9 -- Populate the reaction and stockpile order lists. -- To be called whenever a world is loaded. @@ -140,12 +140,12 @@ function collect_orders() stockpile = stockpile, entry = entry, } - + found = true break end end - + if not found then print("Unmatched stockflow entry for stockpile #"..stockpile.stockpile_number..": "..entry.value.." ("..order_number..")") end @@ -174,7 +174,7 @@ function reaction_entry(reactions, job_type, values, name) -- We could warn about it; in any case, don't add it to the list. return end - + local order = df.manager_order:new() -- These defaults differ from the newly created order's. order:assign{ @@ -396,7 +396,7 @@ function collect_reactions() reaction_entry(result, job_types.MakeCheese) reaction_entry(result, job_types.MilkCreature) reaction_entry(result, job_types.ShearCreature) - reaction_entry(result, job_types.SpinThread) + reaction_entry(result, job_types.SpinThread, {material_category = {strand = true}}) reaction_entry(result, job_types.MakeLye) reaction_entry(result, job_types.ProcessPlants) reaction_entry(result, job_types.ProcessPlantsBag) @@ -416,13 +416,13 @@ function collect_reactions() end for _, reaction_id in ipairs(entity.entity_raw.workshops.permitted_reaction_id) do - local reaction = df.global.world.raws.reactions[reaction_id] + local reaction = df.global.world.raws.reactions.reactions[reaction_id] local name = string.gsub(reaction.name, "^.", string.upper) reaction_entry(result, job_types.CustomReaction, {reaction_name = reaction.code}, name) end - + -- Reactions generated by the game. - for _, reaction in ipairs(df.global.world.raws.reactions) do + for _, reaction in ipairs(df.global.world.raws.reactions.reactions) do if reaction.source_enid == entity.id then local name = string.gsub(reaction.name, "^.", string.upper) reaction_entry(result, job_types.CustomReaction, {reaction_name = reaction.code}, name) @@ -538,6 +538,7 @@ function collect_reactions() {job_types.MakeEarring, "Make", "Earring"}, {job_types.MakeBracelet, "Make", "Bracelet"}, {job_types.MakeGem, "Make Large", "Gem"}, + {job_types.ConstructMechanisms, "Construct", "Mechanisms"}, }, mat_flags) end @@ -750,7 +751,7 @@ function collect_reactions() {job_types.MakeBracelet, "Make", "Bracelet"}, {job_types.MakeEarring, "Make", "Earring"}, }, mat) - + if not mat.cloth then material_reactions(result, { {job_types.MakeCrown, "Make", "Crown"}, @@ -758,7 +759,7 @@ function collect_reactions() {job_types.MakeRing, "Make", "Ring"}, {job_types.MakeGem, "Make Large", "Gem"}, }, mat) - + if not mat.short then material_reactions(result, { {job_types.MakeScepter, "Make", "Scepter"}, @@ -797,10 +798,30 @@ screen = gui.FramedScreen { function screen:onRenderBody(dc) -- Emulates the built-in manager screen. + + if not (self.page_size == self.frame_rect.height - ExtraLines) then + -- The screen size has changed. + self:refilter() + end + + -- Top instruction line. dc:seek(1, 1):string("Type in parts of the name to narrow your search. ", COLOR_WHITE) - dc:string(gui.getKeyDisplay("LEAVESCREEN"), COLOR_LIGHTGREEN) - dc:string(" to abort.", COLOR_WHITE) - dc:seek(1, PageSize + 5):string(self.search_string, COLOR_LIGHTCYAN) + dc:key("LEAVESCREEN"):string(" to abort.", COLOR_WHITE) + + -- Search term, if any. + dc:seek(1, FirstRow + self.page_size + 1):string(self.search_string, COLOR_LIGHTCYAN) + + -- Bottom instruction line. + dc:seek(1, FirstRow + self.page_size + 2) + dc:key("STANDARDSCROLL_UP"):key("STANDARDSCROLL_DOWN") + dc:key("STANDARDSCROLL_PAGEUP"):key("STANDARDSCROLL_PAGEDOWN") + dc:key("STANDARDSCROLL_LEFT"):key("STANDARDSCROLL_RIGHT") + dc:string(": Select", COLOR_WHITE) + + dc:seek(CenterCol, FirstRow + self.page_size + 2) + dc:key("SETUPGAME_SAVE_PROFILE_ABORT"):string(": No order", COLOR_WHITE) + + -- Reaction lines. for _, item in ipairs(self.displayed) do dc:seek(item.x, item.y):string(item.name, item.color) end @@ -815,28 +836,69 @@ function screen:onInput(keys) if selected then store_order(self.stockpile, selected.index) end + elseif keys.SETUPGAME_SAVE_PROFILE_ABORT then + self:dismiss() + clear_order(self.stockpile) elseif keys.STANDARDSCROLL_UP then self.position = self.position - 1 elseif keys.STANDARDSCROLL_DOWN then self.position = self.position + 1 elseif keys.STANDARDSCROLL_LEFT then - self.position = self.position - PageSize + if self.position == 1 then + -- Move from the very first item to the very last item. + self.position = #self.reactions + elseif self.position < self.page_size then + -- On the first column, move to the very first item. + self.position = 1 + else + -- Move to the same position on the previous column. + self.position = self.position - self.page_size + end elseif keys.STANDARDSCROLL_RIGHT then - self.position = self.position + PageSize + if self.position == #self.reactions then + -- Move from the very last item to the very first item. + self.position = 1 + else + -- Move to the same position on the next column. + self.position = self.position + self.page_size + if self.position > #self.reactions then + -- If that's past the end, move to the very last item. + self.position = #self.reactions + end + end elseif keys.STANDARDSCROLL_PAGEUP then - -- Moves to the first item displayed on the new page, for some reason. - self.position = self.position - PageSize*2 - ((self.position-1) % (PageSize*2)) + if self.position == 1 then + -- Move from the very first item to the very last item. + self.position = #self.reactions + elseif self.position < self.page_size*2 then + -- On the first page, move to the very first item. + self.position = 1 + else + -- Move to the same position on the previous page. + self.position = self.position - self.page_size*2 + end elseif keys.STANDARDSCROLL_PAGEDOWN then - -- Moves to the first item displayed on the new page, for some reason. - self.position = self.position + PageSize*2 - ((self.position-1) % (PageSize*2)) + if self.position == #self.reactions then + -- Move from the very last item to the very first item. + self.position = 1 + else + -- Move to the same position on the next page. + self.position = self.position + self.page_size*2 + if self.position > #self.reactions then + -- If that's past the end, move to the very last item. + self.position = #self.reactions + end + end elseif keys.STRING_A000 then -- This seems like an odd way to check for Backspace. self.search_string = string.sub(self.search_string, 1, -2) + self.position = 1 elseif keys._STRING and keys._STRING >= 32 then -- This interface only accepts letters and spaces. local char = string.char(keys._STRING) if char == " " or string.find(char, "^%a") then self.search_string = self.search_string .. string.upper(char) + self.position = 1 end end @@ -886,6 +948,13 @@ function splitstring(full, pattern) end function screen:refilter() + -- Determine which rows to show, and in which colors. + -- Todo: The official one now has three categories of search results: + -- * Cyan: Contains at least one exact word from the search terms + -- * Yellow: At least one word starts with at least one search term + -- * Grey: Each search term is found in the middle of a word + self.page_size = self.frame_rect.height - ExtraLines + local filtered = {} local needles = splitstring(self.search_string, " ") for key, value in ipairs(reaction_list) do @@ -904,12 +973,12 @@ function screen:refilter() end local start = 1 - while self.position >= start + PageSize*2 do - start = start + PageSize*2 + while self.position >= start + self.page_size*2 do + start = start + self.page_size*2 end local displayed = {} - for n = 0, PageSize*2 - 1 do + for n = 0, self.page_size*2 - 1 do local item = filtered[start + n] if not item then break @@ -918,9 +987,9 @@ function screen:refilter() local x = 1 local y = FirstRow + n - if n >= PageSize then + if n >= self.page_size then x = CenterCol - y = y - PageSize + y = y - self.page_size name = " "..name end @@ -941,6 +1010,14 @@ function screen:refilter() self.displayed = displayed end +function clear_order(stockpile) + local saved = saved_orders[stockpile.id] + if saved then + saved.entry:delete() + saved_orders[stockpile.id] = nil + end +end + function store_order(stockpile, order_number) local name = reaction_list[order_number].name -- print("Setting stockpile #"..stockpile.stockpile_number.." to "..name.." (#"..order_number..")") @@ -1011,22 +1088,20 @@ function order_quantity(order, quantity) end end - if amount > 30 then - -- Respect the quantity limit. - -- With this many in the queue, we can wait for the next cycle. - return 30 - end - return amount end -- Place a new copy of the order onto the manager's queue. function create_orders(order, amount) local new_order = order:new() + amount = math.floor(amount) new_order.amount_left = amount new_order.amount_total = amount -- Todo: Create in a validated state if the fortress is small enough? - new_order.is_validated = 0 + new_order.status.validated = false + new_order.status.active = false + new_order.id = df.global.world.manager_order_next_id + df.global.world.manager_order_next_id = df.global.world.manager_order_next_id + 1 df.global.world.manager_orders:insert('#', new_order) end @@ -1061,8 +1136,7 @@ function check_stockpiles(verbose) if trigger and trigger.divisor then local reaction = spec.entry.ints[entry_ints.order_number] local filled, empty = check_pile(spec.stockpile, verbose) - local amount = trigger.filled and filled or empty - amount = (amount - (amount % trigger.divisor)) / trigger.divisor + local amount = math.floor((trigger.filled and filled or empty) / trigger.divisor) result[reaction] = (result[reaction] or 0) + amount end end diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index dcec1248d..6c4230a51 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -29,7 +29,7 @@ end local function get_reaction(name) if not reaction_id_cache then reaction_id_cache = {} - for i,v in ipairs(df.global.world.raws.reactions) do + for i,v in ipairs(df.global.world.raws.reactions.reactions) do reaction_id_cache[v.code] = i end end diff --git a/plugins/luasocket.cpp b/plugins/luasocket.cpp index fba059fdc..2903616e7 100644 --- a/plugins/luasocket.cpp +++ b/plugins/luasocket.cpp @@ -83,7 +83,7 @@ static int lua_socket_bind(std::string ip,int port) handle_error(err,false); } sock->SetBlocking(); - if(!sock->Listen((uint8_t*)ip.c_str(),port)) + if(!sock->Listen(ip.c_str(),port)) { handle_error(sock->GetSocketError(),false); } @@ -223,7 +223,7 @@ static void lua_client_send(int server_id,int client_id,std::string data) throw std::runtime_error("Client does with this id not exist"); } CActiveSocket *sock=(*target)[client_id]; - if(sock->Send((const uint8_t*)data.c_str(),data.size())!=data.size()) + if(size_t(sock->Send((const uint8_t*)data.c_str(),data.size()))!=data.size()) { throw std::runtime_error(sock->DescribeError()); } @@ -238,7 +238,7 @@ static int lua_socket_connect(std::string ip,int port) delete sock; throw std::runtime_error(CSimpleSocket::DescribeError(err)); } - if(!sock->Open((const uint8_t*)ip.c_str(),port)) + if(!sock->Open(ip.c_str(),port)) { CSimpleSocket::CSocketError err=sock->GetSocketError(); delete sock; diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index dfc7c1f0d..5dab2ab09 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -15,8 +15,9 @@ #include #include #include - #include + +#include "df/activity_event.h" #include "df/world.h" #include "df/ui.h" #include "df/graphic.h" @@ -289,7 +290,8 @@ struct UnitInfo int active_index; string squad_effective_name; string squad_info; - string job_info; + string job_desc; + enum { IDLE, SOCIAL, JOB } job_mode; bool selected; struct { // Used for custom professions, 1-indexed @@ -363,16 +365,18 @@ bool sortBySquad (const UnitInfo *d1, const UnitInfo *d2) bool sortByJob (const UnitInfo *d1, const UnitInfo *d2) { - bool gt = false; + if (d1->job_mode != d2->job_mode) + { + if (descending) + return int(d1->job_mode) < int(d2->job_mode); + else + return int(d1->job_mode) > int(d2->job_mode); + } - if (d1->job_info == "Idle") - gt = false; - else if (d2->job_info == "Idle") - gt = true; + if (descending) + return d1->job_desc > d2->job_desc; else - gt = (d1->job_info > d2->job_info); - - return descending ? gt : !gt; + return d1->job_desc < d2->job_desc; } bool sortByStress (const UnitInfo *d1, const UnitInfo *d2) @@ -592,7 +596,7 @@ namespace unit_ops { } string get_short_profname(UnitInfo *u) { - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (columns[i].profession == u->unit->profession) return string(columns[i].label); @@ -647,15 +651,15 @@ struct ProfessionTemplate name = line.substr(nextInd + 1); continue; } - if (line.compare("MASK")==0) + if (line == "MASK") { mask = true; continue; } - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { - if (line.compare(ENUM_KEY_STR(unit_labor, columns[i].labor)) == 0) + if (line == ENUM_KEY_STR(unit_labor, columns[i].labor)) { labors.push_back(columns[i].labor); } @@ -674,7 +678,7 @@ struct ProfessionTemplate if (mask) outfile << "MASK" << std::endl; - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (hasLabor(columns[i].labor)) { @@ -692,7 +696,7 @@ struct ProfessionTemplate if (!mask && name.size() > 0) unit_ops::set_profname(u, name); - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { df::unit_labor labor = columns[i].labor; bool status = hasLabor(labor); @@ -705,7 +709,7 @@ struct ProfessionTemplate void fromUnit(UnitInfo* u) { - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (u->unit->status.labors[columns[i].labor]) labors.push_back(columns[i].labor); @@ -743,9 +747,10 @@ public: return; } Filesystem::listdir(professions_folder, files); + std::sort(files.begin(), files.end()); for(size_t i = 0; i < files.size(); i++) { - if (files[i].compare(".") == 0 || files[i].compare("..") == 0) + if (files[i] == "." || files[i] == "..") continue; ProfessionTemplate t; @@ -894,7 +899,7 @@ public: } OutputString(COLOR_LIGHTGREEN, x, y, itos(units.size())); OutputString(COLOR_GREY, x, y, string(" ") + (units.size() > 1 ? "dwarves" : "dwarf") + " selected: "); - int max_x = gps->dimx - 2; + size_t max_x = gps->dimx - 2; size_t i = 0; for ( ; i < units.size(); i++) { @@ -954,6 +959,7 @@ public: viewscreen_unitprofessionset(vector &base_units, bool filter_selected = true ) + :menu_options(-1) // default { menu_options.multiselect = false; menu_options.auto_select = true; @@ -967,7 +973,7 @@ public: std::string name = manager.templates[i].name; if (manager.templates[i].mask) name += " (mask)"; - ListEntry elem(name, i+1); + ListEntry elem(name, i); menu_options.add(elem); } menu_options.filterDisplay(); @@ -1001,20 +1007,22 @@ public: } if (events->count(interface_key::SELECT)) { - select_profession(menu_options.getFirstSelectedElem()); + if (menu_options.hasSelection()) + { + select_profession(menu_options.getFirstSelectedElem()); + } Screen::dismiss(this); return; } } void select_profession(size_t selected) { - if (selected > manager.templates.size()) + if (manager.templates.empty() || selected >= manager.templates.size()) return; - ProfessionTemplate prof = manager.templates[selected - 1]; + ProfessionTemplate prof = manager.templates[selected]; - for (auto it = units.begin(); it != units.end(); ++it) + for (UnitInfo *u : units) { - UnitInfo* u = (*it); if (!u || !u->unit || !u->allowEdit) continue; prof.apply(u); } @@ -1038,7 +1046,7 @@ public: menu_options.display(true); OutputString(COLOR_LIGHTGREEN, x, y, itos(units.size())); OutputString(COLOR_GREY, x, y, string(" ") + (units.size() > 1 ? "dwarves" : "dwarf") + " selected: "); - int max_x = gps->dimx - 2; + size_t max_x = gps->dimx - 2; size_t i = 0; for ( ; i < units.size(); i++) { @@ -1130,7 +1138,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur df::unit *unit = src[i]; if (!unit) { - if (cursor_pos > i) + if (cursor_pos > int(i)) cursor_pos--; continue; } @@ -1184,7 +1192,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur if (first_row > sel_row) first_row = sel_row - num_rows + 1; // don't scroll beyond the end - if (first_row > units.size() - num_rows) + if (first_row > int(units.size()) - num_rows) first_row = units.size() - num_rows; last_selection = -1; @@ -1199,7 +1207,7 @@ void viewscreen_unitlaborsst::calcIDs() if (!initialized) { initialized = true; - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) group_map.insert(std::pair(columns[i].profession, columns[i].group)); } memset(list_prof_ids, 0, sizeof(list_prof_ids)); @@ -1230,9 +1238,18 @@ void viewscreen_unitlaborsst::refreshNames() cur->profession = Units::getProfessionName(unit); if (unit->job.current_job == NULL) { - cur->job_info = "Idle"; + df::activity_event *event = Units::getMainSocialEvent(unit); + if (event) { + event->getName(unit->id, &cur->job_desc); + cur->job_mode = UnitInfo::SOCIAL; + } + else { + cur->job_desc = "Idle"; + cur->job_mode = UnitInfo::IDLE; + } } else { - cur->job_info = DFHack::Job::getName(unit->job.current_job); + cur->job_desc = DFHack::Job::getName(unit->job.current_job); + cur->job_mode = UnitInfo::JOB; } if (unit->military.squad_id > -1) { cur->squad_effective_name = Units::getSquadName(unit); @@ -1250,7 +1267,7 @@ void viewscreen_unitlaborsst::calcSize() auto dim = Screen::getWindowSize(); num_rows = dim.y - 11; - if (num_rows > units.size()) + if (num_rows > int(units.size())) num_rows = units.size(); int num_columns = dim.x - DISP_COLUMN_MAX - 1; @@ -1272,18 +1289,18 @@ void viewscreen_unitlaborsst::calcSize() // get max_name/max_prof from strings length for (size_t i = 0; i < units.size(); i++) { - if (col_maxwidth[DISP_COLUMN_NAME] < units[i]->name.size()) + if (size_t(col_maxwidth[DISP_COLUMN_NAME]) < units[i]->name.size()) col_maxwidth[DISP_COLUMN_NAME] = units[i]->name.size(); size_t detail_cmp; if (detail_mode == DETAIL_MODE_SQUAD) { detail_cmp = units[i]->squad_info.size(); } else if (detail_mode == DETAIL_MODE_JOB) { - detail_cmp = units[i]->job_info.size(); + detail_cmp = units[i]->job_desc.size(); } else { detail_cmp = units[i]->profession.size(); } - if (col_maxwidth[DISP_COLUMN_DETAIL] < detail_cmp) + if (size_t(col_maxwidth[DISP_COLUMN_DETAIL]) < detail_cmp) col_maxwidth[DISP_COLUMN_DETAIL] = detail_cmp; } @@ -1366,7 +1383,7 @@ void viewscreen_unitlaborsst::calcSize() return; // if the window grows vertically, scroll upward to eliminate blank rows from the bottom - if (first_row > units.size() - num_rows) + if (first_row > int(units.size()) - num_rows) first_row = units.size() - num_rows; // if it shrinks vertically, scroll downward to keep the cursor visible @@ -1374,7 +1391,7 @@ void viewscreen_unitlaborsst::calcSize() first_row = sel_row - num_rows + 1; // if the window grows horizontally, scroll to the left to eliminate blank columns from the right - if (first_column > NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS]) + if (first_column > int(NUM_COLUMNS) - col_widths[DISP_COLUMN_LABORS]) first_column = NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS]; // if it shrinks horizontally, scroll to the right to keep the cursor visible @@ -1420,7 +1437,7 @@ void viewscreen_unitlaborsst::feed(set *events) { sel_row = 0; } - if ((sel_row < units.size()-1) && events->count(interface_key::CURSOR_DOWN_Z_AUX)) + if ((size_t(sel_row) < units.size()-1) && events->count(interface_key::CURSOR_DOWN_Z_AUX)) { sel_row = units.size()-1; } @@ -1433,9 +1450,9 @@ void viewscreen_unitlaborsst::feed(set *events) sel_row = 0; } - if (sel_row > units.size() - 1) + if (size_t(sel_row) > units.size() - 1) { - if (old_sel_row == units.size()-1 && events->count(interface_key::CURSOR_DOWN)) + if (size_t(old_sel_row) == units.size()-1 && events->count(interface_key::CURSOR_DOWN)) sel_row = 0; else sel_row = units.size() - 1; @@ -1470,7 +1487,7 @@ void viewscreen_unitlaborsst::feed(set *events) { // go to beginning of next group int cur = columns[sel_column].group; - int next = sel_column+1; + size_t next = sel_column+1; while ((next < NUM_COLUMNS) && (columns[next].group == cur)) next++; if ((next < NUM_COLUMNS) && (columns[next].group != cur)) @@ -1482,14 +1499,14 @@ void viewscreen_unitlaborsst::feed(set *events) if (sel_column < 0) sel_column = 0; - if (sel_column > NUM_COLUMNS - 1) + if (size_t(sel_column) > NUM_COLUMNS - 1) sel_column = NUM_COLUMNS - 1; if (events->count(interface_key::CURSOR_DOWN_Z) || events->count(interface_key::CURSOR_UP_Z)) { // when moving by group, ensure the whole group is shown onscreen int endgroup_column = sel_column; - while ((endgroup_column < NUM_COLUMNS-1) && columns[endgroup_column+1].group == columns[sel_column].group) + while ((size_t(endgroup_column) < NUM_COLUMNS-1) && columns[endgroup_column+1].group == columns[sel_column].group) endgroup_column++; if (first_column < endgroup_column - col_widths[DISP_COLUMN_LABORS] + 1) @@ -1657,7 +1674,7 @@ void viewscreen_unitlaborsst::feed(set *events) { if (newstatus) { - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if ((columns[i].labor != unit_labor::NONE) && columns[i].special) unit->status.labors[columns[i].labor] = false; @@ -1671,7 +1688,7 @@ void viewscreen_unitlaborsst::feed(set *events) { const SkillColumn &col = columns[input_column]; bool newstatus = !unit->status.labors[col.labor]; - for (int i = 0; i < NUM_COLUMNS; i++) + for (size_t i = 0; i < NUM_COLUMNS; i++) { if (columns[i].group != col.group) continue; @@ -1681,7 +1698,7 @@ void viewscreen_unitlaborsst::feed(set *events) { if (newstatus) { - for (int j = 0; j < NUM_COLUMNS; j++) + for (size_t j = 0; j < NUM_COLUMNS; j++) { if ((columns[j].labor != unit_labor::NONE) && columns[j].special) unit->status.labors[columns[j].labor] = false; @@ -1750,6 +1767,8 @@ void viewscreen_unitlaborsst::feed(set *events) case ALTSORT_ARRIVAL: altsort = ALTSORT_NAME; break; + case ALTSORT_MAX: + break; } } if (events->count(interface_key::OPTION20)) @@ -1830,7 +1849,7 @@ void viewscreen_unitlaborsst::feed(set *events) { if (events->count(interface_key::UNITJOB_VIEW_UNIT) || events->count(interface_key::UNITJOB_ZOOM_CRE)) { - for (int i = 0; i < unitlist->units[unitlist->page].size(); i++) + for (size_t i = 0; i < unitlist->units[unitlist->page].size(); i++) { if (unitlist->units[unitlist->page][i] == units[input_row]->unit) { @@ -1876,7 +1895,7 @@ void viewscreen_unitlaborsst::render() for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++) { int col_offset = col + first_column; - if (col_offset >= NUM_COLUMNS) + if (size_t(col_offset) >= NUM_COLUMNS) break; int8_t fg = columns[col_offset].color; @@ -1905,7 +1924,7 @@ void viewscreen_unitlaborsst::render() for (int row = 0; row < num_rows; row++) { int row_offset = row + first_row; - if (row_offset >= units.size()) + if (size_t(row_offset) >= units.size()) break; UnitInfo *cur = units[row_offset]; @@ -1950,11 +1969,13 @@ void viewscreen_unitlaborsst::render() fg = 11; detail_str = cur->squad_info; } else if (detail_mode == DETAIL_MODE_JOB) { - detail_str = cur->job_info; - if (detail_str == "Idle") { - fg = 14; + detail_str = cur->job_desc; + if (cur->job_mode == UnitInfo::IDLE) { + fg = COLOR_YELLOW; + } else if (cur->job_mode == UnitInfo::SOCIAL) { + fg = COLOR_LIGHTGREEN; } else { - fg = 10; + fg = COLOR_LIGHTCYAN; } } else { fg = cur->color; @@ -1979,7 +2000,7 @@ void viewscreen_unitlaborsst::render() skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); if ((skill != NULL) && (skill->rating || skill->experience)) { - int level = skill->rating; + size_t level = skill->rating; if (level > NUM_SKILL_LEVELS - 1) level = NUM_SKILL_LEVELS - 1; c = skill_levels[level].abbrev; @@ -2041,7 +2062,7 @@ void viewscreen_unitlaborsst::render() skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); if (skill) { - int level = skill->rating; + size_t level = skill->rating; if (level > NUM_SKILL_LEVELS - 1) level = NUM_SKILL_LEVELS - 1; str = stl_sprintf("%s %s", skill_levels[level].name, ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); diff --git a/plugins/misery.cpp b/plugins/misery.cpp index a4468079c..6a67dc90f 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -1,65 +1,107 @@ -#include "PluginManager.h" -#include "Export.h" +#include +#include +#include +#include #include "DataDefs.h" -#include "df/world.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Units.h" + +#include "df/emotion_type.h" #include "df/ui.h" #include "df/unit.h" -#include "df/unit_thought.h" +#include "df/unit_personality.h" +#include "df/unit_soul.h" #include "df/unit_thought_type.h" - -#include -#include -#include +#include "df/world.h" using namespace std; using namespace DFHack; -/* -misery -====== -When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default). - -Usage: - -:misery enable n: enable misery with optional magnitude n. If specified, n must be positive. -:misery n: same as "misery enable n" -:misery enable: same as "misery enable 2" -:misery disable: stop adding new negative thoughts. This will not remove existing - duplicated thoughts. Equivalent to "misery 1" -:misery clear: remove fake thoughts added in this session of DF. Saving makes them - permanent! Does not change factor. -*/ DFHACK_PLUGIN("misery"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(cur_year); +REQUIRE_GLOBAL(cur_year_tick); -static int factor = 1; -static map processedThoughtCountTable; +typedef df::unit_personality::T_emotions Emotion; -//keep track of fake thoughts so you can remove them if requested -static vector > fakeThoughts; -static int count; -const int maxCount = 1000; +static int factor = 1; +static int tick = 0; +const int INTERVAL = 1000; command_result misery(color_ostream& out, vector& parameters); +void add_misery(df::unit *unit); +void clear_misery(df::unit *unit); + +const int FAKE_EMOTION_FLAG = (1 << 30); +const int STRENGTH_MULTIPLIER = 100; + +bool is_valid_unit (df::unit *unit) { + if (!Units::isOwnRace(unit) || !Units::isOwnCiv(unit)) + return false; + if (Units::isDead(unit)) + return false; + return true; +} + +inline bool is_fake_emotion (Emotion *e) { + return e->flags.whole & FAKE_EMOTION_FLAG; +} + +void add_misery (df::unit *unit) { + // Add a fake miserable thought + // Remove any fake ones that already exist + if (!unit || !unit->status.current_soul) + return; + clear_misery(unit); + auto &emotions = unit->status.current_soul->personality.emotions; + Emotion *e = new Emotion; + e->type = df::emotion_type::MISERY; + e->thought = df::unit_thought_type::SoapyBath; + e->flags.whole |= FAKE_EMOTION_FLAG; + emotions.push_back(e); + + for (Emotion *e : emotions) { + if (is_fake_emotion(e)) { + e->year = *cur_year; + e->year_tick = *cur_year_tick; + e->strength = STRENGTH_MULTIPLIER * factor; + e->severity = STRENGTH_MULTIPLIER * factor; + } + } +} + +void clear_misery (df::unit *unit) { + if (!unit || !unit->status.current_soul) + return; + auto &emotions = unit->status.current_soul->personality.emotions; + auto it = remove_if(emotions.begin(), emotions.end(), [](Emotion *e) { + if (is_fake_emotion(e)) { + delete e; + return true; + } + return false; + }); + emotions.erase(it, emotions.end()); +} DFhackCExport command_result plugin_shutdown(color_ostream& out) { - factor = 1; + factor = 0; return CR_OK; } DFhackCExport command_result plugin_onupdate(color_ostream& out) { static bool wasLoaded = false; - if ( factor == 1 || !world || !world->map.block_index ) { + if ( factor == 0 || !world || !world->map.block_index ) { if ( wasLoaded ) { //we just unloaded the game: clear all data - factor = 1; + factor = 0; is_enabled = false; - processedThoughtCountTable.clear(); - fakeThoughts.clear(); wasLoaded = false; } return CR_OK; @@ -69,71 +111,17 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { wasLoaded = true; } - if ( count < maxCount ) { - count++; + if ( tick < INTERVAL ) { + tick++; return CR_OK; } - count = 0; - - int32_t race_id = ui->race_id; - int32_t civ_id = ui->civ_id; - for ( size_t a = 0; a < world->units.all.size(); a++ ) { - df::unit* unit = world->units.all[a]; //TODO: consider units.active - //living, native units only - if ( unit->race != race_id || unit->civ_id != civ_id ) - continue; - if ( unit->flags1.bits.dead ) - continue; - - int processedThoughtCount; - map::iterator i = processedThoughtCountTable.find(unit->id); - if ( i == processedThoughtCountTable.end() ) { - processedThoughtCount = unit->status.recent_events.size(); - processedThoughtCountTable[unit->id] = processedThoughtCount; - } else { - processedThoughtCount = (*i).second; - } - - if ( processedThoughtCount == unit->status.recent_events.size() ) { - continue; - } else if ( processedThoughtCount > unit->status.recent_events.size() ) { - processedThoughtCount = unit->status.recent_events.size(); - } + tick = 0; - //don't reprocess any old thoughts - vector newThoughts; - for ( size_t b = processedThoughtCount; b < unit->status.recent_events.size(); b++ ) { - df::unit_thought* oldThought = unit->status.recent_events[b]; - const char* bob = ENUM_ATTR(unit_thought_type, value, oldThought->type); - if ( bob[0] != '-' ) { - //out.print("unit %4d: old thought value = %s\n", unit->id, bob); - continue; - } - /*out.print("unit %4d: Duplicating thought type %d (%s), value %s, age %d, subtype %d, severity %d\n", - unit->id, - oldThought->type.value, - ENUM_ATTR(unit_thought_type, caption, (oldThought->type)), - //df::enum_traits::attr_table[oldThought->type].caption - bob, - oldThought->age, - oldThought->subtype, - oldThought->severity - );*/ - //add duplicate thoughts to the temp list - for ( size_t c = 0; c < factor; c++ ) { - df::unit_thought* thought = new df::unit_thought; - thought->type = unit->status.recent_events[b]->type; - thought->age = unit->status.recent_events[b]->age; - thought->subtype = unit->status.recent_events[b]->subtype; - thought->severity = unit->status.recent_events[b]->severity; - newThoughts.push_back(thought); - } + //TODO: consider units.active + for (df::unit *unit : world->units.all) { + if (is_valid_unit(unit)) { + add_misery(unit); } - for ( size_t b = 0; b < newThoughts.size(); b++ ) { - fakeThoughts.push_back(std::pair(a, unit->status.recent_events.size())); - unit->status.recent_events.push_back(newThoughts[b]); - } - processedThoughtCountTable[unit->id] = unit->status.recent_events.size(); } return CR_OK; @@ -163,7 +151,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { is_enabled = enable; - factor = enable ? 2 : 1; + factor = enable ? 1 : 0; + tick = INTERVAL; } return CR_OK; @@ -183,34 +172,34 @@ command_result misery(color_ostream &out, vector& parameters) { if ( parameters.size() > 1 ) { return CR_WRONG_USAGE; } - factor = 1; + factor = 0; is_enabled = false; return CR_OK; } else if ( parameters[0] == "enable" ) { is_enabled = true; - factor = 2; + factor = 1; if ( parameters.size() == 2 ) { int a = atoi(parameters[1].c_str()); - if ( a <= 1 ) { + if ( a < 1 ) { out.printerr("Second argument must be a positive integer.\n"); return CR_WRONG_USAGE; } factor = a; } + tick = INTERVAL; } else if ( parameters[0] == "clear" ) { - for ( size_t a = 0; a < fakeThoughts.size(); a++ ) { - int dorfIndex = fakeThoughts[a].first; - int thoughtIndex = fakeThoughts[a].second; - world->units.all[dorfIndex]->status.recent_events[thoughtIndex]->age = 1000000; + for (df::unit *unit : world->units.all) { + if (is_valid_unit(unit)) { + clear_misery(unit); + } } - fakeThoughts.clear(); } else { int a = atoi(parameters[0].c_str()); - if ( a < 1 ) { + if ( a < 0 ) { return CR_WRONG_USAGE; } factor = a; - is_enabled = factor > 1; + is_enabled = factor > 0; } return CR_OK; diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 2da47d8cd..66fa0fee5 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -46,7 +46,7 @@ static bool live_view = true; static bool skip_tracking_once = false; static bool mouse_moved = false; -static int scroll_delay = 100; +static uint32_t scroll_delay = 100; static df::coord get_mouse_pos(int32_t &mx, int32_t &my) { @@ -65,7 +65,7 @@ static df::coord get_mouse_pos(int32_t &mx, int32_t &my) pos.x = vx + mx - 1; pos.y = vy + my - 1; - pos.z = vz - Gui::getDepthAt(pos.x, pos.y); + pos.z = vz - Gui::getDepthAt(mx, my); return pos; } @@ -231,9 +231,10 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest case Burrows: return ui->burrows.in_define_mode; - }; - return false; + default: + return false; + } } bool isInTrackableMode() @@ -391,10 +392,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (!designationMode) { - while (ui->main.mode != Default) - { - sendKey(df::interface_key::LEAVESCREEN); - } + Gui::resetDwarfmodeView(); if (key == interface_key::NONE) key = get_default_query_mode(mpos); @@ -507,16 +505,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return; Gui::setCursorCoords(mpos.x, mpos.y, mpos.z); - if (mpos.z == 0) - { - sendKey(interface_key::CURSOR_UP_Z); - sendKey(interface_key::CURSOR_DOWN_Z); - } - else - { - sendKey(interface_key::CURSOR_DOWN_Z); - sendKey(interface_key::CURSOR_UP_Z); - } + Gui::refreshSidebar(); } bool inBuildPlacement() diff --git a/plugins/orders.cpp b/plugins/orders.cpp new file mode 100644 index 000000000..4a2e3b0c0 --- /dev/null +++ b/plugins/orders.cpp @@ -0,0 +1,818 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Filesystem.h" +#include "modules/Materials.h" + +#include "jsoncpp.h" + +#include "df/building.h" +#include "df/historical_figure.h" +#include "df/itemdef_ammost.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_foodst.h" +#include "df/itemdef_glovesst.h" +#include "df/itemdef_helmst.h" +#include "df/itemdef_instrumentst.h" +#include "df/itemdef_pantsst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_siegeammost.h" +#include "df/itemdef_toolst.h" +#include "df/itemdef_toyst.h" +#include "df/itemdef_trapcompst.h" +#include "df/itemdef_weaponst.h" +#include "df/job_item.h" +#include "df/manager_order.h" +#include "df/manager_order_condition_item.h" +#include "df/manager_order_condition_order.h" +#include "df/world.h" + +using namespace DFHack; +using namespace df::enums; + +DFHACK_PLUGIN("orders"); + +REQUIRE_GLOBAL(world); + +static command_result orders_command(color_ostream & out, std::vector & parameters); + +DFhackCExport command_result plugin_init(color_ostream & out, std::vector & commands) +{ + commands.push_back(PluginCommand( + "orders", + "Manipulate manager orders.", + orders_command, + false, + "orders - Manipulate manager orders\n" + " orders export [name]\n" + " Exports the current list of manager orders to a file named dfhack-config/orders/[name].json.\n" + " orders import [name]\n" + " Imports manager orders from a file named dfhack-config/orders/[name].json.\n" + " orders clear\n" + " Deletes all manager orders in the current embark.\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream & out) +{ + return CR_OK; +} + +static command_result orders_export_command(color_ostream & out, const std::string & name); +static command_result orders_import_command(color_ostream & out, const std::string & name); +static command_result orders_clear_command(color_ostream & out); + +static command_result orders_command(color_ostream & out, std::vector & parameters) +{ + class color_ostream_resetter + { + color_ostream & out; + + public: + color_ostream_resetter(color_ostream & out) : out(out) {} + ~color_ostream_resetter() { out.reset_color(); } + } resetter(out); + + if (parameters.empty()) + { + return CR_WRONG_USAGE; + } + + if (parameters[0] == "export" && parameters.size() == 2) + { + return orders_export_command(out, parameters[1]); + } + + if (parameters[0] == "import" && parameters.size() == 2) + { + return orders_import_command(out, parameters[1]); + } + + if (parameters[0] == "clear" && parameters.size() == 1) + { + return orders_clear_command(out); + } + + return CR_WRONG_USAGE; +} + +static bool is_safe_filename(color_ostream & out, const std::string & name) +{ + if (name.empty()) + { + out << COLOR_LIGHTRED << "Missing filename!" << std::endl; + return false; + } + + for (auto it : name) + { + if (isalnum(it)) + { + continue; + } + + if (it != ' ' && it != '.' && it != '-' && it != '_') + { + out << COLOR_LIGHTRED << "Invalid symbol in name: " << it << std::endl; + return false; + } + } + + return true; +} + +template +static void bitfield_to_json_array(Json::Value & out, B bits) +{ + std::vector names; + bitfield_to_string(&names, bits); + + for (auto & it : names) + { + out.append(it); + } +} + +template +static void json_array_to_bitfield(B & bits, Json::Value & arr) +{ + if (arr.size() == 0) + { + return; + } + + for (Json::ArrayIndex i = arr.size(); i != 0; i--) + { + if (!arr[i - 1].isString()) + { + continue; + } + + std::string str(arr[i - 1].asString()); + + int current; + if (get_bitfield_field(¤t, bits, str)) + { + if (!current && set_bitfield_field(&bits, str, 1)) + { + Json::Value removed; + arr.removeIndex(i - 1, &removed); + } + } + } +} + +template +static df::itemdef *get_itemdef(int16_t subtype) +{ + return D::find(subtype); +} + +template +static df::itemdef *get_itemdef(const std::string & subtype) +{ + for (auto it : D::get_vector()) + { + if (it->id == subtype) + { + return it; + } + } + return nullptr; +} + +template +static df::itemdef *get_itemdef(color_ostream & out, df::item_type type, ST subtype) +{ + switch (type) + { + case item_type::AMMO: + return get_itemdef(subtype); + case item_type::ARMOR: + return get_itemdef(subtype); + case item_type::FOOD: + return get_itemdef(subtype); + case item_type::GLOVES: + return get_itemdef(subtype); + case item_type::HELM: + return get_itemdef(subtype); + case item_type::INSTRUMENT: + return get_itemdef(subtype); + case item_type::PANTS: + return get_itemdef(subtype); + case item_type::SHIELD: + return get_itemdef(subtype); + case item_type::SHOES: + return get_itemdef(subtype); + case item_type::SIEGEAMMO: + return get_itemdef(subtype); + case item_type::TOOL: + return get_itemdef(subtype); + case item_type::TOY: + return get_itemdef(subtype); + case item_type::TRAPCOMP: + return get_itemdef(subtype); + case item_type::WEAPON: + return get_itemdef(subtype); + default: + out << COLOR_YELLOW << "Unhandled raw item type in manager order: " << enum_item_key(type) << "! Please report this bug to DFHack." << std::endl; + return nullptr; + } +} + +static command_result orders_export_command(color_ostream & out, const std::string & name) +{ + if (!is_safe_filename(out, name)) + { + return CR_WRONG_USAGE; + } + + Json::Value orders(Json::arrayValue); + + { + CoreSuspender suspend; + + for (auto it : world->manager_orders) + { + Json::Value order(Json::objectValue); + + order["id"] = it->id; + order["job"] = enum_item_key(it->job_type); + if (!it->reaction_name.empty()) + { + order["reaction"] = it->reaction_name; + } + + if (it->item_type != item_type::NONE) + { + order["item_type"] = enum_item_key(it->item_type); + } + if (it->item_subtype != -1) + { + df::itemdef *def = get_itemdef(out, it->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, it->job_type) : it->item_type, it->item_subtype); + + if (def) + { + order["item_subtype"] = def->id; + } + } + + if (it->job_type == job_type::PrepareMeal) + { + order["meal_ingredients"] = it->mat_type; + } + else if (it->mat_type != -1 || it->mat_index != -1) + { + order["material"] = MaterialInfo(it).getToken(); + } + + if (it->item_category.whole != 0) + { + bitfield_to_json_array(order["item_category"], it->item_category); + } + + if (it->hist_figure_id != -1) + { + order["hist_figure"] = it->hist_figure_id; + } + + if (it->material_category.whole != 0) + { + bitfield_to_json_array(order["material_category"], it->material_category); + } + + if (it->art_spec.type != df::job_art_specification::None) + { + Json::Value art(Json::objectValue); + + art["type"] = enum_item_key(it->art_spec.type); + art["id"] = it->art_spec.id; + if (it->art_spec.subid != -1) + { + art["subid"] = it->art_spec.subid; + } + + order["art"] = art; + } + + order["amount_left"] = it->amount_left; + order["amount_total"] = it->amount_total; + order["is_validated"] = bool(it->status.bits.validated); + order["is_active"] = bool(it->status.bits.active); + + order["frequency"] = enum_item_key(it->frequency); + + // TODO: finished_year, finished_year_tick + + if (it->workshop_id != -1) + { + order["workshop_id"] = it->workshop_id; + } + + if (it->max_workshops != 0) + { + order["max_workshops"] = it->max_workshops; + } + + if (!it->item_conditions.empty()) + { + Json::Value conditions(Json::arrayValue); + + for (auto it2 : it->item_conditions) + { + Json::Value condition(Json::objectValue); + + condition["condition"] = enum_item_key(it2->compare_type); + condition["value"] = it2->compare_val; + + if (it2->flags1.whole != 0 || it2->flags2.whole != 0 || it2->flags3.whole != 0) + { + bitfield_to_json_array(condition["flags"], it2->flags1); + bitfield_to_json_array(condition["flags"], it2->flags2); + bitfield_to_json_array(condition["flags"], it2->flags3); + // TODO: flags4, flags5 + } + + if (it2->item_type != item_type::NONE) + { + condition["item_type"] = enum_item_key(it2->item_type); + } + if (it2->item_subtype != -1) + { + df::itemdef *def = get_itemdef(out, it2->item_type, it2->item_subtype); + + if (def) + { + condition["item_subtype"] = def->id; + } + } + + if (it2->mat_type != -1 || it2->mat_index != -1) + { + condition["material"] = MaterialInfo(it2).getToken(); + } + + if (it2->inorganic_bearing != -1) + { + condition["bearing"] = df::inorganic_raw::find(it2->inorganic_bearing)->id; + } + + if (!it2->reaction_class.empty()) + { + condition["reaction_class"] = it2->reaction_class; + } + + if (!it2->has_material_reaction_product.empty()) + { + condition["reaction_product"] = it2->has_material_reaction_product; + } + + if (it2->has_tool_use != tool_uses::NONE) + { + condition["tool"] = enum_item_key(it2->has_tool_use); + } + + // TODO: anon_1, anon_2, anon_3 + + conditions.append(condition); + } + + order["item_conditions"] = conditions; + } + + if (!it->order_conditions.empty()) + { + Json::Value conditions(Json::arrayValue); + + for (auto it2 : it->order_conditions) + { + Json::Value condition(Json::objectValue); + + condition["order"] = it2->order_id; + condition["condition"] = enum_item_key(it2->condition); + + // TODO: anon_1 + + conditions.append(condition); + } + + order["order_conditions"] = conditions; + } + + // TODO: anon_1 + + orders.append(order); + } + } + + Filesystem::mkdir("dfhack-config/orders"); + + std::ofstream file("dfhack-config/orders/" + name + ".json"); + + file << orders << std::endl; + + return file.good() ? CR_OK : CR_FAILURE; +} + +static command_result orders_import_command(color_ostream & out, const std::string & name) +{ + if (!is_safe_filename(out, name)) + { + return CR_WRONG_USAGE; + } + + Json::Value orders; + + { + std::ifstream file("dfhack-config/orders/" + name + ".json"); + + if (!file.good()) + { + out << COLOR_LIGHTRED << "Cannot find orders file." << std::endl; + return CR_FAILURE; + } + + file >> orders; + + if (!file.good()) + { + out << COLOR_LIGHTRED << "Error reading orders file." << std::endl; + return CR_FAILURE; + } + } + + if (orders.type() != Json::arrayValue) + { + out << COLOR_LIGHTRED << "Invalid orders file: expected array" << std::endl; + return CR_FAILURE; + } + + CoreSuspender suspend; + + std::map id_mapping; + for (auto it : orders) + { + id_mapping[it["id"].asInt()] = world->manager_order_next_id; + world->manager_order_next_id++; + } + + for (auto & it : orders) + { + df::manager_order *order = new df::manager_order(); + + order->id = id_mapping.at(it["id"].asInt()); + + if (!find_enum_item(&order->job_type, it["job"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid job type for imported manager order: " << it["job"].asString() << std::endl; + + return CR_FAILURE; + } + + if (it.isMember("reaction")) + { + order->reaction_name = it["reaction"].asString(); + } + + if (it.isMember("item_type")) + { + if (!find_enum_item(&order->item_type, it["item_type"].asString()) || order->item_type == item_type::NONE) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid item type for imported manager order: " << it["item_type"].asString() << std::endl; + + return CR_FAILURE; + } + } + if (it.isMember("item_subtype")) + { + df::itemdef *def = get_itemdef(out, order->item_type == item_type::NONE ? ENUM_ATTR(job_type, item, order->job_type) : order->item_type, it["item_subtype"].asString()); + + if (def) + { + order->item_subtype = def->subtype; + } + else + { + delete order; + + out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << it["item_subtype"].asString() << std::endl; + + return CR_FAILURE; + } + } + + if (it.isMember("meal_ingredients")) + { + order->mat_type = it["meal_ingredients"].asInt(); + order->mat_index = -1; + } + else if (it.isMember("material")) + { + MaterialInfo mat; + if (!mat.find(it["material"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid material for imported manager order: " << it["material"].asString() << std::endl; + + return CR_FAILURE; + } + order->mat_type = mat.type; + order->mat_index = mat.index; + } + + if (it.isMember("item_category")) + { + json_array_to_bitfield(order->item_category, it["item_category"]); + if (!it["item_category"].empty()) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid item_category value for imported manager order: " << it["item_category"] << std::endl; + + return CR_FAILURE; + } + } + + if (it.isMember("hist_figure")) + { + if (!df::historical_figure::find(it["hist_figure"].asInt())) + { + delete order; + + out << COLOR_YELLOW << "Missing historical figure for imported manager order: " << it["hist_figure"].asInt() << std::endl; + + continue; + } + + order->hist_figure_id = it["hist_figure"].asInt(); + } + + if (it.isMember("material_category")) + { + json_array_to_bitfield(order->material_category, it["material_category"]); + if (!it["material_category"].empty()) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid material_category value for imported manager order: " << it["material_category"] << std::endl; + + return CR_FAILURE; + } + } + + if (it.isMember("art")) + { + if (!find_enum_item(&order->art_spec.type, it["art"]["type"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid art type value for imported manager order: " << it["art"]["type"].asString() << std::endl; + + return CR_FAILURE; + } + + order->art_spec.id = it["art"]["id"].asInt(); + if (it["art"].isMember("subid")) + { + order->art_spec.subid = it["art"]["subid"].asInt(); + } + } + + order->amount_left = it["amount_left"].asInt(); + order->amount_total = it["amount_total"].asInt(); + order->status.bits.validated = it["is_validated"].asBool(); + order->status.bits.active = it["is_active"].asBool(); + + if (!find_enum_item(&order->frequency, it["frequency"].asString())) + { + delete order; + + out << COLOR_LIGHTRED << "Invalid frequency value for imported manager order: " << it["frequency"].asString() << std::endl; + + return CR_FAILURE; + } + + // TODO: finished_year, finished_year_tick + + if (it.isMember("workshop_id")) + { + if (!df::building::find(it["workshop_id"].asInt())) + { + delete order; + + out << COLOR_YELLOW << "Missing workshop for imported manager order: " << it["workshop_id"].asInt() << std::endl; + + continue; + } + + order->workshop_id = it["workshop_id"].asInt(); + } + + if (it.isMember("max_workshops")) + { + order->max_workshops = it["max_workshops"].asInt(); + } + + if (it.isMember("item_conditions")) + { + for (auto & it2 : it["item_conditions"]) + { + df::manager_order_condition_item *condition = new df::manager_order_condition_item(); + + if (!find_enum_item(&condition->compare_type, it2["condition"].asString())) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition condition for imported manager order: " << it2["condition"].asString() << std::endl; + + continue; + } + + condition->compare_val = it2["value"].asInt(); + + if (it2.isMember("flags")) + { + json_array_to_bitfield(condition->flags1, it2["flags"]); + json_array_to_bitfield(condition->flags2, it2["flags"]); + json_array_to_bitfield(condition->flags3, it2["flags"]); + // TODO: flags4, flags5 + + if (!it2["flags"].empty()) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition flags for imported manager order: " << it2["flags"] << std::endl; + + continue; + } + } + + if (it2.isMember("item_type")) + { + if (!find_enum_item(&condition->item_type, it2["item_type"].asString()) || condition->item_type == item_type::NONE) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition item type for imported manager order: " << it2["item_type"].asString() << std::endl; + + continue; + } + } + if (it2.isMember("item_subtype")) + { + df::itemdef *def = get_itemdef(out, condition->item_type, it2["item_subtype"].asString()); + + if (def) + { + condition->item_subtype = def->subtype; + } + else + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << it2["item_subtype"].asString() << std::endl; + + continue; + } + } + + if (it2.isMember("material")) + { + MaterialInfo mat; + if (!mat.find(it2["material"].asString())) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition material for imported manager order: " << it2["material"].asString() << std::endl; + + continue; + } + condition->mat_type = mat.type; + condition->mat_index = mat.index; + } + + if (it2.isMember("bearing")) + { + std::string bearing(it2["bearing"].asString()); + auto found = std::find_if(world->raws.inorganics.begin(), world->raws.inorganics.end(), [bearing](df::inorganic_raw *raw) -> bool { return raw->id == bearing; }); + if (found == world->raws.inorganics.end()) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition inorganic bearing type for imported manager order: " << it2["bearing"].asString() << std::endl; + + continue; + } + condition->inorganic_bearing = found - world->raws.inorganics.begin(); + } + + if (it2.isMember("reaction_class")) + { + condition->reaction_class = it2["reaction_class"].asString(); + } + + if (it2.isMember("reaction_product")) + { + condition->has_material_reaction_product = it2["reaction_product"].asString(); + } + + if (it2.isMember("tool")) + { + if (!find_enum_item(&condition->has_tool_use, it2["tool"].asString()) || condition->has_tool_use == tool_uses::NONE) + { + delete condition; + + out << COLOR_YELLOW << "Invalid item condition tool use for imported manager order: " << it2["tool"].asString() << std::endl; + + continue; + } + } + + // TODO: anon_1, anon_2, anon_3 + + order->item_conditions.push_back(condition); + } + } + + if (it.isMember("order_conditions")) + { + for (auto & it2 : it["order_conditions"]) + { + df::manager_order_condition_order *condition = new df::manager_order_condition_order(); + + int32_t id = it2["order"].asInt(); + if (id == it["id"].asInt() || !id_mapping.count(id)) + { + delete condition; + + out << COLOR_YELLOW << "Missing order condition target for imported manager order: " << it2["order"].asInt() << std::endl; + + continue; + } + condition->order_id = id_mapping.at(id); + + if (!find_enum_item(&condition->condition, it2["condition"].asString())) + { + delete condition; + + out << COLOR_YELLOW << "Invalid order condition type for imported manager order: " << it2["condition"].asString() << std::endl; + + continue; + } + + // TODO: anon_1 + + order->order_conditions.push_back(condition); + } + } + + // TODO: anon_1 + + world->manager_orders.push_back(order); + } + + return CR_OK; +} + +static command_result orders_clear_command(color_ostream & out) +{ + CoreSuspender suspend; + + for (auto order : world->manager_orders) + { + for (auto condition : order->item_conditions) + { + delete condition; + } + for (auto condition : order->order_conditions) + { + delete condition; + } + if (order->anon_1) + { + for (auto anon_1 : *order->anon_1) + { + delete anon_1; + } + delete order->anon_1; + } + + delete order; + } + + out << "Deleted " << world->manager_orders.size() << " manager orders." << std::endl; + + world->manager_orders.clear(); + + return CR_OK; +} diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp new file mode 100644 index 000000000..6007d8401 --- /dev/null +++ b/plugins/pathable.cpp @@ -0,0 +1,90 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "DataFuncs.h" +#include "DataIdentity.h" +#include "Export.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "df/world.h" + +using namespace DFHack; + +DFHACK_PLUGIN("pathable"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(window_x); +REQUIRE_GLOBAL(window_y); +REQUIRE_GLOBAL(window_z); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + return CR_OK; +} + +static void paintScreen(df::coord cursor, bool skip_unrevealed = false) +{ + auto dims = Gui::getDwarfmodeViewDims(); + for (int y = dims.map_y1; y <= dims.map_y2; y++) + { + for (int x = dims.map_x1; x <= dims.map_x2; x++) + { + Screen::Pen cur_tile = Screen::readTile(x, y, true); + if (!cur_tile.valid()) + continue; + + df::coord map_pos( + *window_x + x - dims.map_x1, + *window_y + y - dims.map_y1, + *window_z + ); + + // Keep yellow cursor + if (map_pos == cursor) + continue; + + if (map_pos.x < 0 || map_pos.x >= world->map.x_count || + map_pos.y < 0 || map_pos.y >= world->map.y_count || + map_pos.z < 0 || map_pos.z >= world->map.z_count) + { + continue; + } + + if (skip_unrevealed && !Maps::isTileVisible(map_pos)) + continue; + + int color = Maps::canWalkBetween(cursor, map_pos) ? COLOR_GREEN : COLOR_RED; + + if (cur_tile.fg && cur_tile.ch != ' ') + { + cur_tile.fg = color; + cur_tile.bg = 0; + } + else + { + cur_tile.fg = 0; + cur_tile.bg = color; + } + + cur_tile.bold = false; + + if (cur_tile.tile) + cur_tile.tile_mode = Screen::Pen::CharColor; + + Screen::paintTile(cur_tile, x, y, true); + } + } +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(paintScreen), + DFHACK_LUA_END +}; + diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp index c059db4c3..d35906a05 100644 --- a/plugins/petcapRemover.cpp +++ b/plugins/petcapRemover.cpp @@ -67,7 +67,7 @@ void impregnateMany() { if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) continue; popcount[unit->race]++; - if ( unit->relations.pregnancy_genes ) { + if ( unit->pregnancy_genes ) { //already pregnant //for player convenience and population stability, count the fetus toward the population cap popcount[unit->race]++; @@ -122,14 +122,14 @@ void impregnateMany() { bool impregnate(df::unit* female, df::unit* male) { if ( !female || !male ) return false; - if ( female->relations.pregnancy_genes ) + if ( female->pregnancy_genes ) return false; df::unit_genes* preg = new df::unit_genes; *preg = male->appearance.genes; - female->relations.pregnancy_genes = preg; - female->relations.pregnancy_timer = pregtime; //300000 for dwarves - female->relations.pregnancy_caste = male->caste; + female->pregnancy_genes = preg; + female->pregnancy_timer = pregtime; //300000 for dwarves + female->pregnancy_caste = male->caste; return true; } diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 5d84ca96e..a15377279 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -13,7 +13,9 @@ #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" + #include "df/plant.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp index 039b133f2..c61c3c8ee 100644 --- a/plugins/power-meter.cpp +++ b/plugins/power-meter.cpp @@ -1,13 +1,3 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -15,22 +5,34 @@ #include #include -#include -#include "df/graphic.h" +#include "Console.h" +#include "Core.h" +#include "Error.h" +#include "Export.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "TileTypes.h" +#include "VTableInterpose.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "modules/World.h" + +#include "df/building_drawbuffer.h" #include "df/building_trapst.h" -#include "df/builtin_mats.h" -#include "df/world.h" #include "df/buildings_other_id.h" +#include "df/builtin_mats.h" +#include "df/flow_info.h" +#include "df/graphic.h" #include "df/machine.h" #include "df/machine_info.h" -#include "df/building_drawbuffer.h" +#include "df/report.h" +#include "df/tile_designation.h" #include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" #include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" - -#include "MiscUtils.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" using std::vector; using std::string; diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 0dae3d6eb..5f4a59ceb 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -1,33 +1,37 @@ // Just show some position data -#include -#include #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include using namespace std; -#include "Core.h" #include "Console.h" +#include "Core.h" #include "Export.h" +#include "MiscUtils.h" #include "PluginManager.h" -#include "modules/Units.h" -#include "df/unit_inventory_item.h" -#include "df/building_nest_boxst.h" -#include "modules/Maps.h" + +#include "modules/Buildings.h" #include "modules/Gui.h" -#include "modules/Materials.h" #include "modules/MapCache.h" -#include "modules/Buildings.h" -#include "MiscUtils.h" +#include "modules/Maps.h" +#include "modules/Materials.h" +#include "modules/Units.h" -#include "df/world.h" -#include "df/world_raws.h" +#include "df/block_square_event_grassst.h" +#include "df/block_square_event_world_constructionst.h" #include "df/building_def.h" +#include "df/building_nest_boxst.h" #include "df/region_map_entry.h" +#include "df/unit_inventory_item.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_raws.h" using std::vector; using std::string; @@ -115,6 +119,7 @@ void describeTile(color_ostream &out, df::tiletype tiletype) out.print("%d", tiletype); if(tileName(tiletype)) out.print(" = %s",tileName(tiletype)); + out.print(" (%s)", ENUM_KEY_STR(tiletype, tiletype).c_str()); out.print("\n"); df::tiletype_shape shape = tileShape(tiletype); @@ -198,6 +203,7 @@ command_result df_probe (color_ostream &out, vector & parameters) df::tiletype tiletype = mc.tiletypeAt(cursor); df::tile_designation &des = block.designation[tileX][tileY]; df::tile_occupancy &occ = block.occupancy[tileX][tileY]; + uint8_t fog_of_war = block.fog_of_war[tileX][tileY]; /* if(showDesig) { @@ -303,6 +309,9 @@ command_result df_probe (color_ostream &out, vector & parameters) if(des.bits.water_stagnant) out << "stagnant" << endl; + out.print("%-16s= %s\n", "dig", ENUM_KEY_STR(tile_dig_designation, des.bits.dig).c_str()); + out.print("%-16s= %s\n", "traffic", ENUM_KEY_STR(tile_traffic, des.bits.traffic).c_str()); + #define PRINT_FLAG( FIELD, BIT ) out.print("%-16s= %c\n", #BIT , ( FIELD.bits.BIT ? 'Y' : ' ' ) ) PRINT_FLAG( des, hidden ); PRINT_FLAG( des, light ); @@ -311,6 +320,7 @@ command_result df_probe (color_ostream &out, vector & parameters) PRINT_FLAG( des, water_table ); PRINT_FLAG( des, rained ); PRINT_FLAG( occ, monster_lair); + out.print("%-16s= %d\n", "fog_of_war", fog_of_war); df::coord2d pc(blockX, blockY); @@ -391,9 +401,9 @@ command_result df_bprobe (color_ostream &out, vector & parameters) Buildings::t_building building; if (!Buildings::Read(i, building)) continue; - if (!(building.x1 <= cursor->x && cursor->x <= building.x2 && - building.y1 <= cursor->y && cursor->y <= building.y2 && - building.z == cursor->z)) + if (int32_t(building.x1) > cursor->x || cursor->x > int32_t(building.x2) || + int32_t(building.y1) > cursor->y || cursor->y > int32_t(building.y2) || + int32_t(building.z) != cursor->z) continue; string name; building.origin->getName(&name); diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 522630afe..fd2d959eb 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -152,7 +152,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool for (MatSorter::const_iterator it = sorting_vector.begin(); it != sorting_vector.end(); ++it) { - if(it->first >= materials.size()) + if(size_t(it->first) >= materials.size()) { con << "Bad index: " << it->first << " out of " << materials.size() << endl; @@ -177,16 +177,21 @@ void printVeins(color_ostream &con, MatMap &mat_map, MatMap gems; MatMap rest; - for (MatMap::const_iterator it = mat_map.begin(); it != mat_map.end(); ++it) + for (const auto &kv : mat_map) { - df::inorganic_raw *gloss = world->raws.inorganics[it->first]; + df::inorganic_raw *gloss = vector_get(world->raws.inorganics, kv.first); + if (!gloss) + { + con.printerr("invalid material gloss: %hi\n", kv.first); + continue; + } if (gloss->material.isGem()) - gems[it->first] = it->second; + gems[kv.first] = kv.second; else if (gloss->isOre()) - ores[it->first] = it->second; + ores[kv.first] = kv.second; else - rest[it->first] = it->second; + rest[kv.first] = kv.second; } con << "Ores:" << std::endl; @@ -721,7 +726,7 @@ command_result prospector (color_ostream &con, vector & parameters) } if (showSlade && blockFeatureGlobal.type != -1 && des.bits.feature_global - && blockFeatureGlobal.type == feature_type::feature_underworld_from_layer + && blockFeatureGlobal.type == feature_type::underworld_from_layer && blockFeatureGlobal.main_material == 0) // stone { layerMats[blockFeatureGlobal.sub_material].add(global_z); @@ -747,7 +752,7 @@ command_result prospector (color_ostream &con, vector & parameters) for (PlantList::const_iterator it = plants->begin(); it != plants->end(); it++) { const df::plant & plant = *(*it); - if (plant.pos.z != z) + if (uint32_t(plant.pos.z) != z) continue; df::coord2d loc(plant.pos.x, plant.pos.y); loc = loc % 16; diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto new file mode 100644 index 000000000..7d3d128d6 --- /dev/null +++ b/plugins/proto/AdventureControl.proto @@ -0,0 +1,105 @@ +package AdventureControl; + +//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization +option optimize_for = LITE_RUNTIME; + +import "RemoteFortressReader.proto"; + +enum AdvmodeMenu +{ + Default = 0; + Look = 1; + ConversationAddress = 2; + ConversationSelect = 3; + ConversationSpeak = 4; + Inventory = 5; + Drop = 6; + ThrowItem = 7; + Wear = 8; + Remove = 9; + Interact = 10; + Put = 11; + PutContainer = 12; + Eat = 13; + ThrowAim = 14; + Fire = 15; + Get = 16; + Unk17 = 17; + CombatPrefs = 18; + Companions = 19; + MovementPrefs = 20; + SpeedPrefs = 21; + InteractAction = 22; + MoveCarefully = 23; + Announcements = 24; + UseBuilding = 25; + Travel = 26; + Unk27 = 27; + Unk28 = 28; + SleepConfirm = 29; + SelectInteractionTarget = 30; + Unk31 = 31; + Unk32 = 32; + FallAction = 33; + ViewTracks = 34; + Jump = 35; + Unk36 = 36; + AttackConfirm = 37; + AttackType = 38; + AttackBodypart = 39; + AttackStrike = 40; + Unk41 = 41; + Unk42 = 42; + DodgeDirection = 43; + Unk44 = 44; + Unk45 = 45; + Build = 46; +} + +enum CarefulMovementType +{ + DEFAULT_MOVEMENT = 0; + RELEASE_ITEM_HOLD = 1; + RELEASE_TILE_HOLD = 2; + ATTACK_CREATURE = 3; + HOLD_TILE = 4; + MOVE = 5; + CLIMB = 6; + HOLD_ITEM = 7; + BUILDING_INTERACT = 8; + ITEM_INTERACT = 9; + ITEM_INTERACT_GUIDE = 10; + ITEM_INTERACT_RIDE = 11; + ITEM_INTERACT_PUSH = 12; +} + +enum MiscMoveType +{ + SET_CLIMB = 0; + SET_STAND = 1; + SET_CANCEL = 2; +} + +message MoveCommandParams +{ + optional RemoteFortressReader.Coord direction = 1; +} + +message MovementOption +{ + optional RemoteFortressReader.Coord dest = 1; + optional RemoteFortressReader.Coord source = 2; + optional RemoteFortressReader.Coord grab = 3; + optional CarefulMovementType movement_type = 4; +} + +message MenuContents +{ + optional AdvmodeMenu current_menu = 1; + repeated MovementOption movements = 2; +} + +message MiscMoveParams +{ + optional MiscMoveType type = 1; +} \ No newline at end of file diff --git a/plugins/proto/ItemdefInstrument.proto b/plugins/proto/ItemdefInstrument.proto new file mode 100644 index 000000000..c92491ace --- /dev/null +++ b/plugins/proto/ItemdefInstrument.proto @@ -0,0 +1,104 @@ +package ItemdefInstrument; + +//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization +option optimize_for = LITE_RUNTIME; + +message InstrumentFlags + { + optional bool indefinite_pitch = 1; + optional bool placed_as_building = 2; + optional bool metal_mat = 3; + optional bool stone_mat = 4; + optional bool wood_mat = 5; + optional bool glass_mat = 6; + optional bool ceramic_mat = 7; + optional bool shell_mat = 8; + optional bool bone_mat = 9; +} + +enum PitchChoiceType +{ + MEMBRANE_POSITION = 0; + SUBPART_CHOICE = 1; + KEYBOARD = 2; + STOPPING_FRET = 3; + STOPPING_AGAINST_BODY = 4; + STOPPING_HOLE = 5; + STOPPING_HOLE_KEY = 6; + SLIDE = 7; + HARMONIC_SERIES = 8; + VALVE_ROUTES_AIR = 9; + BP_IN_BELL = 10; + FOOT_PEDALS = 11; +} + +enum SoundProductionType +{ + PLUCKED_BY_BP = 0; + PLUCKED = 1; + BOWED = 2; + STRUCK_BY_BP = 3; + STRUCK = 4; + VIBRATE_BP_AGAINST_OPENING = 5; + BLOW_AGAINST_FIPPLE = 6; + BLOW_OVER_OPENING_SIDE = 7; + BLOW_OVER_OPENING_END = 8; + BLOW_OVER_SINGLE_REED = 9; + BLOW_OVER_DOUBLE_REED = 10; + BLOW_OVER_FREE_REED = 11; + STRUCK_TOGETHER = 12; + SHAKEN = 13; + SCRAPED = 14; + FRICTION = 15; + RESONATOR = 16; + BAG_OVER_REED = 17; + AIR_OVER_REED = 18; + AIR_OVER_FREE_REED = 19; + AIR_AGAINST_FIPPLE = 20; +} + +enum TuningType +{ + PEGS = 0; + ADJUSTABLE_BRIDGES = 1; + CROOKS = 2; + TIGHTENING = 3; + LEVERS = 4; +} + +message InstrumentPiece +{ + optional string type = 1; + optional string id = 2; + optional string name = 3; + optional string name_plural = 4; +} + +message InstrumentRegister +{ + optional int32 pitch_range_min = 1; + optional int32 pitch_range_max = 2; +} + +message InstrumentDef +{ + optional InstrumentFlags flags = 1; + optional int32 size = 2; + optional int32 value = 3; + optional int32 material_size = 4; + repeated InstrumentPiece pieces = 5; + optional int32 pitch_range_min = 6; + optional int32 pitch_range_max = 7; + optional int32 volume_mb_min = 8; + optional int32 volume_mb_max = 9; + repeated SoundProductionType sound_production = 10; + repeated string sound_production_parm1 = 11; + repeated string sound_production_parm2 = 12; + repeated PitchChoiceType pitch_choice = 13; + repeated string pitch_choice_parm1 = 14; + repeated string pitch_choice_parm2 = 15; + repeated TuningType tuning = 16; + repeated string tuning_parm = 17; + repeated InstrumentRegister registers = 18; + optional string description = 19; +} \ No newline at end of file diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 8b4cb66f2..3298d42fb 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -3,6 +3,8 @@ package RemoteFortressReader; //Attempts to provide a complete framework for reading everything from a fortress needed for vizualization option optimize_for = LITE_RUNTIME; +import "ItemdefInstrument.proto"; + //We use shapes, etc, because the actual tiletypes may differ between DF versions. enum TiletypeShape { @@ -98,6 +100,7 @@ enum BuildingDirection EAST = 1; SOUTH = 2; WEST = 3; + NONE = 4; } enum TileDigDesignation @@ -117,6 +120,56 @@ enum TileDigDesignation UP_STAIR_DIG = 6; } +enum HairStyle +{ + UNKEMPT = -1; + NEATLY_COMBED = 0; + BRAIDED = 1; + DOUBLE_BRAID = 2; + PONY_TAILS = 3; + CLEAN_SHAVEN = 4; +} + +enum InventoryMode +{ + Hauled = 0; + /** + * also shield, crutch + */ + Weapon = 1; + /** + * quiver + */ + Worn = 2; + Piercing = 3; + /** + * attached to clothing + */ + Flask = 4; + /** + * e.g. bandage + */ + WrappedAround = 5; + StuckIn = 6; + /** + * string descr like Worn + */ + InMouth = 7; + /** + * Left shoulder, right shoulder, or head, selected randomly using pet_seed + */ + Pet = 8; + SewnInto = 9; + Strapped = 10; +} + +message Coord +{ + optional int32 x = 1; + optional int32 y = 2; + optional int32 z = 3; +} + message Tiletype { required int32 id = 1; @@ -143,6 +196,12 @@ message BuildingExtents repeated int32 extents = 5; } +message BuildingItem +{ + optional Item item = 1; + optional int32 mode = 2; +} + message BuildingInstance { required int32 index = 1; @@ -158,6 +217,8 @@ message BuildingInstance optional bool is_room = 11; optional BuildingExtents room = 12; optional BuildingDirection direction = 13; //Doesn't mean anything for most buildings + repeated BuildingItem items = 14; + optional int32 active = 15; } message RiverEdge @@ -176,6 +237,51 @@ message RiverTile optional RiverEdge west = 4; } +enum MatterState +{ + Solid = 0; + Liquid = 1; + Gas = 2; + Powder = 3; + Paste = 4; + Pressed = 5; +} + +message Spatter +{ + optional MatPair material = 1; + optional int32 amount = 2; + optional MatterState state = 3; + optional MatPair item = 4; +} + +message SpatterPile +{ + repeated Spatter spatters = 1; +} + +message Item +{ + optional int32 id = 1; + optional Coord pos = 2; + optional uint32 flags1 = 3; + optional uint32 flags2 = 4; + optional MatPair type = 5; + optional MatPair material = 6; + optional ColorDefinition dye = 7; + optional int32 stack_size = 8; + optional float subpos_x = 9; + optional float subpos_y = 10; + optional float subpos_z = 11; + optional bool projectile = 12; + optional float velocity_x = 13; + optional float velocity_y = 14; + optional float velocity_z = 15; + optional int32 volume = 16; + repeated ItemImprovement improvements = 17; + optional ArtImage image = 18; +} + message MapBlock { required int32 map_x = 1; @@ -202,6 +308,12 @@ message MapBlock repeated int32 tree_y = 22; repeated int32 tree_z = 23; repeated TileDigDesignation tile_dig_designation = 24; + repeated SpatterPile spatterPile = 25; + repeated Item items = 26; + repeated bool tile_dig_designation_marker = 27; + repeated bool tile_dig_designation_auto = 28; + repeated int32 grass_percent = 29; + repeated FlowInfo flows = 30; } message MatPair { @@ -220,6 +332,7 @@ message MaterialDefinition{ optional string id = 2; optional string name = 3; optional ColorDefinition state_color = 4; //Simplifying colors to assume room temperature. + optional ItemdefInstrument.InstrumentDef instrument = 5; } message BuildingType @@ -243,6 +356,12 @@ message MaterialList{ repeated MaterialDefinition material_list = 1; } +message Hair +{ + optional int32 length = 1; + optional HairStyle style = 2; +} + message BodySizeInfo { optional int32 size_cur = 1; @@ -253,6 +372,24 @@ message BodySizeInfo optional int32 length_base = 6; /*!< (size_base*10000)^0.333 */ } +message UnitAppearance +{ + repeated int32 body_modifiers = 1; + repeated int32 bp_modifiers = 2; + optional int32 size_modifier = 3; + repeated int32 colors = 4; + optional Hair hair = 5; + optional Hair beard = 6; + optional Hair moustache = 7; + optional Hair sideburns = 8; +} + +message InventoryItem +{ + optional InventoryMode mode = 1; + optional Item item = 2; +} + message UnitDefinition { required int32 id = 1; @@ -270,6 +407,14 @@ message UnitDefinition optional string name = 13; optional int32 blood_max = 14; optional int32 blood_count = 15; + optional UnitAppearance appearance = 16; + optional int32 profession_id = 17; + repeated string noble_positions = 18; + optional int32 rider_id = 19; + repeated InventoryItem inventory = 20; + optional float subpos_x = 21; + optional float subpos_y = 22; + optional float subpos_z = 23; } message UnitList @@ -293,6 +438,8 @@ message BlockList repeated MapBlock map_blocks = 1; optional int32 map_x = 2; optional int32 map_y = 3; + repeated Engraving engravings = 4; + repeated Wave ocean_waves = 5; } message PlantDef @@ -398,11 +545,175 @@ message WorldMap optional int32 cur_year_tick = 21; optional WorldPoles world_poles = 22; repeated RiverTile river_tiles = 23; + repeated int32 water_elevation = 24; + repeated RegionTile region_tiles = 25; +} + +enum SiteRealizationBuildingType +{ + cottage_plot = 0; + castle_wall = 1; + castle_tower = 2; + castle_courtyard = 3; + house = 4; + temple = 5; + tomb = 6; + shop_house = 7; + warehouse = 8; + market_square = 9; + pasture = 10; + waste = 11; + courtyard = 12; + well = 13; + vault = 14; + great_tower = 15; + trenches = 16; + tree_house = 17; + hillock_house = 18; + mead_hall = 19; + fortress_entrance = 20; + library = 21; + tavern = 22; +} + +message SiteRealizationBuildingWall +{ + optional int32 start_x = 1; + optional int32 start_y = 2; + optional int32 start_z = 3; + optional int32 end_x = 4; + optional int32 end_y = 5; + optional int32 end_z = 6; +} + +message SiteRealizationBuildingTower +{ + optional int32 roof_z = 1; + optional bool round = 2; + optional bool goblin = 3; +} + +message TrenchSpoke +{ + optional int32 mound_start = 1; + optional int32 trench_start = 2; + optional int32 trench_end = 3; + optional int32 mound_end = 4; +} + +message SiteRealizationBuildingTrenches +{ + repeated TrenchSpoke spokes = 1; +} + +message SiteRealizationBuilding +{ + optional int32 id = 1; + optional SiteRealizationBuildingType type = 2; + optional int32 min_x = 3; + optional int32 min_y = 4; + optional int32 max_x = 5; + optional int32 max_y = 6; + optional MatPair material = 7; + optional SiteRealizationBuildingWall wall_info = 8; + optional SiteRealizationBuildingTower tower_info = 9; + optional SiteRealizationBuildingTrenches trench_info = 10; +} + +message RegionTile +{ + optional int32 elevation = 1; + optional int32 rainfall = 2; + optional int32 vegetation = 3; + optional int32 temperature = 4; + optional int32 evilness = 5; + optional int32 drainage = 6; + optional int32 volcanism = 7; + optional int32 savagery = 8; + optional int32 salinity = 9; + optional RiverTile river_tiles = 10; + optional int32 water_elevation = 11; + optional MatPair surface_material = 12; + repeated MatPair plant_materials = 13; + repeated SiteRealizationBuilding buildings = 14; + repeated MatPair stone_materials = 15; + repeated MatPair tree_materials = 16; + optional int32 snow = 17; +} + +message RegionMap +{ + optional int32 map_x = 1; + optional int32 map_y = 2; + optional string name = 3; + optional string name_english = 4; + repeated RegionTile tiles = 5; } message RegionMaps { repeated WorldMap world_maps = 1; + repeated RegionMap region_maps = 2; +} + +enum PatternType +{ + MONOTONE = 0; + STRIPES = 1; + IRIS_EYE = 2; + SPOTS = 3; + PUPIL_EYE = 4; + MOTTLED = 5; +} + +message PatternDescriptor +{ + optional string id = 1; + repeated ColorDefinition colors = 2; + optional PatternType pattern = 3; +} + +message ColorModifierRaw +{ + repeated PatternDescriptor patterns = 1; + repeated int32 body_part_id = 2; + repeated int32 tissue_layer_id = 3; + optional int32 start_date = 4; + optional int32 end_date = 5; + optional string part = 6; +} + +message BodyPartLayerRaw +{ + optional string layer_name = 1; + optional int32 tissue_id = 2; + optional int32 layer_depth = 3; + repeated int32 bp_modifiers = 4; +} + +message BodyPartRaw +{ + optional string token = 1; + optional string category = 2; + optional int32 parent = 3; + repeated bool flags = 4; + repeated BodyPartLayerRaw layers = 5; + optional int32 relsize = 6; +} + +message BpAppearanceModifier +{ + optional string type = 1; + optional int32 mod_min = 2; + optional int32 mod_max = 3; +} + +message TissueRaw +{ + optional string id = 1; + optional string name = 2; + optional MatPair material = 3; + optional string subordinate_to_tissue = 4; } message CasteRaw @@ -413,6 +724,16 @@ message CasteRaw repeated string baby_name = 4; repeated string child_name = 5; optional int32 gender = 6; + repeated BodyPartRaw body_parts = 7; + optional int32 total_relsize = 8; + repeated BpAppearanceModifier modifiers = 9; + repeated int32 modifier_idx = 10; + repeated int32 part_idx = 11; + repeated int32 layer_idx = 12; + repeated BpAppearanceModifier body_appearance_modifiers = 13; + repeated ColorModifierRaw color_modifiers = 14; + optional string description = 15; + optional int32 adult_size = 16; } message CreatureRaw @@ -427,6 +748,7 @@ message CreatureRaw optional ColorDefinition color = 8; optional int32 adultsize = 9; repeated CasteRaw caste = 10; + repeated TissueRaw tissues = 11; } message CreatureRawList @@ -516,4 +838,234 @@ message KeyboardEvent optional uint32 sym = 5; optional uint32 mod = 6; optional uint32 unicode = 7; +} + +message DigCommand +{ + optional TileDigDesignation designation = 1; + repeated Coord locations = 2; +} + +message SingleBool +{ + optional bool Value = 1; +} + +message VersionInfo +{ + optional string dwarf_fortress_version = 1; + optional string dfhack_version = 2; + optional string remote_fortress_reader_version = 3; +} + +message ListRequest +{ + optional int32 list_start = 1; + optional int32 list_end = 2; +} + +message Report +{ + optional int32 type = 1; + optional string text = 2; + optional ColorDefinition color = 3; + optional int32 duration = 4; + optional bool continuation = 5; + optional bool unconscious = 6; + optional bool announcement = 7; + optional int32 repeat_count = 8; + optional Coord pos = 9; + optional int32 id = 10; + optional int32 year = 11; + optional int32 time = 12; +} + +message Status +{ + repeated Report reports = 1; +} + +message ShapeDescriptior +{ + optional string id = 1; + optional int32 tile = 2; +} + +message Language +{ + repeated ShapeDescriptior shapes = 1; +} + +enum ImprovementType +{ + ART_IMAGE = 0; + COVERED = 1; + RINGS_HANGING = 2; + BANDS = 3; + SPIKES = 4; + ITEMSPECIFIC = 5; + THREAD = 6; + CLOTH = 7; + SEWN_IMAGE = 8; + PAGES = 9; + ILLUSTRATION = 10; + INSTRUMENT_PIECE = 11; + WRITING = 12; +} + +message ItemImprovement +{ + optional MatPair material = 1; + optional ImprovementType type = 2; + optional int32 shape = 3; + optional int32 specific_type= 4; + optional ArtImage image = 5; +} + +enum ArtImageElementType +{ + IMAGE_CREATURE = 0; + IMAGE_PLANT = 1; + IMAGE_TREE = 2; + IMAGE_SHAPE = 3; + IMAGE_ITEM = 4; +} + +message ArtImageElement +{ + optional int32 count = 1; + optional ArtImageElementType type = 2; + optional MatPair creature_item = 3; + optional MatPair material = 5; + optional int32 id = 6; +} + +enum ArtImagePropertyType +{ + TRANSITIVE_VERB = 0; + INTRANSITIVE_VERB = 1; +} + +message ArtImageProperty +{ + optional int32 subject = 1; + optional int32 object = 2; + optional ArtImageVerb verb = 3; + optional ArtImagePropertyType type = 4; +} + +message ArtImage +{ + repeated ArtImageElement elements = 1; + optional MatPair id = 2; + repeated ArtImageProperty properties = 3; +} + +message Engraving +{ + optional Coord pos = 1; + optional int32 quality = 2; + optional int32 tile = 3; + optional ArtImage image = 4; + optional bool floor = 5; + optional bool west = 6; + optional bool east = 7; + optional bool north = 8; + optional bool south = 9; + optional bool hidden = 10; + optional bool northwest = 11; + optional bool northeast = 12; + optional bool southwest = 13; + optional bool southeast = 14; +} + +enum ArtImageVerb +{ + VERB_WITHERING = 0; + VERB_SURROUNDEDBY = 1; + VERB_MASSACRING = 2; + VERB_FIGHTING = 3; + VERB_LABORING = 4; + VERB_GREETING = 5; + VERB_REFUSING = 6; + VERB_SPEAKING = 7; + VERB_EMBRACING = 8; + VERB_STRIKINGDOWN = 9; + VERB_MENACINGPOSE = 10; + VERB_TRAVELING = 11; + VERB_RAISING = 12; + VERB_HIDING = 13; + VERB_LOOKINGCONFUSED = 14; + VERB_LOOKINGTERRIFIED = 15; + VERB_DEVOURING = 16; + VERB_ADMIRING = 17; + VERB_BURNING = 18; + VERB_WEEPING = 19; + VERB_LOOKINGDEJECTED = 20; + VERB_CRINGING = 21; + VERB_SCREAMING = 22; + VERB_SUBMISSIVEGESTURE = 23; + VERB_FETALPOSITION = 24; + VERB_SMEAREDINTOSPIRAL = 25; + VERB_FALLING = 26; + VERB_DEAD = 27; + VERB_LAUGHING = 28; + VERB_LOOKINGOFFENDED = 29; + VERB_BEINGSHOT = 30; + VERB_PLAINTIVEGESTURE = 31; + VERB_MELTING = 32; + VERB_SHOOTING = 33; + VERB_TORTURING = 34; + VERB_COMMITTINGDEPRAVEDACT = 35; + VERB_PRAYING = 36; + VERB_CONTEMPLATING = 37; + VERB_COOKING = 38; + VERB_ENGRAVING = 39; + VERB_PROSTRATING = 40; + VERB_SUFFERING = 41; + VERB_BEINGIMPALED = 42; + VERB_BEINGCONTORTED = 43; + VERB_BEINGFLAYED = 44; + VERB_HANGINGFROM = 45; + VERB_BEINGMUTILATED = 46; + VERB_TRIUMPHANTPOSE = 47; +} + +enum FlowType +{ + Miasma = 0; + Steam = 1; + Mist = 2; + MaterialDust = 3; + MagmaMist = 4; + Smoke = 5; + Dragonfire = 6; + Fire = 7; + Web = 8; + MaterialGas = 9; + MaterialVapor = 10; + OceanWave = 11; + SeaFoam = 12; + ItemCloud = 13; + CampFire = -1; +} + +message FlowInfo +{ + optional int32 index = 1; + optional FlowType type = 2; + optional int32 density = 3; + optional Coord pos = 4; + optional Coord dest = 5; + optional bool expanding = 6; + optional bool reuse = 7; + optional int32 guide_id = 8; + optional MatPair material = 9; + optional MatPair item = 10; +} + +message Wave +{ + optional Coord dest = 1; + optional Coord pos = 2; } \ No newline at end of file diff --git a/plugins/proto/tmp/.gitignore b/plugins/proto/tmp/.gitignore new file mode 100644 index 000000000..75feca5b1 --- /dev/null +++ b/plugins/proto/tmp/.gitignore @@ -0,0 +1 @@ +*.pb.* diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp deleted file mode 100644 index 907e98969..000000000 --- a/plugins/remotefortressreader.cpp +++ /dev/null @@ -1,2168 +0,0 @@ -#define DF_VERSION 42004 - -// some headers required for a plugin. Nothing special, just the basics. -#include "Core.h" -#include -#include -#include - -// DF data structure definition headers -#include "DataDefs.h" -#include "df/world.h" -#include "df/ui.h" -#include "df/item.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/body_part_raw.h" -#include "df/historical_figure.h" - -#include "df/job_item.h" -#include "df/job_material_category.h" -#include "df/dfhack_material_category.h" -#include "df/matter_state.h" -#include "df/material_vec_ref.h" -#include "df/builtin_mats.h" -#include "df/map_block_column.h" -#include "df/plant.h" -#include "df/plant_raw_flags.h" -#if DF_VERSION > 40001 -#include "df/plant_tree_info.h" -#include "df/plant_tree_tile.h" -#include "df/plant_growth.h" -#include "df/plant_growth_print.h" -#endif -#include "df/itemdef.h" -#include "df/building_def_workshopst.h" -#include "df/building_def_furnacest.h" -#include "df/building_wellst.h" -#include "df/building_water_wheelst.h" -#include "df/building_screw_pumpst.h" -#include "df/building_axle_horizontalst.h" -#include "df/building_windmillst.h" -#include "df/building_siegeenginest.h" -#include "df/building_rollersst.h" -#include "df/building_bridgest.h" - -#include "df/descriptor_color.h" -#include "df/descriptor_pattern.h" -#include "df/descriptor_shape.h" - -#include "df/physical_attribute_type.h" -#include "df/mental_attribute_type.h" -#include "df/color_modifier_raw.h" - -#include "df/region_map_entry.h" -#include "df/world_region_details.h" -#include "df/army.h" -#include "df/army_flags.h" - -#include "df/unit.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" - -#include "df/enabler.h" -#include "df/graphic.h" - -#include "df/viewscreen_choose_start_sitest.h" - -//DFhack specific headers -#include "modules/Maps.h" -#include "modules/MapCache.h" -#include "modules/Materials.h" -#include "modules/Gui.h" -#include "modules/Translation.h" -#include "modules/Items.h" -#include "modules/Buildings.h" -#include "modules/Units.h" -#include "modules/World.h" -#include "TileTypes.h" -#include "MiscUtils.h" -#include "Hooks.h" -#include "SDL_events.h" -#include "SDL_keyboard.h" - -#include -#include - -#include "RemoteFortressReader.pb.h" - -#include "RemoteServer.h" - -using namespace DFHack; -using namespace df::enums; -using namespace RemoteFortressReader; -using namespace std; - -DFHACK_PLUGIN("RemoteFortressReader"); -#if DF_VERSION < 40024 -using namespace df::global; -#else -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(ui); -#endif - -// Here go all the command declarations... -// mostly to allow having the mandatory stuff on top of the file and commands on the bottom - -static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); -static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); -static command_result GetTiletypeList(color_ostream &stream, const EmptyMessage *in, TiletypeList *out); -static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out); -static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out); -static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in); -static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out); -static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out); -static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out); -static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); -static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); -static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); -static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); -static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out); -static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); -static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out); -static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out); -static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); -static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); - - -void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); -void FindChangedBlocks(); - -const char* growth_locations[] = { - "TWIGS", - "LIGHT_BRANCHES", - "HEAVY_BRANCHES", - "TRUNK", - "ROOTS", - "CAP", - "SAPLING", - "SHRUB" -}; -#define GROWTH_LOCATIONS_SIZE 8 - -// Mandatory init function. If you have some global state, create it here. -DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) -{ - //// Fill the command list with your commands. - //commands.push_back(PluginCommand( - // "isoworldremote", "Dump north-west embark tile to text file for debug purposes.", - // isoWorldRemote, false, /* true means that the command can't be used from non-interactive user interface */ - // // Extended help string. Used by CR_WRONG_USAGE and the help command: - // " This command does nothing at all.\n" - // "Example:\n" - // " isoworldremote\n" - // " Does nothing.\n" - //)); - return CR_OK; -} - -DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) -{ - RPCService *svc = new RPCService(); - svc->addFunction("GetMaterialList", GetMaterialList); - svc->addFunction("GetGrowthList", GetGrowthList); - svc->addFunction("GetBlockList", GetBlockList); - svc->addFunction("CheckHashes", CheckHashes); - svc->addFunction("GetTiletypeList", GetTiletypeList); - svc->addFunction("GetPlantList", GetPlantList); - svc->addFunction("GetUnitList", GetUnitList); - svc->addFunction("GetViewInfo", GetViewInfo); - svc->addFunction("GetMapInfo", GetMapInfo); - svc->addFunction("ResetMapHashes", ResetMapHashes); - svc->addFunction("GetItemList", GetItemList); - svc->addFunction("GetBuildingDefList", GetBuildingDefList); - svc->addFunction("GetWorldMap", GetWorldMap); - svc->addFunction("GetRegionMaps", GetRegionMaps); - svc->addFunction("GetCreatureRaws", GetCreatureRaws); - svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); - svc->addFunction("GetPlantRaws", GetPlantRaws); - svc->addFunction("CopyScreen", CopyScreen); - svc->addFunction("PassKeyboardEvent", PassKeyboardEvent); - return svc; -} - -// This is called right before the plugin library is removed from memory. -DFhackCExport command_result plugin_shutdown(color_ostream &out) -{ - // You *MUST* kill all threads you created before this returns. - // If everything fails, just return CR_FAILURE. Your plugin will be - // in a zombie state, but things won't crash. - return CR_OK; -} - -uint16_t fletcher16(uint8_t const *data, size_t bytes) -{ - uint16_t sum1 = 0xff, sum2 = 0xff; - - while (bytes) { - size_t tlen = bytes > 20 ? 20 : bytes; - bytes -= tlen; - do { - sum2 += sum1 += *data++; - } while (--tlen); - sum1 = (sum1 & 0xff) + (sum1 >> 8); - sum2 = (sum2 & 0xff) + (sum2 >> 8); - } - /* Second reduction step to reduce sums to 8 bits */ - sum1 = (sum1 & 0xff) + (sum1 >> 8); - sum2 = (sum2 & 0xff) + (sum2 >> 8); - return sum2 << 8 | sum1; -} - -void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) -{ - if (!df::global::enabler) - return; - - auto enabler = df::global::enabler; - - out->set_red((int)(enabler->ccolor[index][0] * 255)); - out->set_green((int)(enabler->ccolor[index][1] * 255)); - out->set_blue((int)(enabler->ccolor[index][2] * 255)); -} - -void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) -{ - int index = in[0] | (8 * in[2]); - ConvertDfColor(index, out); -} - -void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build) -{ - df::building * local_build = df::global::world->buildings.all[buildingIndex]; - remote_build->set_index(buildingIndex); - int minZ = local_build->z; - if (local_build->getType() == df::enums::building_type::Well) - { - df::building_wellst * well_building = virtual_cast(local_build); - if (well_building) - minZ = well_building->bucket_z; - } - remote_build->set_pos_x_min(local_build->x1); - remote_build->set_pos_y_min(local_build->y1); - remote_build->set_pos_z_min(minZ); - - remote_build->set_pos_x_max(local_build->x2); - remote_build->set_pos_y_max(local_build->y2); - remote_build->set_pos_z_max(local_build->z); - - auto buildingType = remote_build->mutable_building_type(); - auto type = local_build->getType(); - buildingType->set_building_type(type); - buildingType->set_building_subtype(local_build->getSubtype()); - buildingType->set_building_custom(local_build->getCustomType()); - - auto material = remote_build->mutable_material(); - material->set_mat_type(local_build->mat_type); - material->set_mat_index(local_build->mat_index); - - remote_build->set_building_flags(local_build->flags.whole); - remote_build->set_is_room(local_build->is_room); - if (local_build->is_room || local_build->getType() == df::enums::building_type::Civzone || local_build->getType() == df::enums::building_type::Stockpile) - { - auto room = remote_build->mutable_room(); - room->set_pos_x(local_build->room.x); - room->set_pos_y(local_build->room.y); - room->set_width(local_build->room.width); - room->set_height(local_build->room.height); - for (int i = 0; i < (local_build->room.width * local_build->room.height); i++) - { - room->add_extents(local_build->room.extents[i]); - } - } - - - switch (type) - { - case df::enums::building_type::NONE: - break; - case df::enums::building_type::Chair: - break; - case df::enums::building_type::Bed: - break; - case df::enums::building_type::Table: - break; - case df::enums::building_type::Coffin: - break; - case df::enums::building_type::FarmPlot: - break; - case df::enums::building_type::Furnace: - break; - case df::enums::building_type::TradeDepot: - break; - case df::enums::building_type::Shop: - break; - case df::enums::building_type::Door: - break; - case df::enums::building_type::Floodgate: - break; - case df::enums::building_type::Box: - break; - case df::enums::building_type::Weaponrack: - break; - case df::enums::building_type::Armorstand: - break; - case df::enums::building_type::Workshop: - break; - case df::enums::building_type::Cabinet: - break; - case df::enums::building_type::Statue: - break; - case df::enums::building_type::WindowGlass: - break; - case df::enums::building_type::WindowGem: - break; - case df::enums::building_type::Well: - break; - case df::enums::building_type::Bridge: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto direction = actual->direction; - switch (direction) - { - case df::building_bridgest::Retracting: - break; - case df::building_bridgest::Left: - remote_build->set_direction(WEST); - break; - case df::building_bridgest::Right: - remote_build->set_direction(EAST); - break; - case df::building_bridgest::Up: - remote_build->set_direction(NORTH); - break; - case df::building_bridgest::Down: - remote_build->set_direction(SOUTH); - break; - default: - break; - } - } - } - break; - case df::enums::building_type::RoadDirt: - break; - case df::enums::building_type::RoadPaved: - break; - case df::enums::building_type::SiegeEngine: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto facing = actual->facing; - switch (facing) - { - case df::building_siegeenginest::Left: - remote_build->set_direction(WEST); - break; - case df::building_siegeenginest::Up: - remote_build->set_direction(NORTH); - break; - case df::building_siegeenginest::Right: - remote_build->set_direction(EAST); - break; - case df::building_siegeenginest::Down: - remote_build->set_direction(SOUTH); - break; - default: - break; - } - } - } - break; - case df::enums::building_type::Trap: - break; - case df::enums::building_type::AnimalTrap: - break; - case df::enums::building_type::Support: - break; - case df::enums::building_type::ArcheryTarget: - break; - case df::enums::building_type::Chain: - break; - case df::enums::building_type::Cage: - break; - case df::enums::building_type::Stockpile: - break; - case df::enums::building_type::Civzone: - break; - case df::enums::building_type::Weapon: - break; - case df::enums::building_type::Wagon: - break; - case df::enums::building_type::ScrewPump: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto direction = actual->direction; - switch (direction) - { - case df::enums::screw_pump_direction::FromNorth: - remote_build->set_direction(NORTH); - break; - case df::enums::screw_pump_direction::FromEast: - remote_build->set_direction(EAST); - break; - case df::enums::screw_pump_direction::FromSouth: - remote_build->set_direction(SOUTH); - break; - case df::enums::screw_pump_direction::FromWest: - remote_build->set_direction(WEST); - break; - default: - break; - } - } - } - break; - case df::enums::building_type::Construction: - break; - case df::enums::building_type::Hatch: - break; - case df::enums::building_type::GrateWall: - break; - case df::enums::building_type::GrateFloor: - break; - case df::enums::building_type::BarsVertical: - break; - case df::enums::building_type::BarsFloor: - break; - case df::enums::building_type::GearAssembly: - break; - case df::enums::building_type::AxleHorizontal: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - if(actual->is_vertical) - remote_build->set_direction(NORTH); - else - remote_build->set_direction(EAST); - } - } - break; - case df::enums::building_type::AxleVertical: - break; - case df::enums::building_type::WaterWheel: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - if (actual->is_vertical) - remote_build->set_direction(NORTH); - else - remote_build->set_direction(EAST); - } - } - break; - case df::enums::building_type::Windmill: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - if (actual->orient_x < 0) - remote_build->set_direction(WEST); - else if (actual->orient_x > 0) - remote_build->set_direction(EAST); - else if (actual->orient_y < 0) - remote_build->set_direction(NORTH); - else if (actual->orient_y > 0) - remote_build->set_direction(SOUTH); - else - remote_build->set_direction(WEST); - } - } - break; - case df::enums::building_type::TractionBench: - break; - case df::enums::building_type::Slab: - break; - case df::enums::building_type::Nest: - break; - case df::enums::building_type::NestBox: - break; - case df::enums::building_type::Hive: - break; - case df::enums::building_type::Rollers: - { - auto actual = strict_virtual_cast(local_build); - if (actual) - { - auto direction = actual->direction; - switch (direction) - { - case df::enums::screw_pump_direction::FromNorth: - remote_build->set_direction(NORTH); - break; - case df::enums::screw_pump_direction::FromEast: - remote_build->set_direction(EAST); - break; - case df::enums::screw_pump_direction::FromSouth: - remote_build->set_direction(SOUTH); - break; - case df::enums::screw_pump_direction::FromWest: - remote_build->set_direction(WEST); - break; - default: - break; - } - } - } - break; - default: - break; - } -} - -RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) -{ - switch (material) - { - case df::enums::tiletype_material::NONE: - return RemoteFortressReader::NO_MATERIAL; - break; - case df::enums::tiletype_material::AIR: - return RemoteFortressReader::AIR; - break; - case df::enums::tiletype_material::SOIL: - return RemoteFortressReader::SOIL; - break; - case df::enums::tiletype_material::STONE: - return RemoteFortressReader::STONE; - break; - case df::enums::tiletype_material::FEATURE: - return RemoteFortressReader::FEATURE; - break; - case df::enums::tiletype_material::LAVA_STONE: - return RemoteFortressReader::LAVA_STONE; - break; - case df::enums::tiletype_material::MINERAL: - return RemoteFortressReader::MINERAL; - break; - case df::enums::tiletype_material::FROZEN_LIQUID: - return RemoteFortressReader::FROZEN_LIQUID; - break; - case df::enums::tiletype_material::CONSTRUCTION: - return RemoteFortressReader::CONSTRUCTION; - break; - case df::enums::tiletype_material::GRASS_LIGHT: - return RemoteFortressReader::GRASS_LIGHT; - break; - case df::enums::tiletype_material::GRASS_DARK: - return RemoteFortressReader::GRASS_DARK; - break; - case df::enums::tiletype_material::GRASS_DRY: - return RemoteFortressReader::GRASS_DRY; - break; - case df::enums::tiletype_material::GRASS_DEAD: - return RemoteFortressReader::GRASS_DEAD; - break; - case df::enums::tiletype_material::PLANT: - return RemoteFortressReader::PLANT; - break; - case df::enums::tiletype_material::HFS: - return RemoteFortressReader::HFS; - break; - case df::enums::tiletype_material::CAMPFIRE: - return RemoteFortressReader::CAMPFIRE; - break; - case df::enums::tiletype_material::FIRE: - return RemoteFortressReader::FIRE; - break; - case df::enums::tiletype_material::ASHES: - return RemoteFortressReader::ASHES; - break; - case df::enums::tiletype_material::MAGMA: - return RemoteFortressReader::MAGMA; - break; - case df::enums::tiletype_material::DRIFTWOOD: - return RemoteFortressReader::DRIFTWOOD; - break; - case df::enums::tiletype_material::POOL: - return RemoteFortressReader::POOL; - break; - case df::enums::tiletype_material::BROOK: - return RemoteFortressReader::BROOK; - break; - case df::enums::tiletype_material::RIVER: - return RemoteFortressReader::RIVER; - break; -#if DF_VERSION > 40001 - case df::enums::tiletype_material::ROOT: - return RemoteFortressReader::ROOT; - break; - case df::enums::tiletype_material::TREE: - return RemoteFortressReader::TREE_MATERIAL; - break; - case df::enums::tiletype_material::MUSHROOM: - return RemoteFortressReader::MUSHROOM; - break; - case df::enums::tiletype_material::UNDERWORLD_GATE: - return RemoteFortressReader::UNDERWORLD_GATE; - break; -#endif - default: - return RemoteFortressReader::NO_MATERIAL; - break; - } - return RemoteFortressReader::NO_MATERIAL; -} - -RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special special) -{ - switch (special) - { - case df::enums::tiletype_special::NONE: - return RemoteFortressReader::NO_SPECIAL; - break; - case df::enums::tiletype_special::NORMAL: - return RemoteFortressReader::NORMAL; - break; - case df::enums::tiletype_special::RIVER_SOURCE: - return RemoteFortressReader::RIVER_SOURCE; - break; - case df::enums::tiletype_special::WATERFALL: - return RemoteFortressReader::WATERFALL; - break; - case df::enums::tiletype_special::SMOOTH: - return RemoteFortressReader::SMOOTH; - break; - case df::enums::tiletype_special::FURROWED: - return RemoteFortressReader::FURROWED; - break; - case df::enums::tiletype_special::WET: - return RemoteFortressReader::WET; - break; - case df::enums::tiletype_special::DEAD: - return RemoteFortressReader::DEAD; - break; - case df::enums::tiletype_special::WORN_1: - return RemoteFortressReader::WORN_1; - break; - case df::enums::tiletype_special::WORN_2: - return RemoteFortressReader::WORN_2; - break; - case df::enums::tiletype_special::WORN_3: - return RemoteFortressReader::WORN_3; - break; - case df::enums::tiletype_special::TRACK: - return RemoteFortressReader::TRACK; - break; -#if DF_VERSION > 40001 - case df::enums::tiletype_special::SMOOTH_DEAD: - return RemoteFortressReader::SMOOTH_DEAD; - break; -#endif - default: - return RemoteFortressReader::NO_SPECIAL; - break; - } - return RemoteFortressReader::NO_SPECIAL; -} - -RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) -{ - switch (shape) - { - case df::enums::tiletype_shape::NONE: - return RemoteFortressReader::NO_SHAPE; - break; - case df::enums::tiletype_shape::EMPTY: - return RemoteFortressReader::EMPTY; - break; - case df::enums::tiletype_shape::FLOOR: - return RemoteFortressReader::FLOOR; - break; - case df::enums::tiletype_shape::BOULDER: - return RemoteFortressReader::BOULDER; - break; - case df::enums::tiletype_shape::PEBBLES: - return RemoteFortressReader::PEBBLES; - break; - case df::enums::tiletype_shape::WALL: - return RemoteFortressReader::WALL; - break; - case df::enums::tiletype_shape::FORTIFICATION: - return RemoteFortressReader::FORTIFICATION; - break; - case df::enums::tiletype_shape::STAIR_UP: - return RemoteFortressReader::STAIR_UP; - break; - case df::enums::tiletype_shape::STAIR_DOWN: - return RemoteFortressReader::STAIR_DOWN; - break; - case df::enums::tiletype_shape::STAIR_UPDOWN: - return RemoteFortressReader::STAIR_UPDOWN; - break; - case df::enums::tiletype_shape::RAMP: - return RemoteFortressReader::RAMP; - break; - case df::enums::tiletype_shape::RAMP_TOP: - return RemoteFortressReader::RAMP_TOP; - break; - case df::enums::tiletype_shape::BROOK_BED: - return RemoteFortressReader::BROOK_BED; - break; - case df::enums::tiletype_shape::BROOK_TOP: - return RemoteFortressReader::BROOK_TOP; - break; -#if DF_VERSION > 40001 - case df::enums::tiletype_shape::BRANCH: - return RemoteFortressReader::BRANCH; - break; -#endif -#if DF_VERSION < 40001 - case df::enums::tiletype_shape::TREE: - return RemoteFortressReader::TREE_SHAPE; - break; -#endif -#if DF_VERSION > 40001 - - case df::enums::tiletype_shape::TRUNK_BRANCH: - return RemoteFortressReader::TRUNK_BRANCH; - break; - case df::enums::tiletype_shape::TWIG: - return RemoteFortressReader::TWIG; - break; -#endif - case df::enums::tiletype_shape::SAPLING: - return RemoteFortressReader::SAPLING; - break; - case df::enums::tiletype_shape::SHRUB: - return RemoteFortressReader::SHRUB; - break; - case df::enums::tiletype_shape::ENDLESS_PIT: - return RemoteFortressReader::EMPTY; - break; - default: - return RemoteFortressReader::NO_SHAPE; - break; - } - return RemoteFortressReader::NO_SHAPE; -} - -RemoteFortressReader::TiletypeVariant TranslateVariant(df::tiletype_variant variant) -{ - switch (variant) - { - case df::enums::tiletype_variant::NONE: - return RemoteFortressReader::NO_VARIANT; - break; - case df::enums::tiletype_variant::VAR_1: - return RemoteFortressReader::VAR_1; - break; - case df::enums::tiletype_variant::VAR_2: - return RemoteFortressReader::VAR_2; - break; - case df::enums::tiletype_variant::VAR_3: - return RemoteFortressReader::VAR_3; - break; - case df::enums::tiletype_variant::VAR_4: - return RemoteFortressReader::VAR_4; - break; - default: - return RemoteFortressReader::NO_VARIANT; - break; - } - return RemoteFortressReader::NO_VARIANT; -} - -static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) -{ - clock_t start = clock(); - for (int i = 0; i < world->map.map_blocks.size(); i++) - { - df::map_block * block = world->map.map_blocks[i]; - fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); - } - clock_t end = clock(); - double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; - stream.print("Checking all hashes took %f seconds.", elapsed_secs); - return CR_OK; -} - -map hashes; - -//check if the tiletypes have changed -bool IsTiletypeChanged(DFCoord pos) -{ - uint16_t hash; - df::map_block * block = Maps::getBlock(pos); - if (block) - hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); - else - hash = 0; - if (hashes[pos] != hash) - { - hashes[pos] = hash; - return true; - } - return false; -} - -map waterHashes; - -//check if the designations have changed -bool IsDesignationChanged(DFCoord pos) -{ - uint16_t hash; - df::map_block * block = Maps::getBlock(pos); - if (block) - hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); - else - hash = 0; - if (waterHashes[pos] != hash) - { - waterHashes[pos] = hash; - return true; - } - return false; -} - -map buildingHashes; - -//check if the designations have changed -bool IsBuildingChanged(DFCoord pos) -{ - df::map_block * block = Maps::getBlock(pos); - bool changed = false; - for (int x = 0; x < 16; x++) - for (int y = 0; y < 16; y++) - { - DFCoord localPos = DFCoord(pos.x * 16 + x, pos.y * 16 + y, pos.z); - auto bld = block->occupancy[x][y].bits.building; - if (buildingHashes[pos] != bld) - { - buildingHashes[pos] = bld; - changed = true; - } - } - return changed; -} - -static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) -{ - hashes.clear(); - waterHashes.clear(); - buildingHashes.clear(); - return CR_OK; -} - -df::matter_state GetState(df::material * mat, uint16_t temp = 10015) -{ - df::matter_state state = matter_state::Solid; - if (temp >= mat->heat.melting_point) - state = df::matter_state::Liquid; - if (temp >= mat->heat.boiling_point) - state = matter_state::Gas; - return state; -} - -static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) -{ - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - - - - df::world_raws *raws = &world->raws; - MaterialInfo mat; - for (int i = 0; i < raws->inorganics.size(); i++) - { - mat.decode(0, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(0); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - for (int i = 0; i < 19; i++) - { - int k = -1; - if (i == 7) - k = 1;// for coal. - for (int j = -1; j <= k; j++) - { - mat.decode(i, j); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(i); - mat_def->mutable_mat_pair()->set_mat_index(j); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - } - for (int i = 0; i < raws->creatures.all.size(); i++) - { - df::creature_raw * creature = raws->creatures.all[i]; - for (int j = 0; j < creature->material.size(); j++) - { - mat.decode(j + 19, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + 19); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - } - for (int i = 0; i < raws->plants.all.size(); i++) - { - df::plant_raw * plant = raws->plants.all[i]; - for (int j = 0; j < plant->material.size(); j++) - { - mat.decode(j + 419, i); - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type(j + 419); - mat_def->mutable_mat_pair()->set_mat_index(i); - mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; - if (plant->material[j]->state_color[GetState(plant->material[j])] < raws->language.colors.size()) - { - df::descriptor_color *color = raws->language.colors[plant->material[j]->state_color[GetState(plant->material[j])]]; - mat_def->mutable_state_color()->set_red(color->red * 255); - mat_def->mutable_state_color()->set_green(color->green * 255); - mat_def->mutable_state_color()->set_blue(color->blue * 255); - } - } - } - return CR_OK; -} - -static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) -{ - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - FOR_ENUM_ITEMS(item_type, it) - { - MaterialDefinition *mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(-1); - mat_def->set_id(ENUM_KEY_STR(item_type, it)); - int subtypes = Items::getSubtypeCount(it); - if (subtypes >= 0) - { - for (int i = 0; i < subtypes; i++) - { - mat_def = out->add_material_list(); - mat_def->mutable_mat_pair()->set_mat_type((int)it); - mat_def->mutable_mat_pair()->set_mat_index(i); - df::itemdef * item = Items::getSubtypeDef(it, i); - mat_def->set_id(item->id); - } - } - } - - - return CR_OK; -} - -static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) -{ - if (!Core::getInstance().isWorldLoaded()) { - //out->set_available(false); - return CR_OK; - } - - - - df::world_raws *raws = &world->raws; - if (!raws) - return CR_OK;//'. - - - for (int i = 0; i < raws->plants.all.size(); i++) - { - df::plant_raw * pp = raws->plants.all[i]; - if (!pp) - continue; - MaterialDefinition * basePlant = out->add_material_list(); - basePlant->set_id(pp->id + ":BASE"); - basePlant->set_name(pp->name); - basePlant->mutable_mat_pair()->set_mat_type(-1); - basePlant->mutable_mat_pair()->set_mat_index(i); -#if DF_VERSION > 40001 - for (int g = 0; g < pp->growths.size(); g++) - { - df::plant_growth* growth = pp->growths[g]; - if (!growth) - continue; - for (int l = 0; l < GROWTH_LOCATIONS_SIZE; l++) - { - MaterialDefinition * out_growth = out->add_material_list(); - out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); - out_growth->set_name(growth->name); - out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); - out_growth->mutable_mat_pair()->set_mat_index(i); - } - } -#endif - } - return CR_OK; -} - -void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) -{ - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - - MapExtras::Block * block = MC->BlockAtTile(DfBlock->map_pos); - - int trunk_percent[16][16]; - int tree_x[16][16]; - int tree_y[16][16]; - int tree_z[16][16]; - for (int xx = 0; xx < 16; xx++) - for (int yy = 0; yy < 16; yy++) - { - trunk_percent[xx][yy] = 255; - tree_x[xx][yy] = -3000; - tree_y[xx][yy] = -3000; - tree_z[xx][yy] = -3000; - } - - df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; - for (int i = 0; i < column->plants.size(); i++) - { - df::plant* plant = column->plants[i]; - if (plant->tree_info == NULL) - continue; - df::plant_tree_info * tree_info = plant->tree_info; - if ( - plant->pos.z - tree_info->roots_depth > DfBlock->map_pos.z - || (plant->pos.z + tree_info->body_height) <= DfBlock->map_pos.z - || (plant->pos.x - tree_info->dim_x / 2) > (DfBlock->map_pos.x + 16) - || (plant->pos.x + tree_info->dim_x / 2) < (DfBlock->map_pos.x) - || (plant->pos.y - tree_info->dim_y / 2) > (DfBlock->map_pos.y + 16) - || (plant->pos.y + tree_info->dim_y / 2) < (DfBlock->map_pos.y) - ) - continue; - DFCoord localPos = plant->pos - DfBlock->map_pos; - for (int xx = 0; xx < tree_info->dim_x; xx++) - for (int yy = 0; yy < tree_info->dim_y; yy++) - { - int xxx = localPos.x - (tree_info->dim_x / 2) + xx; - int yyy = localPos.y - (tree_info->dim_y / 2) + yy; - if (xxx < 0 - || yyy < 0 - || xxx >= 16 - || yyy >= 16 - ) - continue; - df::plant_tree_tile tile; - if (-localPos.z < 0) - { - tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; - } - else - { - tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; - } - if (!tile.whole || tile.bits.blocked) - continue; - if (tree_info->body_height <= 1) - trunk_percent[xxx][yyy] = 0; - else - trunk_percent[xxx][yyy] = -localPos.z * 100 / (tree_info->body_height - 1); - tree_x[xxx][yyy] = xx - tree_info->dim_x / 2; - tree_y[xxx][yyy] = yy - tree_info->dim_y / 2; - tree_z[xxx][yyy] = localPos.z; - } - } - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - df::tiletype tile = DfBlock->tiletype[xx][yy]; - NetBlock->add_tiles(tile); - df::coord2d p = df::coord2d(xx, yy); - t_matpair baseMat = block->baseMaterialAt(p); - t_matpair staticMat = block->staticMaterialAt(p); - switch (tileMaterial(tile)) - { - case tiletype_material::FROZEN_LIQUID: - staticMat.mat_type = builtin_mats::WATER; - staticMat.mat_index = -1; - break; - default: - break; - } - RemoteFortressReader::MatPair * material = NetBlock->add_materials(); - material->set_mat_type(staticMat.mat_type); - material->set_mat_index(staticMat.mat_index); - RemoteFortressReader::MatPair * layerMaterial = NetBlock->add_layer_materials(); - layerMaterial->set_mat_type(0); - layerMaterial->set_mat_index(block->layerMaterialAt(p)); - RemoteFortressReader::MatPair * veinMaterial = NetBlock->add_vein_materials(); - veinMaterial->set_mat_type(0); - veinMaterial->set_mat_index(block->veinMaterialAt(p)); - RemoteFortressReader::MatPair * baseMaterial = NetBlock->add_base_materials(); - baseMaterial->set_mat_type(baseMat.mat_type); - baseMaterial->set_mat_index(baseMat.mat_index); - RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); - constructionItem->set_mat_type(-1); - constructionItem->set_mat_index(-1); - if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) - { - df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); - if (con) - { - constructionItem->set_mat_type(con->item_type); - constructionItem->set_mat_index(con->item_subtype); - } - } - NetBlock->add_tree_percent(trunk_percent[xx][yy]); - NetBlock->add_tree_x(tree_x[xx][yy]); - NetBlock->add_tree_y(tree_y[xx][yy]); - NetBlock->add_tree_z(tree_z[xx][yy]); - } -} - -void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) -{ - NetBlock->set_map_x(DfBlock->map_pos.x); - NetBlock->set_map_y(DfBlock->map_pos.y); - NetBlock->set_map_z(DfBlock->map_pos.z); - - for (int yy = 0; yy < 16; yy++) - for (int xx = 0; xx < 16; xx++) - { - df::tile_designation designation = DfBlock->designation[xx][yy]; - int lava = 0; - int water = 0; - if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) - lava = designation.bits.flow_size; - else - water = designation.bits.flow_size; - NetBlock->add_magma(lava); - NetBlock->add_water(water); - NetBlock->add_aquifer(designation.bits.water_table); - NetBlock->add_hidden(designation.bits.hidden); - NetBlock->add_light(designation.bits.light); - NetBlock->add_outside(designation.bits.outside); - NetBlock->add_subterranean(designation.bits.subterranean); - NetBlock->add_water_salt(designation.bits.water_salt); - NetBlock->add_water_stagnant(designation.bits.water_stagnant); - switch (designation.bits.dig) - { - case df::enums::tile_dig_designation::No: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - case df::enums::tile_dig_designation::Default: - NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); - break; - case df::enums::tile_dig_designation::UpDownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::Channel: - NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); - break; - case df::enums::tile_dig_designation::Ramp: - NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); - break; - case df::enums::tile_dig_designation::DownStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); - break; - case df::enums::tile_dig_designation::UpStair: - NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); - break; - default: - NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); - break; - } - } -} - -void CopyBuildings(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) -{ - int minX = DfBlock->map_pos.x; - int minY = DfBlock->map_pos.y; - int Z = DfBlock->map_pos.z; - - int maxX = minX + 15; - int maxY = minY + 15; - - for (int i = 0; i < df::global::world->buildings.all.size(); i++) - { - df::building * bld = df::global::world->buildings.all[i]; - if (bld->x1 > maxX || bld->y1 > maxY || bld->x2 < minX || bld->y2 < minY) - continue; - - int z2 = bld->z; - - if (bld->getType() == building_type::Well) - { - df::building_wellst * well_building = virtual_cast(bld); - if (well_building) - { - z2 = well_building->bucket_z; - } - } - if (bld->z < Z || z2 > Z) - continue; - auto out_bld = NetBlock->add_buildings(); - CopyBuilding(i, out_bld); - } -} - -static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) -{ - int x, y, z; - DFHack::Maps::getPosition(x, y, z); - out->set_map_x(x); - out->set_map_y(y); - MapExtras::MapCache MC; - int center_x = (in->min_x() + in->max_x()) / 2; - int center_y = (in->min_y() + in->max_y()) / 2; - - int NUMBER_OF_POINTS = ((in->max_x() - center_x + 1) * 2) * ((in->max_y() - center_y + 1) * 2); - int blocks_needed; - if (in->has_blocks_needed()) - blocks_needed = in->blocks_needed(); - else - blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z()); - int blocks_sent = 0; - int min_x = in->min_x(); - int min_y = in->min_y(); - int max_x = in->max_x(); - int max_y = in->max_y(); - //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); - for (int zz = in->max_z() - 1; zz >= in->min_z(); zz--) - { - // (di, dj) is a vector - direction in which we move right now - int di = 1; - int dj = 0; - // length of current segment - int segment_length = 1; - // current position (i, j) and how much of current segment we passed - int i = center_x; - int j = center_y; - int segment_passed = 0; - for (int k = 0; k < NUMBER_OF_POINTS; ++k) - { - if (blocks_sent >= blocks_needed) - break; - if (!(i < min_x || i >= max_x || j < min_y || j >= max_y)) - { - DFCoord pos = DFCoord(i, j, zz); - df::map_block * block = DFHack::Maps::getBlock(pos); - if (block != NULL) - { - int nonAir = 0; - for (int xxx = 0; xxx < 16; xxx++) - for (int yyy = 0; yyy < 16; yyy++) - { - if ((DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::None && - DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) - || block->designation[xxx][yyy].bits.flow_size > 0 - || block->occupancy[xxx][yyy].bits.building > 0) - nonAir++; - } - if (nonAir > 0) - { - bool tileChanged = IsTiletypeChanged(pos); - bool desChanged = IsDesignationChanged(pos); - //bool bldChanged = IsBuildingChanged(pos); - RemoteFortressReader::MapBlock *net_block; - if (tileChanged || desChanged) - net_block = out->add_map_blocks(); - if (tileChanged) - { - CopyBlock(block, net_block, &MC, pos); - blocks_sent++; - } - if (desChanged) - CopyDesignation(block, net_block, &MC, pos); - if (tileChanged) - { - CopyBuildings(block, net_block, &MC, pos); - } - } - } - } - - // make a step, add 'direction' vector (di, dj) to current position (i, j) - i += di; - j += dj; - ++segment_passed; - //System.out.println(i + " " + j); - - if (segment_passed == segment_length) - { - // done with current segment - segment_passed = 0; - - // 'rotate' directions - int buffer = di; - di = -dj; - dj = buffer; - - // increase segment length if necessary - if (dj == 0) { - ++segment_length; - } - } - } - //for (int yy = in->min_y(); yy < in->max_y(); yy++) - //{ - // for (int xx = in->min_x(); xx < in->max_x(); xx++) - // { - // DFCoord pos = DFCoord(xx, yy, zz); - // df::map_block * block = DFHack::Maps::getBlock(pos); - // if (block == NULL) - // continue; - // { - // RemoteFortressReader::MapBlock *net_block = out->add_map_blocks(); - // CopyBlock(block, net_block, &MC, pos); - // } - // } - //} - } - MC.trash(); - return CR_OK; -} - -static command_result GetTiletypeList(color_ostream &stream, const EmptyMessage *in, TiletypeList *out) -{ - int count = 0; - FOR_ENUM_ITEMS(tiletype, tt) - { - Tiletype * type = out->add_tiletype_list(); - type->set_id(tt); - type->set_name(ENUM_KEY_STR(tiletype, tt)); - const char * name = tileName(tt); - if (name != NULL && name[0] != 0) - type->set_caption(name); - type->set_shape(TranslateShape(tileShape(tt))); - type->set_special(TranslateSpecial(tileSpecial(tt))); - type->set_material(TranslateMaterial(tileMaterial(tt))); - type->set_variant(TranslateVariant(tileVariant(tt))); - type->set_direction(tileDirection(tt).getStr()); - count++; - } - return CR_OK; -} - -static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out) -{ - int min_x = in->min_x() / 3; - int min_y = in->min_y() / 3; - int min_z = in->min_z(); - int max_x = in->max_x() / 3; - int max_y = in->max_y() / 3; - int max_z = in->max_z(); - -#if DF_VERSION < 40001 - //plants are gotten differently here -#else - for (int xx = min_x; xx < max_x; xx++) - for (int yy = min_y; yy < max_y; yy++) - { - if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) - continue; - df::map_block_column * column = world->map.column_index[xx][yy]; - for (int i = 0; i < column->plants.size(); i++) - { - df::plant * plant = column->plants[i]; - if (!plant->tree_info) - { - if (plant->pos.z < min_z || plant->pos.z >= max_z) - continue; - if (plant->pos.x < in->min_x() * 16 || plant->pos.x >= in->max_x() * 16) - continue; - if (plant->pos.y < in->min_y() * 16 || plant->pos.y >= in->max_y() * 16) - continue; - } - else - { - if (plant->pos.z - plant->tree_info->roots_depth < min_z || plant->pos.z + plant->tree_info->body_height > max_z) - continue; - if (plant->pos.x - plant->tree_info->dim_x / 2 < in->min_x() * 16 || plant->pos.x + plant->tree_info->dim_x / 2 >= in->max_x() * 16) - continue; - if (plant->pos.y - plant->tree_info->dim_y / 2 < in->min_y() * 16 || plant->pos.y + plant->tree_info->dim_y / 2 >= in->max_y() * 16) - continue; - } - RemoteFortressReader::PlantDef * out_plant = out->add_plant_list(); - out_plant->set_index(plant->material); - out_plant->set_pos_x(plant->pos.x); - out_plant->set_pos_y(plant->pos.y); - out_plant->set_pos_z(plant->pos.z); - } - } -#endif - return CR_OK; -} - -static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out) -{ - auto world = df::global::world; - for (int i = 0; i < world->units.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - auto send_unit = out->add_creature_list(); - send_unit->set_id(unit->id); - send_unit->set_pos_x(unit->pos.x); - send_unit->set_pos_y(unit->pos.y); - send_unit->set_pos_z(unit->pos.z); - send_unit->mutable_race()->set_mat_type(unit->race); - send_unit->mutable_race()->set_mat_index(unit->caste); - ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); - send_unit->set_flags1(unit->flags1.whole); - send_unit->set_flags2(unit->flags2.whole); - send_unit->set_flags3(unit->flags3.whole); - send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); - auto size_info = send_unit->mutable_size_info(); - size_info->set_size_cur(unit->body.size_info.size_cur); - size_info->set_size_base(unit->body.size_info.size_base); - size_info->set_area_cur(unit->body.size_info.area_cur); - size_info->set_area_base(unit->body.size_info.area_base); - size_info->set_length_cur(unit->body.size_info.length_cur); - size_info->set_length_base(unit->body.size_info.length_base); - if (unit->name.has_name) - { - send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); - } - } - return CR_OK; -} - -static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out) -{ - int x, y, z, w, h, cx, cy, cz; - Gui::getWindowSize(w, h); - Gui::getViewCoords(x, y, z); - Gui::getCursorCoords(cx, cy, cz); - - auto embark = Gui::getViewscreenByType(0); - if (embark) - { - df::embark_location location = embark->location; - df::world_data * data = df::global::world->world_data; - if (data && data->region_map) - { - z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; - } - } - - out->set_view_pos_x(x); - out->set_view_pos_y(y); - out->set_view_pos_z(z); - out->set_view_size_x(w); - out->set_view_size_y(h); - out->set_cursor_pos_x(cx); - out->set_cursor_pos_y(cy); - out->set_cursor_pos_z(cz); - out->set_follow_unit_id(ui->follow_unit); - out->set_follow_item_id(ui->follow_item); - return CR_OK; -} - -static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) -{ - if (!Maps::IsValid()) - return CR_FAILURE; - uint32_t size_x, size_y, size_z; - int32_t pos_x, pos_y, pos_z; - Maps::getSize(size_x, size_y, size_z); - Maps::getPosition(pos_x, pos_y, pos_z); - out->set_block_size_x(size_x); - out->set_block_size_y(size_y); - out->set_block_size_z(size_z); - out->set_block_pos_x(pos_x); - out->set_block_pos_y(pos_y); - out->set_block_pos_z(pos_z); - out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); - out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); - out->set_save_name(df::global::world->cur_savegame.save_dir); - return CR_OK; -} - -static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out) -{ - FOR_ENUM_ITEMS(building_type, bt) - { - BuildingDefinition * bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(-1); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt)); - - switch (bt) - { - case df::enums::building_type::NONE: - break; - case df::enums::building_type::Chair: - break; - case df::enums::building_type::Bed: - break; - case df::enums::building_type::Table: - break; - case df::enums::building_type::Coffin: - break; - case df::enums::building_type::FarmPlot: - break; - case df::enums::building_type::Furnace: - FOR_ENUM_ITEMS(furnace_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(furnace_type, st)); - - if (st == furnace_type::Custom) - { - for (int i = 0; i < world->raws.buildings.furnaces.size(); i++) - { - auto cust = world->raws.buildings.furnaces[i]; - - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(cust->id); - bld->set_id(cust->code); - bld->set_name(cust->name); - } - } - } - break; - case df::enums::building_type::TradeDepot: - break; - case df::enums::building_type::Shop: - FOR_ENUM_ITEMS(shop_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(shop_type, st)); - - } - break; - case df::enums::building_type::Door: - break; - case df::enums::building_type::Floodgate: - break; - case df::enums::building_type::Box: - break; - case df::enums::building_type::Weaponrack: - break; - case df::enums::building_type::Armorstand: - break; - case df::enums::building_type::Workshop: - FOR_ENUM_ITEMS(workshop_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(workshop_type, st)); - - if (st == workshop_type::Custom) - { - for (int i = 0; i < world->raws.buildings.workshops.size(); i++) - { - auto cust = world->raws.buildings.workshops[i]; - - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(cust->id); - bld->set_id(cust->code); - bld->set_name(cust->name); - } - } - } - break; - case df::enums::building_type::Cabinet: - break; - case df::enums::building_type::Statue: - break; - case df::enums::building_type::WindowGlass: - break; - case df::enums::building_type::WindowGem: - break; - case df::enums::building_type::Well: - break; - case df::enums::building_type::Bridge: - break; - case df::enums::building_type::RoadDirt: - break; - case df::enums::building_type::RoadPaved: - break; - case df::enums::building_type::SiegeEngine: - FOR_ENUM_ITEMS(siegeengine_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(siegeengine_type, st)); - - } - break; - case df::enums::building_type::Trap: - FOR_ENUM_ITEMS(trap_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(trap_type, st)); - - } - break; - case df::enums::building_type::AnimalTrap: - break; - case df::enums::building_type::Support: - break; - case df::enums::building_type::ArcheryTarget: - break; - case df::enums::building_type::Chain: - break; - case df::enums::building_type::Cage: - break; - case df::enums::building_type::Stockpile: - break; - case df::enums::building_type::Civzone: - FOR_ENUM_ITEMS(civzone_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(civzone_type, st)); - - } - break; - case df::enums::building_type::Weapon: - break; - case df::enums::building_type::Wagon: - break; - case df::enums::building_type::ScrewPump: - break; - case df::enums::building_type::Construction: - FOR_ENUM_ITEMS(construction_type, st) - { - bld = out->add_building_list(); - bld->mutable_building_type()->set_building_type(bt); - bld->mutable_building_type()->set_building_subtype(st); - bld->mutable_building_type()->set_building_custom(-1); - bld->set_id(ENUM_KEY_STR(building_type, bt) + "_" + ENUM_KEY_STR(construction_type, st)); - - } - break; - case df::enums::building_type::Hatch: - break; - case df::enums::building_type::GrateWall: - break; - case df::enums::building_type::GrateFloor: - break; - case df::enums::building_type::BarsVertical: - break; - case df::enums::building_type::BarsFloor: - break; - case df::enums::building_type::GearAssembly: - break; - case df::enums::building_type::AxleHorizontal: - break; - case df::enums::building_type::AxleVertical: - break; - case df::enums::building_type::WaterWheel: - break; - case df::enums::building_type::Windmill: - break; - case df::enums::building_type::TractionBench: - break; - case df::enums::building_type::Slab: - break; - case df::enums::building_type::Nest: - break; - case df::enums::building_type::NestBox: - break; - case df::enums::building_type::Hive: - break; - case df::enums::building_type::Rollers: - break; - default: - break; - } - } - return CR_OK; -} - -DFCoord GetMapCenter() -{ - DFCoord output; - auto embark = Gui::getViewscreenByType(0); - if (embark) - { - df::embark_location location = embark->location; - output.x = (location.region_pos.x * 16) + 8; - output.y = (location.region_pos.y * 16) + 8; - output.z = 100; - df::world_data * data = df::global::world->world_data; - if (data && data->region_map) - { - output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; - } - } - else if (Maps::IsValid()) - { - int x, y, z; - Maps::getPosition(x,y,z); - output = DFCoord(x, y, z); - } - else - for (int i = 0; i < df::global::world->armies.all.size(); i++) - { - df::army * thisArmy = df::global::world->armies.all[i]; - if (thisArmy->flags.is_set(df::enums::army_flags::player)) - { - output.x = (thisArmy->pos.x / 3) - 1; - output.y = (thisArmy->pos.y / 3) - 1; - output.z = thisArmy->pos.z; - } - } - return output; -} - -static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out) -{ - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - return CR_OK; -} - -static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out) -{ - if (!df::global::world->world_data) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - if (!data->region_map) - { - out->set_world_width(0); - out->set_world_height(0); - return CR_FAILURE; - } - int width = data->world_width; - int height = data->world_height; - out->set_world_width(width); - out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); - auto poles = data->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } - for (int yy = 0; yy < height; yy++) - for (int xx = 0; xx < width; xx++) - { - df::region_map_entry * map_entry = &data->region_map[xx][yy]; - out->add_elevation(map_entry->elevation); - out->add_rainfall(map_entry->rainfall); - out->add_vegetation(map_entry->vegetation); - out->add_temperature(map_entry->temperature); - out->add_evilness(map_entry->evilness); - out->add_drainage(map_entry->drainage); - out->add_volcanism(map_entry->volcanism); - out->add_savagery(map_entry->savagery); - out->add_salinity(map_entry->salinity); - auto clouds = out->add_clouds(); - clouds->set_cirrus(map_entry->clouds.bits.cirrus); - clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); - clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); - clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); - clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); - } - DFCoord pos = GetMapCenter(); - out->set_center_x(pos.x); - out->set_center_y(pos.y); - out->set_center_z(pos.z); - - - out->set_cur_year(World::ReadCurrentYear()); - out->set_cur_year_tick(World::ReadCurrentTick()); - return CR_OK; -} - -static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1) -{ - out->add_rainfall(e1->rainfall); - out->add_vegetation(e1->vegetation); - out->add_temperature(e1->temperature); - out->add_evilness(e1->evilness); - out->add_drainage(e1->drainage); - out->add_volcanism(e1->volcanism); - out->add_savagery(e1->savagery); - out->add_salinity(e1->salinity); -} - -static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) -{ - if (pos.x < 0) - pos.x = 0; - if (pos.y < 0) - pos.y = 0; - if (pos.x >= worldData->world_width) - pos.x = worldData->world_width - 1; - if (pos.y >= worldData->world_height) - pos.y = worldData->world_height - 1; - AddRegionTiles(out, &worldData->region_map[pos.x][pos.y]); -} - -static df::coord2d ShiftCoords(df::coord2d source, int direction) -{ - switch (direction) - { - case 1: - return df::coord2d(source.x - 1, source.y + 1); - case 2: - return df::coord2d(source.x, source.y + 1); - case 3: - return df::coord2d(source.x + 1, source.y + 1); - case 4: - return df::coord2d(source.x - 1, source.y); - case 5: - return source; - case 6: - return df::coord2d(source.x + 1, source.y); - case 7: - return df::coord2d(source.x - 1, source.y - 1); - case 8: - return df::coord2d(source.x, source.y - 1); - case 9: - return df::coord2d(source.x + 1, source.y - 1); - default: - return source; - } -} - -static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, WorldMap * out) -{ - int pos_x = worldRegionDetails->pos.x; - int pos_y = worldRegionDetails->pos.y; - out->set_map_x(pos_x); - out->set_map_y(pos_y); - out->set_world_width(17); - out->set_world_height(17); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); - out->set_name_english(name); - out->set_name(name); - auto poles = worldData->flip_latitude; - switch (poles) - { - case df::world_data::None: - out->set_world_poles(WorldPoles::NO_POLES); - break; - case df::world_data::North: - out->set_world_poles(WorldPoles::NORTH_POLE); - break; - case df::world_data::South: - out->set_world_poles(WorldPoles::SOUTH_POLE); - break; - case df::world_data::Both: - out->set_world_poles(WorldPoles::BOTH_POLES); - break; - default: - break; - } - - df::world_region_details * south = NULL; - df::world_region_details * east = NULL; - df::world_region_details * southEast = NULL; - - for (int i = 0; i < worldData->region_details.size(); i++) - { - auto region = worldData->region_details[i]; - if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) - southEast = region; - else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) - east = region; - else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) - south = region; - } - - for (int yy = 0; yy < 17; yy++) - for (int xx = 0; xx < 17; xx++) - { - //This is because the bottom row doesn't line up. - if (xx == 16 && yy == 16 && southEast != NULL) - { - out->add_elevation(southEast->elevation[0][0]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); - } - else if (xx == 16 && east != NULL) - { - out->add_elevation(east->elevation[0][yy]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); - } - else if (yy == 16 && south != NULL) - { - out->add_elevation(south->elevation[xx][0]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); - } - else - { - out->add_elevation(worldRegionDetails->elevation[xx][yy]); - AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); - } - - if (xx == 16 || yy == 16) - { - out->add_river_tiles(); - } - else - { - auto riverTile = out->add_river_tiles(); - auto east = riverTile->mutable_east(); - auto north = riverTile->mutable_north(); - auto south = riverTile->mutable_south(); - auto west = riverTile->mutable_west(); - - north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); - north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); - north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); - north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); - - south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); - south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); - south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); - south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); - - west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); - west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); - west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); - west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); - - east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); - east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); - east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); - east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); - } - } -} - - -static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) -{ - if (!df::global::world->world_data) - { - return CR_FAILURE; - } - df::world_data * data = df::global::world->world_data; - for (int i = 0; i < data->region_details.size(); i++) - { - df::world_region_details * region = data->region_details[i]; - if (!region) - continue; - WorldMap * regionMap = out->add_world_maps(); - CopyLocalMap(data, region, regionMap); - } - return CR_OK; -} - -static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) -{ - if (!df::global::world) - return CR_FAILURE; - - df::world * world = df::global::world; - - for (int i = 0; i < world->raws.creatures.all.size(); i++) - { - df::creature_raw * orig_creature = world->raws.creatures.all[i]; - - auto send_creature = out->add_creature_raws(); - - send_creature->set_index(i); - send_creature->set_creature_id(orig_creature->creature_id); - send_creature->add_name(orig_creature->name[0]); - send_creature->add_name(orig_creature->name[1]); - send_creature->add_name(orig_creature->name[2]); - - send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); - send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); - - send_creature->add_general_child_name(orig_creature->general_child_name[0]); - send_creature->add_general_child_name(orig_creature->general_child_name[1]); - - send_creature->set_creature_tile(orig_creature->creature_tile); - send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); - - ConvertDfColor(orig_creature->color, send_creature->mutable_color()); - - send_creature->set_adultsize(orig_creature->adultsize); - - for (int j = 0; j < orig_creature->caste.size(); j++) - { - auto orig_caste = orig_creature->caste[j]; - if (!orig_caste) - continue; - auto send_caste = send_creature->add_caste(); - - send_caste->set_index(j); - - send_caste->set_caste_id(orig_caste->caste_id); - - send_caste->add_caste_name(orig_caste->caste_name[0]); - send_caste->add_caste_name(orig_caste->caste_name[1]); - send_caste->add_caste_name(orig_caste->caste_name[2]); - - send_caste->add_baby_name(orig_caste->baby_name[0]); - send_caste->add_baby_name(orig_caste->baby_name[1]); - - send_caste->add_child_name(orig_caste->child_name[0]); - send_caste->add_child_name(orig_caste->child_name[1]); - send_caste->set_gender(orig_caste->gender); - } - } - - return CR_OK; -} - -static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out) -{ - if (!df::global::world) - return CR_FAILURE; - - df::world * world = df::global::world; - - for (int i = 0; i < world->raws.plants.all.size(); i++) - { - df::plant_raw* plant_local = world->raws.plants.all[i]; - PlantRaw* plant_remote = out->add_plant_raws(); - - plant_remote->set_index(i); - plant_remote->set_id(plant_local->id); - plant_remote->set_name(plant_local->name); - if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) - plant_remote->set_tile(plant_local->tiles.shrub_tile); - else - plant_remote->set_tile(plant_local->tiles.tree_tile); - for (int j = 0; j < plant_local->growths.size(); j++) - { - df::plant_growth* growth_local = plant_local->growths[j]; - TreeGrowth * growth_remote = plant_remote->add_growths(); - growth_remote->set_index(j); - growth_remote->set_id(growth_local->id); - growth_remote->set_name(growth_local->name); - for (int k = 0; k < growth_local->prints.size(); k++) - { - df::plant_growth_print* print_local = growth_local->prints[k]; - GrowthPrint* print_remote = growth_remote->add_prints(); - print_remote->set_priority(print_local->priority); - print_remote->set_color(print_local->color[0] | (print_local->color[2] * 8)); - print_remote->set_timing_start(print_local->timing_start); - print_remote->set_timing_end(print_local->timing_end); - print_remote->set_tile(print_local->tile_growth); - } - growth_remote->set_timing_start(growth_local->timing_1); - growth_remote->set_timing_end(growth_local->timing_2); - growth_remote->set_twigs(growth_local->locations.bits.twigs); - growth_remote->set_light_branches(growth_local->locations.bits.light_branches); - growth_remote->set_heavy_branches(growth_local->locations.bits.heavy_branches); - growth_remote->set_trunk(growth_local->locations.bits.trunk); - growth_remote->set_roots(growth_local->locations.bits.roots); - growth_remote->set_cap(growth_local->locations.bits.cap); - growth_remote->set_sapling(growth_local->locations.bits.sapling); - growth_remote->set_timing_start(growth_local->timing_1); - growth_remote->set_timing_end(growth_local->timing_2); - growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); - growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); - auto growthMat = growth_remote->mutable_mat(); - growthMat->set_mat_index(growth_local->mat_index); - growthMat->set_mat_type(growth_local->mat_type); - } - } - return CR_OK; -} - -static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out) -{ - df::graphic * gps = df::global::gps; - out->set_width(gps->dimx); - out->set_height(gps->dimy); - for (int i = 0; i < (gps->dimx * gps->dimy); i++) - { - int index = i * 4; - auto tile = out->add_tiles(); - tile->set_character(gps->screen[index]); - tile->set_foreground(gps->screen[index + 1] | (gps->screen[index + 3] * 8)); - tile->set_background(gps->screen[index + 2]); - } - - return CR_OK; -} - -static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in) -{ - SDL::Event e; - e.key.type = in->type(); - e.key.state = in->state(); - e.key.ksym.mod = (SDL::Mod)in->mod(); - e.key.ksym.scancode = in->scancode(); - e.key.ksym.sym = (SDL::Key)in->sym(); - e.key.ksym.unicode = in->unicode(); - SDL_PushEvent(&e); - return CR_OK; -} diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt new file mode 100644 index 000000000..1ff1cac7f --- /dev/null +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -0,0 +1,34 @@ +PROJECT (remotefortressreader) +# A list of source files +SET(PROJECT_SRCS + remotefortressreader.cpp + adventure_control.cpp + building_reader.cpp + item_reader.cpp +) +# A list of headers +SET(PROJECT_HDRS + adventure_control.h + building_reader.h + item_reader.h + df_version_int.h +) +#proto files to include. +SET(PROJECT_PROTO + ${CMAKE_CURRENT_SOURCE_DIR}/../proto/RemoteFortressReader.pb.cc + ${CMAKE_CURRENT_SOURCE_DIR}/../proto/AdventureControl.pb.cc + ${CMAKE_CURRENT_SOURCE_DIR}/../proto/ItemdefInstrument.pb.cc +) + +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO} PROPERTIES GENERATED TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS};${PROJECT_PROTO}) + +IF(UNIX AND NOT APPLE) + SET(PROJECT_LIBS ${PROJECT_LIBS} SDL) +ENDIF() + +# this makes sure all the stuff is put in proper places and linked to dfhack +DFHACK_PLUGIN(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES protobuf-lite ${PROJECT_LIBS} COMPILE_FLAGS_MSVC "/FI\"Export.h\"" COMPILE_FLAGS_GCC "-include Export.h -Wno-misleading-indentation" ) diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp new file mode 100644 index 000000000..6fb592d62 --- /dev/null +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -0,0 +1,317 @@ +#include "adventure_control.h" +#include "DataDefs.h" + +#include "df/adventure_movement_attack_creaturest.h" +#include "df/adventure_movement_building_interactst.h" +#include "df/adventure_movement_climbst.h" +#include "df/adventure_movement_hold_itemst.h" +#include "df/adventure_movement_hold_tilest.h" +#include "df/adventure_movement_optionst.h" +#include "df/ui_advmode.h" +#include "df/viewscreen.h" + +#include "modules/Gui.h" + +#include + +using namespace AdventureControl; +using namespace df::enums; +using namespace DFHack; +using namespace Gui; + +std::queue keyQueue; + +void KeyUpdate() +{ + if (!keyQueue.empty()) + { + getCurViewscreen()->feed_key(keyQueue.front()); + keyQueue.pop(); + } +} + +void SetCoord(df::coord in, RemoteFortressReader::Coord *out) +{ + out->set_x(in.x); + out->set_y(in.y); + out->set_z(in.z); +} + +command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) +{ + auto viewScreen = getCurViewscreen(); + if (!in->has_direction()) + return CR_WRONG_USAGE; + if (!df::global::ui_advmode->menu == ui_advmode_menu::Default) + return CR_OK; + auto dir = in->direction(); + switch (dir.x()) + { + case -1: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_NW_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_NW); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_NW_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_W_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_W); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_W_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_SW_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_SW); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_SW_UP); + break; + } + break; + } + break; + case 0: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_N_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_N); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_N_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_DOWN); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_S_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_S); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_S_UP); + break; + } + break; + } + break; + case 1: + switch (dir.y()) + { + case -1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_NE_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_NE); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_NE_UP); + break; + } + break; + case 0: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_E_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_E); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_E_UP); + break; + } + break; + case 1: + switch (dir.z()) + { + case -1: + viewScreen->feed_key(interface_key::A_MOVE_SE_DOWN); + break; + case 0: + viewScreen->feed_key(interface_key::A_MOVE_SE); + break; + case 1: + viewScreen->feed_key(interface_key::A_MOVE_SE_UP); + break; + } + break; + } + break; + } + return CR_OK; +} + +command_result JumpCommand(DFHack::color_ostream &stream, const MoveCommandParams *in) +{ + if (!in->has_direction()) + return CR_WRONG_USAGE; + if (!df::global::ui_advmode->menu == ui_advmode_menu::Default) + return CR_OK; + auto dir = in->direction(); + keyQueue.push(interface_key::A_JUMP); + int x = dir.x(); + int y = dir.y(); + if (x > 0) + { + for (int i = 0; i < x; i++) + { + keyQueue.push(interface_key::CURSOR_RIGHT); + } + } + if (x < 0) + { + for (int i = 0; i > x; i--) + { + keyQueue.push(interface_key::CURSOR_LEFT); + } + } + if (y > 0) + { + for (int i = 0; i < y; i++) + { + keyQueue.push(interface_key::CURSOR_DOWN); + } + } + if (y < 0) + { + for (int i = 0; i > y; i--) + { + keyQueue.push(interface_key::CURSOR_UP); + } + } + keyQueue.push(interface_key::SELECT); + return CR_OK; +} + +command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, MenuContents *out) +{ + auto advUi = df::global::ui_advmode; + + if (advUi == NULL) + return CR_FAILURE; + + out->set_current_menu((AdvmodeMenu)advUi->menu); + + //Fixme: Needs a proper way to control it, but for now, this is the only way to allow Armok Vision to keep going without the user needing to switch to DF. + if (advUi->menu == ui_advmode_menu::FallAction) + { + getCurViewscreen()->feed_key(interface_key::OPTION1); + } + + switch (advUi->menu) + { + case ui_advmode_menu::MoveCarefully: + for (size_t i = 0; i < advUi->movements.size(); i++) + { + auto movement = advUi->movements[i]; + auto send_movement = out->add_movements(); + SetCoord(movement->source, send_movement->mutable_source()); + SetCoord(movement->dest, send_movement->mutable_dest()); + + STRICT_VIRTUAL_CAST_VAR(climbMovement, df::adventure_movement_climbst, movement); + if (climbMovement) + { + SetCoord(climbMovement->grab, send_movement->mutable_grab()); + send_movement->set_movement_type(CarefulMovementType::CLIMB); + } + STRICT_VIRTUAL_CAST_VAR(holdTileMovement, df::adventure_movement_hold_tilest, movement); + if (holdTileMovement) + { + SetCoord(holdTileMovement->grab, send_movement->mutable_grab()); + send_movement->set_movement_type(CarefulMovementType::HOLD_TILE); + } + } + default: + break; + } + + return CR_OK; +} + +command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in) +{ + if (!(df::global::ui_advmode->menu == ui_advmode_menu::MoveCarefully)) + return CR_OK; + int choice = in->value(); + int page = choice / 5; + int select = choice % 5; + for (int i = 0; i < page; i++) + { + keyQueue.push(interface_key::SECONDSCROLL_PAGEDOWN); + } + keyQueue.push((interface_key::interface_key)(interface_key::OPTION1 + select)); + return CR_OK; +} + +command_result MiscMoveCommand(DFHack::color_ostream &stream, const MiscMoveParams *in) +{ + if (!df::global::ui_advmode->menu == ui_advmode_menu::Default) + return CR_OK; + + auto type = in->type(); + + switch (type) + { + case AdventureControl::SET_CLIMB: + getCurViewscreen()->feed_key(interface_key::A_HOLD); + break; + case AdventureControl::SET_STAND: + getCurViewscreen()->feed_key(interface_key::A_STANCE); + break; + case AdventureControl::SET_CANCEL: + getCurViewscreen()->feed_key(interface_key::LEAVESCREEN); + break; + default: + break; + } + + return CR_OK; +} diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h new file mode 100644 index 000000000..bc674e427 --- /dev/null +++ b/plugins/remotefortressreader/adventure_control.h @@ -0,0 +1,16 @@ +#ifndef ADVENTURE_CONTROL_H +#define ADVENTURE_CONTROL_H + +#include "RemoteClient.h" +#include "AdventureControl.pb.h" + +DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); +DFHack::command_result JumpCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in); +DFHack::command_result MenuQuery(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, AdventureControl::MenuContents *out); +DFHack::command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in); +DFHack::command_result MiscMoveCommand(DFHack::color_ostream &stream, const AdventureControl::MiscMoveParams *in); + +void KeyUpdate(); + + +#endif // !ADVENTURE_CONTROL_H diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp new file mode 100644 index 000000000..5a0dd5c96 --- /dev/null +++ b/plugins/remotefortressreader/building_reader.cpp @@ -0,0 +1,915 @@ +#include "building_reader.h" +#include "DataDefs.h" + +//building types +#include "df/building_actual.h" +#include "df/building_chairst.h" +#include "df/building_bedst.h" +#include "df/building_tablest.h" +#include "df/building_coffinst.h" +#include "df/building_farmplotst.h" +#include "df/building_furnacest.h" +#include "df/building_tradedepotst.h" +#include "df/building_shopst.h" +#include "df/building_doorst.h" +#include "df/building_floodgatest.h" +#include "df/building_boxst.h" +#include "df/building_weaponrackst.h" +#include "df/building_armorstandst.h" +#include "df/building_workshopst.h" +#include "df/building_cabinetst.h" +#include "df/building_statuest.h" +#include "df/building_window_glassst.h" +#include "df/building_window_gemst.h" +#include "df/building_wellst.h" +#include "df/building_bridgest.h" +#include "df/building_road_dirtst.h" +#include "df/building_road_pavedst.h" +#include "df/building_siegeenginest.h" +#include "df/building_trapst.h" +#include "df/building_animaltrapst.h" +#include "df/building_supportst.h" +#include "df/building_archerytargetst.h" +#include "df/building_chainst.h" +#include "df/building_cagest.h" +#include "df/building_stockpilest.h" +#include "df/building_civzonest.h" +#include "df/building_weaponst.h" +#include "df/building_wagonst.h" +#include "df/building_screw_pumpst.h" +#include "df/building_constructionst.h" +#include "df/building_hatchst.h" +#include "df/building_grate_wallst.h" +#include "df/building_grate_floorst.h" +#include "df/building_bars_verticalst.h" +#include "df/building_bars_floorst.h" +#include "df/building_gear_assemblyst.h" +#include "df/building_axle_horizontalst.h" +#include "df/building_axle_verticalst.h" +#include "df/building_water_wheelst.h" +#include "df/building_windmillst.h" +#include "df/building_traction_benchst.h" +#include "df/building_slabst.h" +#include "df/building_nestst.h" +#include "df/building_nest_boxst.h" +#include "df/building_hivest.h" +#include "df/building_rollersst.h" + +#include "df/building_def_furnacest.h" +#include "df/building_def_workshopst.h" +#include "df/world.h" +#include "df/machine.h" + + +#include "modules/Buildings.h" + + +using namespace DFHack; +using namespace df::enums; +using namespace RemoteFortressReader; +using namespace std; + +DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::BuildingList *out) +{ + FOR_ENUM_ITEMS(building_type, bt) + { + BuildingDefinition * bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(-1); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt)); + + switch (bt) + { + case df::enums::building_type::NONE: + break; + case df::enums::building_type::Chair: + break; + case df::enums::building_type::Bed: + break; + case df::enums::building_type::Table: + break; + case df::enums::building_type::Coffin: + break; + case df::enums::building_type::FarmPlot: + break; + case df::enums::building_type::Furnace: + FOR_ENUM_ITEMS(furnace_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(furnace_type, st)); + + if (st == furnace_type::Custom) + { + for (size_t i = 0; i < df::global::world->raws.buildings.furnaces.size(); i++) + { + auto cust = df::global::world->raws.buildings.furnaces[i]; + + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(cust->id); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + cust->code); + bld->set_name(cust->name); + } + } + } + break; + case df::enums::building_type::TradeDepot: + break; + case df::enums::building_type::Shop: + FOR_ENUM_ITEMS(shop_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(shop_type, st)); + + } + break; + case df::enums::building_type::Door: + break; + case df::enums::building_type::Floodgate: + break; + case df::enums::building_type::Box: + break; + case df::enums::building_type::Weaponrack: + break; + case df::enums::building_type::Armorstand: + break; + case df::enums::building_type::Workshop: + FOR_ENUM_ITEMS(workshop_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(workshop_type, st)); + + if (st == workshop_type::Custom) + { + for (size_t i = 0; i < df::global::world->raws.buildings.workshops.size(); i++) + { + auto cust = df::global::world->raws.buildings.workshops[i]; + + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(cust->id); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + cust->code); + bld->set_name(cust->name); + } + } + } + break; + case df::enums::building_type::Cabinet: + break; + case df::enums::building_type::Statue: + break; + case df::enums::building_type::WindowGlass: + break; + case df::enums::building_type::WindowGem: + break; + case df::enums::building_type::Well: + break; + case df::enums::building_type::Bridge: + break; + case df::enums::building_type::RoadDirt: + break; + case df::enums::building_type::RoadPaved: + break; + case df::enums::building_type::SiegeEngine: + FOR_ENUM_ITEMS(siegeengine_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(siegeengine_type, st)); + + } + break; + case df::enums::building_type::Trap: + FOR_ENUM_ITEMS(trap_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(trap_type, st)); + + } + break; + case df::enums::building_type::AnimalTrap: + break; + case df::enums::building_type::Support: + break; + case df::enums::building_type::ArcheryTarget: + break; + case df::enums::building_type::Chain: + break; + case df::enums::building_type::Cage: + break; + case df::enums::building_type::Stockpile: + break; + case df::enums::building_type::Civzone: + FOR_ENUM_ITEMS(civzone_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(civzone_type, st)); + + } + break; + case df::enums::building_type::Weapon: + break; + case df::enums::building_type::Wagon: + break; + case df::enums::building_type::ScrewPump: + break; + case df::enums::building_type::Construction: + FOR_ENUM_ITEMS(construction_type, st) + { + bld = out->add_building_list(); + bld->mutable_building_type()->set_building_type(bt); + bld->mutable_building_type()->set_building_subtype(st); + bld->mutable_building_type()->set_building_custom(-1); + bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(construction_type, st)); + + } + break; + case df::enums::building_type::Hatch: + break; + case df::enums::building_type::GrateWall: + break; + case df::enums::building_type::GrateFloor: + break; + case df::enums::building_type::BarsVertical: + break; + case df::enums::building_type::BarsFloor: + break; + case df::enums::building_type::GearAssembly: + break; + case df::enums::building_type::AxleHorizontal: + break; + case df::enums::building_type::AxleVertical: + break; + case df::enums::building_type::WaterWheel: + break; + case df::enums::building_type::Windmill: + break; + case df::enums::building_type::TractionBench: + break; + case df::enums::building_type::Slab: + break; + case df::enums::building_type::Nest: + break; + case df::enums::building_type::NestBox: + break; + case df::enums::building_type::Hive: + break; + case df::enums::building_type::Rollers: + break; + default: + break; + } + } + return CR_OK; +} + + +void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build) +{ + df::building * local_build = df::global::world->buildings.all[buildingIndex]; + remote_build->set_index(local_build->id); + int minZ = local_build->z; + if (local_build->getType() == df::enums::building_type::Well) + { + df::building_wellst * well_building = virtual_cast(local_build); + if (well_building) + minZ = well_building->bucket_z; + } + remote_build->set_pos_x_min(local_build->x1); + remote_build->set_pos_y_min(local_build->y1); + remote_build->set_pos_z_min(minZ); + + remote_build->set_pos_x_max(local_build->x2); + remote_build->set_pos_y_max(local_build->y2); + remote_build->set_pos_z_max(local_build->z); + + auto buildingType = remote_build->mutable_building_type(); + auto type = local_build->getType(); + buildingType->set_building_type(type); + buildingType->set_building_subtype(local_build->getSubtype()); + buildingType->set_building_custom(local_build->getCustomType()); + + auto material = remote_build->mutable_material(); + material->set_mat_type(local_build->mat_type); + material->set_mat_index(local_build->mat_index); + + remote_build->set_building_flags(local_build->flags.whole); + remote_build->set_is_room(local_build->is_room); + if (local_build->room.width > 0 && local_build->room.height > 0 && local_build->room.extents != nullptr) + { + auto room = remote_build->mutable_room(); + room->set_pos_x(local_build->room.x); + room->set_pos_y(local_build->room.y); + room->set_width(local_build->room.width); + room->set_height(local_build->room.height); + for (int i = 0; i < (local_build->room.width * local_build->room.height); i++) + { + room->add_extents(local_build->room.extents[i]); + } + } + + //Add building-specific info + switch (type) + { + case df::enums::building_type::NONE: + { + auto actual = virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Chair: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Bed: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Table: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Coffin: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::FarmPlot: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Furnace: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::TradeDepot: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Shop: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Door: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->door_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::Floodgate: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::Box: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Weaponrack: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Armorstand: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Workshop: + { + auto actual = strict_virtual_cast(local_build); + if (actual && actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + break; + } + case df::enums::building_type::Cabinet: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Statue: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::WindowGlass: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::WindowGem: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Well: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->bucket_z); + } + break; + } + case df::enums::building_type::Bridge: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto direction = actual->direction; + switch (direction) + { + case df::building_bridgest::Retracting: + remote_build->set_direction(NONE); + break; + case df::building_bridgest::Left: + remote_build->set_direction(WEST); + break; + case df::building_bridgest::Right: + remote_build->set_direction(EAST); + break; + case df::building_bridgest::Up: + remote_build->set_direction(NORTH); + break; + case df::building_bridgest::Down: + remote_build->set_direction(SOUTH); + break; + default: + break; + } + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::RoadDirt: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::RoadPaved: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::SiegeEngine: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto facing = actual->facing; + switch (facing) + { + case df::building_siegeenginest::Left: + remote_build->set_direction(WEST); + break; + case df::building_siegeenginest::Up: + remote_build->set_direction(NORTH); + break; + case df::building_siegeenginest::Right: + remote_build->set_direction(EAST); + break; + case df::building_siegeenginest::Down: + remote_build->set_direction(SOUTH); + break; + default: + break; + } + } + break; + } + case df::enums::building_type::Trap: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->state); + } + break; + } + case df::enums::building_type::AnimalTrap: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Support: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->support_flags.bits.triggered); + } + break; + } + case df::enums::building_type::ArcheryTarget: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto facing = actual->archery_direction; + switch (facing) + { + case df::building_archerytargetst::TopToBottom: + remote_build->set_direction(NORTH); + break; + case df::building_archerytargetst::BottomToTop: + remote_build->set_direction(SOUTH); + break; + case df::building_archerytargetst::LeftToRight: + remote_build->set_direction(WEST); + break; + case df::building_archerytargetst::RightToLeft: + remote_build->set_direction(EAST); + break; + default: + break; + } + } + break; + } + case df::enums::building_type::Chain: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->chain_flags.bits.triggered); + } + break; + } + case df::enums::building_type::Cage: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + remote_build->set_active(actual->cage_flags.bits.triggered); + } + break; + } + case df::enums::building_type::Stockpile: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Civzone: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Weapon: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::Wagon: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::ScrewPump: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto direction = actual->direction; + switch (direction) + { + case df::enums::screw_pump_direction::FromNorth: + remote_build->set_direction(NORTH); + break; + case df::enums::screw_pump_direction::FromEast: + remote_build->set_direction(EAST); + break; + case df::enums::screw_pump_direction::FromSouth: + remote_build->set_direction(SOUTH); + break; + case df::enums::screw_pump_direction::FromWest: + remote_build->set_direction(WEST); + break; + default: + break; + } + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + } + break; + case df::enums::building_type::Construction: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + } + break; + } + case df::enums::building_type::Hatch: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->door_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::GrateWall: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::GrateFloor: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::BarsVertical: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::BarsFloor: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->gate_flags.bits.closed) + remote_build->set_active(1); + else + remote_build->set_active(0); + } + break; + } + case df::enums::building_type::GearAssembly: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + break; + } + case df::enums::building_type::AxleHorizontal: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->is_vertical) + remote_build->set_direction(NORTH); + else + remote_build->set_direction(EAST); + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + } + break; + case df::enums::building_type::AxleVertical: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + break; + } + case df::enums::building_type::WaterWheel: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + if (actual->is_vertical) + remote_build->set_direction(NORTH); + else + remote_build->set_direction(EAST); + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + } + break; + case df::enums::building_type::Windmill: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { +#if DF_VERSION_INT > 34011 + if (actual->orient_x < 0) + remote_build->set_direction(WEST); + else if (actual->orient_x > 0) + remote_build->set_direction(EAST); + else if (actual->orient_y < 0) + remote_build->set_direction(NORTH); + else if (actual->orient_y > 0) + remote_build->set_direction(SOUTH); + else +#endif + remote_build->set_direction(WEST); + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + } + break; + case df::enums::building_type::TractionBench: + break; + case df::enums::building_type::Slab: + break; + case df::enums::building_type::Nest: + break; + case df::enums::building_type::NestBox: + break; + case df::enums::building_type::Hive: + break; + case df::enums::building_type::Rollers: + { + auto actual = strict_virtual_cast(local_build); + if (actual) + { + auto direction = actual->direction; + switch (direction) + { + case df::enums::screw_pump_direction::FromNorth: + remote_build->set_direction(NORTH); + break; + case df::enums::screw_pump_direction::FromEast: + remote_build->set_direction(EAST); + break; + case df::enums::screw_pump_direction::FromSouth: + remote_build->set_direction(SOUTH); + break; + case df::enums::screw_pump_direction::FromWest: + remote_build->set_direction(WEST); + break; + default: + break; + } + if (actual->machine.machine_id >= 0) + { + auto mach = df::machine::find(actual->machine.machine_id); + remote_build->set_active(mach->flags.bits.active); + } + } + } + break; + default: + break; + } +} + diff --git a/plugins/remotefortressreader/building_reader.h b/plugins/remotefortressreader/building_reader.h new file mode 100644 index 000000000..8b3743a1f --- /dev/null +++ b/plugins/remotefortressreader/building_reader.h @@ -0,0 +1,10 @@ +#ifndef BUILDING_READER_H +#define BUILDING_READER_H +#include +#include "RemoteClient.h" +#include "RemoteFortressReader.pb.h" + +DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::BuildingList *out); +void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * remote_build); + +#endif \ No newline at end of file diff --git a/plugins/remotefortressreader/df_version_int.h b/plugins/remotefortressreader/df_version_int.h new file mode 100644 index 000000000..35c9ed851 --- /dev/null +++ b/plugins/remotefortressreader/df_version_int.h @@ -0,0 +1,2 @@ +#pragma once +#define DF_VERSION_INT 44002 diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp new file mode 100644 index 000000000..3884d269f --- /dev/null +++ b/plugins/remotefortressreader/item_reader.cpp @@ -0,0 +1,675 @@ +#include "item_reader.h" +#include "Core.h" +#include "VersionInfo.h" +#include "ItemdefInstrument.pb.h" + +#include "df/art_image.h" +#include "df/art_image_chunk.h" +#include "df/art_image_element.h" +#include "df/art_image_element_creaturest.h" +#include "df/art_image_element_itemst.h" +#include "df/art_image_element_plantst.h" +#include "df/art_image_element_shapest.h" +#include "df/art_image_element_treest.h" +#include "df/art_image_element_type.h" +#include "df/art_image_property.h" +#include "df/art_image_property_intransitive_verbst.h" +#include "df/art_image_property_transitive_verbst.h" +#include "df/art_image_ref.h" +#include "df/descriptor_shape.h" +#include "df/instrument_piece.h" +#include "df/instrument_register.h" +#include "df/item_type.h" +#include "df/item_constructed.h" +#include "df/item_gemst.h" +#include "df/item_smallgemst.h" +#include "df/item_statuest.h" +#include "df/item_threadst.h" +#include "df/item_toolst.h" +#include "df/itemdef_instrumentst.h" +#include "df/itemdef_toolst.h" +#include "df/itemimprovement.h" +#include "df/itemimprovement_art_imagest.h" +#include "df/itemimprovement_bandsst.h" +#include "df/itemimprovement_coveredst.h" +#include "df/itemimprovement_illustrationst.h" +#include "df/itemimprovement_itemspecificst.h" +#include "df/itemimprovement_sewn_imagest.h" +#include "df/itemimprovement_specific_type.h" +#include "df/itemimprovement_threadst.h" +#include "df/itemdef.h" +#include "df/map_block.h" +#include "df/vehicle.h" +#include "df/world.h" + +#include "modules/Items.h" +#include "modules/MapCache.h" +#include "modules/Materials.h" +#include "MiscUtils.h" + + +using namespace DFHack; +using namespace df::enums; +using namespace RemoteFortressReader; +using namespace ItemdefInstrument; +using namespace std; +using namespace df::global; + + +void CopyImage(const df::art_image * image, ArtImage * netImage) +{ + auto id = netImage->mutable_id(); + id->set_mat_type(image->id); + id->set_mat_index(image->subid); + for (size_t i = 0; i < image->elements.size(); i++) + { + auto element = image->elements[i]; + auto netElement = netImage->add_elements(); + auto elementType = element->getType(); + netElement->set_type((ArtImageElementType)elementType); + + netElement->set_count(element->count); + + switch (elementType) + { + case df::enums::art_image_element_type::CREATURE: + { + VIRTUAL_CAST_VAR(creature, df::art_image_element_creaturest, element); + auto cret = netElement->mutable_creature_item(); + cret->set_mat_type(creature->race); + cret->set_mat_index(creature->caste); + break; + } + case df::enums::art_image_element_type::PLANT: + { + VIRTUAL_CAST_VAR(plant, df::art_image_element_plantst, element); + netElement->set_id(plant->plant_id); + break; + } + case df::enums::art_image_element_type::TREE: + { + VIRTUAL_CAST_VAR(tree, df::art_image_element_treest, element); + netElement->set_id(tree->plant_id); + break; + } + case df::enums::art_image_element_type::SHAPE: + { + VIRTUAL_CAST_VAR(shape, df::art_image_element_shapest, element); + netElement->set_id(shape->shape_id); + break; + } + case df::enums::art_image_element_type::ITEM: + { + VIRTUAL_CAST_VAR(item, df::art_image_element_itemst, element); + auto it = netElement->mutable_creature_item(); + it->set_mat_type(item->item_type); + it->set_mat_index(item->item_subtype); + netElement->set_id(item->item_id); + switch (item->item_type) + { + case item_type::PLANT: + it->set_mat_index(item->mat_index); + default: + break; + } + auto mat = netElement->mutable_material(); + mat->set_mat_type(item->mat_type); + mat->set_mat_index(item->mat_index); + break; + } + default: + break; + } + } + for (size_t i = 0; i < image->properties.size(); i++) + { + auto dfProperty = image->properties[i]; + auto netProperty = netImage->add_properties(); + auto propertyType = dfProperty->getType(); + + netProperty->set_type((ArtImagePropertyType)propertyType); + + switch (propertyType) + { + case df::enums::art_image_property_type::transitive_verb: + { + VIRTUAL_CAST_VAR(transitive, df::art_image_property_transitive_verbst, dfProperty); + netProperty->set_subject(transitive->subject); + netProperty->set_object(transitive->object); + netProperty->set_verb((ArtImageVerb)transitive->verb); + break; + } + case df::enums::art_image_property_type::intransitive_verb: + { + VIRTUAL_CAST_VAR(intransitive, df::art_image_property_intransitive_verbst, dfProperty); + netProperty->set_subject(intransitive->subject); + netProperty->set_verb((ArtImageVerb)intransitive->verb); + break; + } + default: + break; + } + } +} + +void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem) +{ + NetItem->set_id(DfItem->id); + NetItem->set_flags1(DfItem->flags.whole); + NetItem->set_flags2(DfItem->flags2.whole); + auto pos = NetItem->mutable_pos(); + pos->set_x(DfItem->pos.x); + pos->set_y(DfItem->pos.y); + pos->set_z(DfItem->pos.z); + auto mat = NetItem->mutable_material(); + mat->set_mat_index(DfItem->getMaterialIndex()); + mat->set_mat_type(DfItem->getMaterial()); + auto type = NetItem->mutable_type(); + type->set_mat_type(DfItem->getType()); + type->set_mat_index(DfItem->getSubtype()); + + bool isProjectile = false; + + item_type::item_type itemType = DfItem->getType(); + + switch (itemType) + { + case df::enums::item_type::NONE: + break; + case df::enums::item_type::BAR: + break; + case df::enums::item_type::SMALLGEM: + { + VIRTUAL_CAST_VAR(smallgem_item, df::item_smallgemst, DfItem); + type->set_mat_index(smallgem_item->shape); + break; + } + case df::enums::item_type::BLOCKS: + break; + case df::enums::item_type::ROUGH: + break; + case df::enums::item_type::BOULDER: + break; + case df::enums::item_type::WOOD: + break; + case df::enums::item_type::DOOR: + break; + case df::enums::item_type::FLOODGATE: + break; + case df::enums::item_type::BED: + break; + case df::enums::item_type::CHAIR: + break; + case df::enums::item_type::CHAIN: + break; + case df::enums::item_type::FLASK: + break; + case df::enums::item_type::GOBLET: + break; + case df::enums::item_type::INSTRUMENT: + break; + case df::enums::item_type::TOY: + break; + case df::enums::item_type::WINDOW: + break; + case df::enums::item_type::CAGE: + break; + case df::enums::item_type::BARREL: + break; + case df::enums::item_type::BUCKET: + break; + case df::enums::item_type::ANIMALTRAP: + break; + case df::enums::item_type::TABLE: + break; + case df::enums::item_type::COFFIN: + break; + case df::enums::item_type::STATUE: + { + VIRTUAL_CAST_VAR(statue, df::item_statuest, DfItem); + + df::art_image_chunk * chunk = NULL; + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); + if (GetArtImageChunk) + { + chunk = GetArtImageChunk(&(world->art_image_chunks), statue->image.id); + } + else + { + for (size_t i = 0; i < world->art_image_chunks.size(); i++) + { + if (world->art_image_chunks[i]->id == statue->image.id) + chunk = world->art_image_chunks[i]; + } + } + if (chunk) + { + CopyImage(chunk->images[statue->image.subid], NetItem->mutable_image()); + } + + + break; + } + case df::enums::item_type::CORPSE: + break; + case df::enums::item_type::WEAPON: + break; + case df::enums::item_type::ARMOR: + break; + case df::enums::item_type::SHOES: + break; + case df::enums::item_type::SHIELD: + break; + case df::enums::item_type::HELM: + break; + case df::enums::item_type::GLOVES: + break; + case df::enums::item_type::BOX: + type->set_mat_index(DfItem->isBag()); + break; + case df::enums::item_type::BIN: + break; + case df::enums::item_type::ARMORSTAND: + break; + case df::enums::item_type::WEAPONRACK: + break; + case df::enums::item_type::CABINET: + break; + case df::enums::item_type::FIGURINE: + break; + case df::enums::item_type::AMULET: + break; + case df::enums::item_type::SCEPTER: + break; + case df::enums::item_type::AMMO: + break; + case df::enums::item_type::CROWN: + break; + case df::enums::item_type::RING: + break; + case df::enums::item_type::EARRING: + break; + case df::enums::item_type::BRACELET: + break; + case df::enums::item_type::GEM: + { + VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem); + type->set_mat_index(gem_item->shape); + break; + } + case df::enums::item_type::ANVIL: + break; + case df::enums::item_type::CORPSEPIECE: + break; + case df::enums::item_type::REMAINS: + break; + case df::enums::item_type::MEAT: + break; + case df::enums::item_type::FISH: + break; + case df::enums::item_type::FISH_RAW: + break; + case df::enums::item_type::VERMIN: + break; + case df::enums::item_type::PET: + break; + case df::enums::item_type::SEEDS: + break; + case df::enums::item_type::PLANT: + //For convenience, we encode the plant type into item type, even if it's available in the material. + type->set_mat_index(DfItem->getMaterialIndex()); + break; + case df::enums::item_type::SKIN_TANNED: + break; + case df::enums::item_type::PLANT_GROWTH: + break; + case df::enums::item_type::THREAD: + { + VIRTUAL_CAST_VAR(thread, df::item_threadst, DfItem); + if (thread && thread->dye_mat_type >= 0) + { + DFHack::MaterialInfo info; + if (info.decode(thread->dye_mat_type, thread->dye_mat_index)) + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + if (DfItem->flags.bits.spider_web) + type->set_mat_index(1); + else + type->set_mat_index(0); + break; + } + case df::enums::item_type::CLOTH: + break; + case df::enums::item_type::TOTEM: + break; + case df::enums::item_type::PANTS: + break; + case df::enums::item_type::BACKPACK: + break; + case df::enums::item_type::QUIVER: + break; + case df::enums::item_type::CATAPULTPARTS: + break; + case df::enums::item_type::BALLISTAPARTS: + break; + case df::enums::item_type::SIEGEAMMO: + break; + case df::enums::item_type::BALLISTAARROWHEAD: + break; + case df::enums::item_type::TRAPPARTS: + break; + case df::enums::item_type::TRAPCOMP: + break; + case df::enums::item_type::DRINK: + break; + case df::enums::item_type::POWDER_MISC: + break; + case df::enums::item_type::CHEESE: + break; + case df::enums::item_type::FOOD: + break; + case df::enums::item_type::LIQUID_MISC: + break; + case df::enums::item_type::COIN: + break; + case df::enums::item_type::GLOB: + break; + case df::enums::item_type::ROCK: + break; + case df::enums::item_type::PIPE_SECTION: + break; + case df::enums::item_type::HATCH_COVER: + break; + case df::enums::item_type::GRATE: + break; + case df::enums::item_type::QUERN: + break; + case df::enums::item_type::MILLSTONE: + break; + case df::enums::item_type::SPLINT: + break; + case df::enums::item_type::CRUTCH: + break; + case df::enums::item_type::TRACTION_BENCH: + break; + case df::enums::item_type::ORTHOPEDIC_CAST: + break; + case df::enums::item_type::TOOL: + if (!isProjectile) + { + VIRTUAL_CAST_VAR(tool, df::item_toolst, DfItem); + if (tool) + { + auto vehicle = binsearch_in_vector(world->vehicles.active, tool->vehicle_id); + if (vehicle) + { + NetItem->set_subpos_x(vehicle->offset_x / 100000.0); + NetItem->set_subpos_y(vehicle->offset_y / 100000.0); + NetItem->set_subpos_z(vehicle->offset_z / 140000.0); + } + } + } + break; + case df::enums::item_type::SLAB: + break; + case df::enums::item_type::EGG: + break; + case df::enums::item_type::BOOK: + break; + case df::enums::item_type::SHEET: + break; + case df::enums::item_type::BRANCH: + break; + default: + break; + } + + VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem); + if (actual_item) + { + NetItem->set_stack_size(actual_item->stack_size); + } + + VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem); + if (constructed_item) + { + for (size_t i = 0; i < constructed_item->improvements.size(); i++) + { + auto improvement = constructed_item->improvements[i]; + + if (!improvement) + continue; + + improvement_type::improvement_type impType = improvement->getType(); + + auto netImp = NetItem->add_improvements(); + + netImp->set_type((ImprovementType)impType); + + auto mat = netImp->mutable_material(); + mat->set_mat_type(improvement->mat_type); + mat->set_mat_index(improvement->mat_index); + + switch (impType) + { + case df::enums::improvement_type::ART_IMAGE: + { + VIRTUAL_CAST_VAR(artImage, df::itemimprovement_art_imagest, improvement); + CopyImage(artImage->getImage(DfItem), netImp->mutable_image()); + break; + } + case df::enums::improvement_type::COVERED: + { + VIRTUAL_CAST_VAR(covered, df::itemimprovement_coveredst, improvement); + netImp->set_shape(covered->shape); + break; + } + case df::enums::improvement_type::RINGS_HANGING: + break; + case df::enums::improvement_type::BANDS: + { + VIRTUAL_CAST_VAR(bands, df::itemimprovement_bandsst, improvement); + netImp->set_shape(bands->shape); + break; + } + case df::enums::improvement_type::ITEMSPECIFIC: + { + VIRTUAL_CAST_VAR(specific, df::itemimprovement_itemspecificst, improvement); + netImp->set_specific_type(specific->type); + break; + } + case df::enums::improvement_type::THREAD: + { + VIRTUAL_CAST_VAR(improvement_thread, df::itemimprovement_threadst, improvement); + if (improvement_thread->dye.mat_type >= 0) + { + DFHack::MaterialInfo info; + if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index)) + continue; + ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye()); + } + break; + } + case df::enums::improvement_type::CLOTH: + break; + case df::enums::improvement_type::SEWN_IMAGE: + break; + case df::enums::improvement_type::PAGES: + break; + case df::enums::improvement_type::ILLUSTRATION: + break; + case df::enums::improvement_type::INSTRUMENT_PIECE: + break; + case df::enums::improvement_type::WRITING: + break; + default: + break; + } + } + } + + NetItem->set_volume(DfItem->getVolume()); +} + +DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out) +{ + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + FOR_ENUM_ITEMS(item_type, it) + { + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(-1); + mat_def->set_id(ENUM_KEY_STR(item_type, it)); + switch (it) + { + case df::enums::item_type::GEM: + case df::enums::item_type::SMALLGEM: + { + for (size_t i = 0; i < world->raws.descriptors.shapes.size(); i++) + { + auto shape = world->raws.descriptors.shapes[i]; + if (shape->gems_use.whole == 0) + continue; + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id); + } + break; + } + case df::enums::item_type::PLANT: + { + for (size_t i = 0; i < world->raws.plants.all.size(); i++) + { + auto plantRaw = world->raws.plants.all[i]; + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(plantRaw->index); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + plantRaw->id); + } + break; + } + case df::enums::item_type::BOX: + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("BOX/CHEST"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("BOX/BAG"); + break; + } + case df::enums::item_type::THREAD: + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(0); + mat_def->set_id("THREAD/NORMAL"); + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(1); + mat_def->set_id("THREAD/WEB"); + break; + } + default: + break; + } + int subtypes = Items::getSubtypeCount(it); + if (subtypes >= 0) + { + for (int i = 0; i < subtypes; i++) + { + mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type((int)it); + mat_def->mutable_mat_pair()->set_mat_index(i); + df::itemdef * item = Items::getSubtypeDef(it, i); + mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + item->id); + switch (it) + { + case df::enums::item_type::INSTRUMENT: + { + VIRTUAL_CAST_VAR(instrument, df::itemdef_instrumentst, item); + mat_def->set_name(DF2UTF(instrument->name)); + auto send_instrument = mat_def->mutable_instrument(); + auto flags = send_instrument->mutable_flags(); + flags->set_indefinite_pitch(instrument->flags.is_set(instrument_flags::INDEFINITE_PITCH)); + flags->set_placed_as_building(instrument->flags.is_set(instrument_flags::PLACED_AS_BUILDING)); + flags->set_metal_mat(instrument->flags.is_set(instrument_flags::METAL_MAT)); + flags->set_stone_mat(instrument->flags.is_set(instrument_flags::STONE_MAT)); + flags->set_wood_mat(instrument->flags.is_set(instrument_flags::WOOD_MAT)); + flags->set_glass_mat(instrument->flags.is_set(instrument_flags::GLASS_MAT)); + flags->set_ceramic_mat(instrument->flags.is_set(instrument_flags::CERAMIC_MAT)); + flags->set_shell_mat(instrument->flags.is_set(instrument_flags::SHELL_MAT)); + flags->set_bone_mat(instrument->flags.is_set(instrument_flags::BONE_MAT)); + send_instrument->set_size(instrument->size); + send_instrument->set_value(instrument->value); + send_instrument->set_material_size(instrument->material_size); + for (size_t j = 0; j < instrument->pieces.size(); j++) + { + auto piece = send_instrument->add_pieces(); + piece->set_type(instrument->pieces[j]->type); + piece->set_id(instrument->pieces[j]->id); + piece->set_name(DF2UTF(instrument->pieces[j]->name)); + piece->set_name_plural(DF2UTF(instrument->pieces[j]->name_plural)); + } + send_instrument->set_pitch_range_min(instrument->pitch_range_min); + send_instrument->set_pitch_range_max(instrument->pitch_range_max); + for (size_t j = 0; j < instrument->sound_production.size(); j++) + { + send_instrument->add_sound_production((SoundProductionType)instrument->sound_production[j]); + } + for (size_t j = 0; j < instrument->sound_production_parm1.size(); j++) + { + send_instrument->add_sound_production_parm1(*(instrument->sound_production_parm1[j])); + } + for (size_t j = 0; j < instrument->sound_production_parm2.size(); j++) + { + send_instrument->add_sound_production_parm2(*(instrument->sound_production_parm2[j])); + } + for (size_t j = 0; j < instrument->pitch_choice.size(); j++) + { + send_instrument->add_pitch_choice((PitchChoiceType)instrument->pitch_choice[j]); + } + for (size_t j = 0; j < instrument->pitch_choice_parm1.size(); j++) + { + send_instrument->add_pitch_choice_parm1(*(instrument->pitch_choice_parm1[j])); + } + for (size_t j = 0; j < instrument->pitch_choice_parm2.size(); j++) + { + send_instrument->add_pitch_choice_parm2(*(instrument->pitch_choice_parm2[j])); + } + for (size_t j = 0; j < instrument->tuning.size(); j++) + { + send_instrument->add_tuning((TuningType)instrument->tuning[j]); + } + for (size_t j = 0; j < instrument->tuning_parm.size(); j++) + { + send_instrument->add_tuning_parm(*(instrument->tuning_parm[j])); + } + for (size_t j = 0; j < instrument->registers.size(); j++) + { + auto reg = send_instrument->add_registers(); + reg->set_pitch_range_min(instrument->registers[j]->pitch_range_min); + reg->set_pitch_range_max(instrument->registers[j]->pitch_range_max); + } + send_instrument->set_description(DF2UTF(instrument->description)); + break; + } + case df::enums::item_type::TOOL: + { + VIRTUAL_CAST_VAR(tool, df::itemdef_toolst, item); + mat_def->set_name(DF2UTF(tool->name)); + } + default: + break; + } + } + } + } + return CR_OK; +} diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h new file mode 100644 index 000000000..045c1be51 --- /dev/null +++ b/plugins/remotefortressreader/item_reader.h @@ -0,0 +1,32 @@ +#ifndef ITEM_READER_H +#define ITEM_READER_H + +#include +#include "RemoteClient.h" +#include "RemoteFortressReader.pb.h" + +#include "DataDefs.h" + +namespace df +{ + struct item; + struct map_block; + struct art_image; + struct art_image_chunk; + struct world; +} + +namespace MapExtras +{ + class MapCache; +} + +DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out); +void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem); +void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out); + +typedef df::art_image_chunk * (*GET_ART_IMAGE_CHUNK)(std::vector *, int); + +void CopyImage(const df::art_image * image, RemoteFortressReader::ArtImage * netImage); + +#endif // !ITEM_READER_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp new file mode 100644 index 000000000..7dced3b9f --- /dev/null +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -0,0 +1,3050 @@ +#include "df_version_int.h" +#define RFR_VERSION "0.19.1" + +#include +#include +#include + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "Hooks.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "RemoteFortressReader.pb.h" +#include "RemoteServer.h" +#include "SDL_events.h" +#include "SDL_keyboard.h" +#include "TileTypes.h" +#include "VersionInfo.h" +#if DF_VERSION_INT > 34011 +#include "DFHackVersion.h" +#endif +#include "modules/Gui.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" +#include "modules/Materials.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "modules/World.h" + +#include "df/unit_action.h" +#if DF_VERSION_INT > 34011 +#include "df/army.h" +#include "df/army_flags.h" +#include "df/block_square_event_item_spatterst.h" +#include "df/block_square_event_grassst.h" +#endif +#include "df/art_image_element_shapest.h" +#include "df/block_square_event_material_spatterst.h" +#include "df/body_appearance_modifier.h" +#include "df/body_part_layer_raw.h" +#include "df/body_part_raw.h" +#include "df/bp_appearance_modifier.h" +#include "df/builtin_mats.h" +#include "df/building_wellst.h" + +#include "df/caste_raw.h" +#include "df/caste_raw.h" +#include "df/color_modifier_raw.h" +#include "df/creature_raw.h" +#include "df/creature_raw.h" +#include "df/descriptor_color.h" +#include "df/descriptor_pattern.h" +#include "df/descriptor_shape.h" +#include "df/dfhack_material_category.h" +#include "df/enabler.h" +#include "df/engraving.h" +#include "df/flow_info.h" +#include "df/flow_guide.h" +#include "df/flow_guide_item_cloudst.h" +#include "df/graphic.h" +#include "df/historical_figure.h" + +#include "df/job.h" +#include "df/job_type.h" +#include "df/job_item.h" +#include "df/job_material_category.h" +#include "df/map_block_column.h" +#include "df/material_vec_ref.h" +#include "df/matter_state.h" +#include "df/mental_attribute_type.h" +#include "df/ocean_wave.h" +#include "df/physical_attribute_type.h" +#include "df/plant.h" +#include "df/plant_raw_flags.h" +#include "df/projectile.h" +#include "df/proj_itemst.h" +#include "df/proj_unitst.h" +#include "df/region_map_entry.h" +#include "df/report.h" +#include "df/site_realization_building.h" +#include "df/site_realization_building_info_castle_towerst.h" +#include "df/site_realization_building_info_castle_wallst.h" +#if DF_VERSION_INT > 34011 +#include "df/site_realization_building_info_trenchesst.h" +#endif +#include "df/tissue.h" +#include "df/tissue_style_raw.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_inventory_item.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/vehicle.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_history.h" +#include "df/world_population.h" +#include "df/world_region.h" +#include "df/world_region_details.h" +#include "df/world_site.h" +#include "df/world_site_realization.h" +#include "df/entity_position.h" + +#if DF_VERSION_INT > 40001 +#include "df/plant_growth.h" +#include "df/plant_growth_print.h" +#include "df/plant_tree_info.h" +#include "df/plant_tree_tile.h" +#endif + +#include "df/unit_relationship_type.h" + +#include "adventure_control.h" +#include "building_reader.h" +#include "item_reader.h" + +using namespace DFHack; +using namespace df::enums; +using namespace RemoteFortressReader; +using namespace std; + +DFHACK_PLUGIN("RemoteFortressReader"); + +#ifndef REQUIRE_GLOBAL +using namespace df::global; +#else +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(gamemode); +REQUIRE_GLOBAL(ui_advmode); +#endif + +// Here go all the command declarations... +// mostly to allow having the mandatory stuff on top of the file and commands on the bottom + +static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); +static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); +static command_result GetTiletypeList(color_ostream &stream, const EmptyMessage *in, TiletypeList *out); +static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out); +static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out); +static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in); +static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out); +static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out); +static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out); +static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out); +static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); +static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out); +static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out); +static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out); +static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); +static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out); +static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out); +static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out); +static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out); +static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out); +static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); +static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); +static command_result SendDigCommand(color_ostream &stream, const DigCommand *in); +static command_result SetPauseState(color_ostream & stream, const SingleBool * in); +static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); +static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); +static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out); +static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out); + + +void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); + +const char* growth_locations[] = { + "TWIGS", + "LIGHT_BRANCHES", + "HEAVY_BRANCHES", + "TRUNK", + "ROOTS", + "CAP", + "SAPLING", + "SHRUB" +}; +#define GROWTH_LOCATIONS_SIZE 8 + + +#include "df/art_image.h" +#include "df/art_image_chunk.h" +#include "df/art_image_ref.h" +command_result loadArtImageChunk(color_ostream &out, vector & parameters) +{ + if (parameters.size() != 1) + return CR_WRONG_USAGE; + + if (!Core::getInstance().isWorldLoaded()) + { + out.printerr("No world loaded\n"); + return CR_FAILURE; + } + + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); + if (GetArtImageChunk) + { + int index = atoi(parameters[0].c_str()); + auto chunk = GetArtImageChunk(&(world->art_image_chunks), index); + out.print("Loaded chunk id: %d\n", chunk->id); + } + return CR_OK; +} + +command_result dump_bp_mods(color_ostream &out, vector & parameters) +{ + remove("bp_appearance_mods.csv"); + ofstream output; + output.open("bp_appearance_mods.csv"); + + output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n"; + + for (size_t creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++) + { + auto creatureRaw = world->raws.creatures.all[creatureIndex]; + for (size_t casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++) + { + df::caste_raw *casteRaw = creatureRaw->caste[casteIndex]; + for (size_t partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++) + { + output << creatureIndex << ";"; + output << creatureRaw->creature_id << ";"; + output << casteRaw->caste_id << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";"; + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";"; + int layer = casteRaw->bp_appearance.layer_idx[partIndex]; + if (layer < 0) + output << "N/A;"; + else + output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";"; + output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";"; + auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]; +#if DF_VERSION_INT > 34011 + if (appMod->growth_rate > 0) + { + output << appMod->growth_min << " - " << appMod->growth_max << "\n"; + } + else +#endif + { + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - "; + output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n"; + } + } + } + } + + output.close(); + + return CR_OK; +} + +command_result RemoteFortressReader_version(color_ostream &out, vector ¶meters) +{ + out.print(RFR_VERSION); + return CR_OK; +} + +DFHACK_PLUGIN_IS_ENABLED(enableUpdates); + +// Mandatory init function. If you have some global state, create it here. +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + //// Fill the command list with your commands. + commands.push_back(PluginCommand( + "dump-bp-mods", "Dump bodypart mods for debugging", + dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " isoworldremote\n" + " Does nothing.\n" + )); + commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); + commands.push_back(PluginCommand( + "load-art-image-chunk", + "Gets an art image chunk by index, loading from disk if necessary", + loadArtImageChunk, false, + "Usage: load_art_image_chunk N, where N is the id of the chunk to get.")); + enableUpdates = true; + return CR_OK; +} + +#ifndef SF_ALLOW_REMOTE +#define SF_ALLOW_REMOTE 0 +#endif // !SF_ALLOW_REMOTE + +DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) +{ + RPCService *svc = new RPCService(); + svc->addFunction("GetMaterialList", GetMaterialList, SF_ALLOW_REMOTE); + svc->addFunction("GetGrowthList", GetGrowthList, SF_ALLOW_REMOTE); + svc->addFunction("GetBlockList", GetBlockList, SF_ALLOW_REMOTE); + svc->addFunction("CheckHashes", CheckHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetTiletypeList", GetTiletypeList, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantList", GetPlantList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitList", GetUnitList, SF_ALLOW_REMOTE); + svc->addFunction("GetUnitListInside", GetUnitListInside, SF_ALLOW_REMOTE); + svc->addFunction("GetViewInfo", GetViewInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetMapInfo", GetMapInfo, SF_ALLOW_REMOTE); + svc->addFunction("ResetMapHashes", ResetMapHashes, SF_ALLOW_REMOTE); + svc->addFunction("GetItemList", GetItemList, SF_ALLOW_REMOTE); + svc->addFunction("GetBuildingDefList", GetBuildingDefList, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMap", GetWorldMap, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapNew", GetWorldMapNew, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMaps", GetRegionMaps, SF_ALLOW_REMOTE); + svc->addFunction("GetRegionMapsNew", GetRegionMapsNew, SF_ALLOW_REMOTE); + svc->addFunction("GetCreatureRaws", GetCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialCreatureRaws", GetPartialCreatureRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetWorldMapCenter", GetWorldMapCenter, SF_ALLOW_REMOTE); + svc->addFunction("GetPlantRaws", GetPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("GetPartialPlantRaws", GetPartialPlantRaws, SF_ALLOW_REMOTE); + svc->addFunction("CopyScreen", CopyScreen, SF_ALLOW_REMOTE); + svc->addFunction("PassKeyboardEvent", PassKeyboardEvent, SF_ALLOW_REMOTE); + svc->addFunction("SendDigCommand", SendDigCommand, SF_ALLOW_REMOTE); + svc->addFunction("SetPauseState", SetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE); + svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE); + svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE); + svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE); + svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE); + svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE); + svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); + svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE); + svc->addFunction("GetLanguage", GetLanguage, SF_ALLOW_REMOTE); + return svc; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!enableUpdates) + return CR_OK; + KeyUpdate(); + return CR_OK; +} + +uint16_t fletcher16(uint8_t const *data, size_t bytes) +{ + uint16_t sum1 = 0xff, sum2 = 0xff; + + while (bytes) { + size_t tlen = bytes > 20 ? 20 : bytes; + bytes -= tlen; + do { + sum2 += sum1 += *data++; + } while (--tlen); + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + } + /* Second reduction step to reduce sums to 8 bits */ + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + return sum2 << 8 | sum1; +} + +void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) +{ + if (!df::global::enabler) + return; + + auto enabler = df::global::enabler; + + out->set_red((int)(enabler->ccolor[index][0] * 255)); + out->set_green((int)(enabler->ccolor[index][1] * 255)); + out->set_blue((int)(enabler->ccolor[index][2] * 255)); +} + +void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) +{ + int index = in[0] | (8 * in[2]); + ConvertDfColor(index, out); +} + +void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out) +{ + df::descriptor_color *color = world->raws.descriptors.colors[index]; + out->set_red(color->red * 255); + out->set_green(color->green * 255); + out->set_blue(color->blue * 255); +} + +void ConvertDFCoord(DFCoord in, RemoteFortressReader::Coord * out) +{ + out->set_x(in.x); + out->set_y(in.y); + out->set_z(in.z); +} + +void ConvertDFCoord(int x, int y, int z, RemoteFortressReader::Coord * out) +{ + out->set_x(x); + out->set_y(y); + out->set_z(z); +} + + +RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) +{ + switch (material) + { + case df::enums::tiletype_material::NONE: + return RemoteFortressReader::NO_MATERIAL; + break; + case df::enums::tiletype_material::AIR: + return RemoteFortressReader::AIR; + break; + case df::enums::tiletype_material::SOIL: + return RemoteFortressReader::SOIL; + break; + case df::enums::tiletype_material::STONE: + return RemoteFortressReader::STONE; + break; + case df::enums::tiletype_material::FEATURE: + return RemoteFortressReader::FEATURE; + break; + case df::enums::tiletype_material::LAVA_STONE: + return RemoteFortressReader::LAVA_STONE; + break; + case df::enums::tiletype_material::MINERAL: + return RemoteFortressReader::MINERAL; + break; + case df::enums::tiletype_material::FROZEN_LIQUID: + return RemoteFortressReader::FROZEN_LIQUID; + break; + case df::enums::tiletype_material::CONSTRUCTION: + return RemoteFortressReader::CONSTRUCTION; + break; + case df::enums::tiletype_material::GRASS_LIGHT: + return RemoteFortressReader::GRASS_LIGHT; + break; + case df::enums::tiletype_material::GRASS_DARK: + return RemoteFortressReader::GRASS_DARK; + break; + case df::enums::tiletype_material::GRASS_DRY: + return RemoteFortressReader::GRASS_DRY; + break; + case df::enums::tiletype_material::GRASS_DEAD: + return RemoteFortressReader::GRASS_DEAD; + break; + case df::enums::tiletype_material::PLANT: + return RemoteFortressReader::PLANT; + break; + case df::enums::tiletype_material::HFS: + return RemoteFortressReader::HFS; + break; + case df::enums::tiletype_material::CAMPFIRE: + return RemoteFortressReader::CAMPFIRE; + break; + case df::enums::tiletype_material::FIRE: + return RemoteFortressReader::FIRE; + break; + case df::enums::tiletype_material::ASHES: + return RemoteFortressReader::ASHES; + break; + case df::enums::tiletype_material::MAGMA: + return RemoteFortressReader::MAGMA; + break; + case df::enums::tiletype_material::DRIFTWOOD: + return RemoteFortressReader::DRIFTWOOD; + break; + case df::enums::tiletype_material::POOL: + return RemoteFortressReader::POOL; + break; + case df::enums::tiletype_material::BROOK: + return RemoteFortressReader::BROOK; + break; + case df::enums::tiletype_material::RIVER: + return RemoteFortressReader::RIVER; + break; +#if DF_VERSION_INT > 40001 + case df::enums::tiletype_material::ROOT: + return RemoteFortressReader::ROOT; + break; + case df::enums::tiletype_material::TREE: + return RemoteFortressReader::TREE_MATERIAL; + break; + case df::enums::tiletype_material::MUSHROOM: + return RemoteFortressReader::MUSHROOM; + break; + case df::enums::tiletype_material::UNDERWORLD_GATE: + return RemoteFortressReader::UNDERWORLD_GATE; + break; +#endif + default: + return RemoteFortressReader::NO_MATERIAL; + break; + } + return RemoteFortressReader::NO_MATERIAL; +} + +RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special special) +{ + switch (special) + { + case df::enums::tiletype_special::NONE: + return RemoteFortressReader::NO_SPECIAL; + break; + case df::enums::tiletype_special::NORMAL: + return RemoteFortressReader::NORMAL; + break; + case df::enums::tiletype_special::RIVER_SOURCE: + return RemoteFortressReader::RIVER_SOURCE; + break; + case df::enums::tiletype_special::WATERFALL: + return RemoteFortressReader::WATERFALL; + break; + case df::enums::tiletype_special::SMOOTH: + return RemoteFortressReader::SMOOTH; + break; + case df::enums::tiletype_special::FURROWED: + return RemoteFortressReader::FURROWED; + break; + case df::enums::tiletype_special::WET: + return RemoteFortressReader::WET; + break; + case df::enums::tiletype_special::DEAD: + return RemoteFortressReader::DEAD; + break; + case df::enums::tiletype_special::WORN_1: + return RemoteFortressReader::WORN_1; + break; + case df::enums::tiletype_special::WORN_2: + return RemoteFortressReader::WORN_2; + break; + case df::enums::tiletype_special::WORN_3: + return RemoteFortressReader::WORN_3; + break; + case df::enums::tiletype_special::TRACK: + return RemoteFortressReader::TRACK; + break; +#if DF_VERSION_INT > 40001 + case df::enums::tiletype_special::SMOOTH_DEAD: + return RemoteFortressReader::SMOOTH_DEAD; + break; +#endif + default: + return RemoteFortressReader::NO_SPECIAL; + break; + } + return RemoteFortressReader::NO_SPECIAL; +} + +RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape) +{ + switch (shape) + { + case df::enums::tiletype_shape::NONE: + return RemoteFortressReader::NO_SHAPE; + break; + case df::enums::tiletype_shape::EMPTY: + return RemoteFortressReader::EMPTY; + break; + case df::enums::tiletype_shape::FLOOR: + return RemoteFortressReader::FLOOR; + break; + case df::enums::tiletype_shape::BOULDER: + return RemoteFortressReader::BOULDER; + break; + case df::enums::tiletype_shape::PEBBLES: + return RemoteFortressReader::PEBBLES; + break; + case df::enums::tiletype_shape::WALL: + return RemoteFortressReader::WALL; + break; + case df::enums::tiletype_shape::FORTIFICATION: + return RemoteFortressReader::FORTIFICATION; + break; + case df::enums::tiletype_shape::STAIR_UP: + return RemoteFortressReader::STAIR_UP; + break; + case df::enums::tiletype_shape::STAIR_DOWN: + return RemoteFortressReader::STAIR_DOWN; + break; + case df::enums::tiletype_shape::STAIR_UPDOWN: + return RemoteFortressReader::STAIR_UPDOWN; + break; + case df::enums::tiletype_shape::RAMP: + return RemoteFortressReader::RAMP; + break; + case df::enums::tiletype_shape::RAMP_TOP: + return RemoteFortressReader::RAMP_TOP; + break; + case df::enums::tiletype_shape::BROOK_BED: + return RemoteFortressReader::BROOK_BED; + break; + case df::enums::tiletype_shape::BROOK_TOP: + return RemoteFortressReader::BROOK_TOP; + break; +#if DF_VERSION_INT > 40001 + case df::enums::tiletype_shape::BRANCH: + return RemoteFortressReader::BRANCH; + break; +#endif +#if DF_VERSION_INT < 40001 + case df::enums::tiletype_shape::TREE: + return RemoteFortressReader::TREE_SHAPE; + break; +#endif +#if DF_VERSION_INT > 40001 + + case df::enums::tiletype_shape::TRUNK_BRANCH: + return RemoteFortressReader::TRUNK_BRANCH; + break; + case df::enums::tiletype_shape::TWIG: + return RemoteFortressReader::TWIG; + break; +#endif + case df::enums::tiletype_shape::SAPLING: + return RemoteFortressReader::SAPLING; + break; + case df::enums::tiletype_shape::SHRUB: + return RemoteFortressReader::SHRUB; + break; + case df::enums::tiletype_shape::ENDLESS_PIT: + return RemoteFortressReader::EMPTY; + break; + default: + return RemoteFortressReader::NO_SHAPE; + break; + } + return RemoteFortressReader::NO_SHAPE; +} + +RemoteFortressReader::TiletypeVariant TranslateVariant(df::tiletype_variant variant) +{ + switch (variant) + { + case df::enums::tiletype_variant::NONE: + return RemoteFortressReader::NO_VARIANT; + break; + case df::enums::tiletype_variant::VAR_1: + return RemoteFortressReader::VAR_1; + break; + case df::enums::tiletype_variant::VAR_2: + return RemoteFortressReader::VAR_2; + break; + case df::enums::tiletype_variant::VAR_3: + return RemoteFortressReader::VAR_3; + break; + case df::enums::tiletype_variant::VAR_4: + return RemoteFortressReader::VAR_4; + break; + default: + return RemoteFortressReader::NO_VARIANT; + break; + } + return RemoteFortressReader::NO_VARIANT; +} + +static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) +{ + clock_t start = clock(); + for (size_t i = 0; i < world->map.map_blocks.size(); i++) + { + df::map_block * block = world->map.map_blocks[i]; + fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); + } + clock_t end = clock(); + double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; + stream.print("Checking all hashes took %f seconds.", elapsed_secs); + return CR_OK; +} + +void CopyMat(RemoteFortressReader::MatPair * mat, int type, int index) +{ + if (type >= MaterialInfo::FIGURE_BASE && type < MaterialInfo::PLANT_BASE) + { + df::historical_figure * figure = df::historical_figure::find(index); + if (figure) + { + type -= MaterialInfo::GROUP_SIZE; + index = figure->race; + } + } + mat->set_mat_type(type); + mat->set_mat_index(index); + +} + +map hashes; + +bool IsTiletypeChanged(DFCoord pos) +{ + uint16_t hash; + df::map_block * block = Maps::getBlock(pos); + if (block) + hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); + else + hash = 0; + if (hashes[pos] != hash) + { + hashes[pos] = hash; + return true; + } + return false; +} + +map waterHashes; + +bool IsDesignationChanged(DFCoord pos) +{ + uint16_t hash; + df::map_block * block = Maps::getBlock(pos); + if (block) + hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); + else + hash = 0; + if (waterHashes[pos] != hash) + { + waterHashes[pos] = hash; + return true; + } + return false; +} + +map buildingHashes; + +bool IsBuildingChanged(DFCoord pos) +{ + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + auto bld = block->occupancy[x][y].bits.building; + if (buildingHashes[pos] != bld) + { + buildingHashes[pos] = bld; + changed = true; + } + } + return changed; +} + +map spatterHashes; + +bool IsspatterChanged(DFCoord pos) +{ + df::map_block * block = Maps::getBlock(pos); + bool changed = false; + std::vector materials; +#if DF_VERSION_INT > 34011 + std::vector items; + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL, NULL, &items)) + return false; +#else + if (!Maps::SortBlockEvents(block, NULL, NULL, &materials, NULL, NULL)) + return false; +#endif + + uint16_t hash = 0; + + for (size_t i = 0; i < materials.size(); i++) + { + auto mat = materials[i]; + hash ^= fletcher16((uint8_t*)mat, sizeof(df::block_square_event_material_spatterst)); + } +#if DF_VERSION_INT > 34011 + for (size_t i = 0; i < items.size(); i++) + { + auto item = items[i]; + hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst)); + } +#endif + if (spatterHashes[pos] != hash) + { + spatterHashes[pos] = hash; + return true; + } + return false; +} + +map itemHashes; + +bool isItemChanged(int i) +{ + uint16_t hash = 0; + auto item = df::item::find(i); + if (item) + { + hash = fletcher16((uint8_t*)item, sizeof(df::item)); + } + if (itemHashes[i] != hash) + { + itemHashes[i] = hash; + return true; + } + return false; +} + +bool areItemsChanged(vector * items) +{ + bool result = false; + for (size_t i = 0; i < items->size(); i++) + { + if (isItemChanged(items->at(i))) + result = true; + } + return result; +} + +map engravingHashes; + +bool isEngravingNew(int index) +{ + if (engravingHashes[index]) + return false; + engravingHashes[index] = true; + return true; +} + +void engravingIsNotNew(int index) +{ + engravingHashes[index] = false; +} + +static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in) +{ + hashes.clear(); + waterHashes.clear(); + buildingHashes.clear(); + spatterHashes.clear(); + itemHashes.clear(); + engravingHashes.clear(); + return CR_OK; +} + +df::matter_state GetState(df::material * mat, uint16_t temp = 10015) +{ + df::matter_state state = matter_state::Solid; + if (temp >= mat->heat.melting_point) + state = df::matter_state::Liquid; + if (temp >= mat->heat.boiling_point) + state = matter_state::Gas; + return state; +} + +static command_result GetMaterialList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) +{ + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + + + + df::world_raws *raws = &world->raws; + df::world_history *history = &world->history; + MaterialInfo mat; + for (size_t i = 0; i < raws->inorganics.size(); i++) + { + mat.decode(0, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(0); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (size_t(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]) < raws->descriptors.colors.size()) + { + ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); + } + } + for (int i = 0; i < 19; i++) + { + int k = -1; + if (i == 7) + k = 1;// for coal. + for (int j = -1; j <= k; j++) + { + mat.decode(i, j); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(i); + mat_def->mutable_mat_pair()->set_mat_index(j); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (size_t(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]) < raws->descriptors.colors.size()) + { + ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); + } + } + } + for (size_t i = 0; i < raws->creatures.all.size(); i++) + { + df::creature_raw * creature = raws->creatures.all[i]; + for (size_t j = 0; j < creature->material.size(); j++) + { + mat.decode(j + MaterialInfo::CREATURE_BASE, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + 19); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (size_t(creature->material[j]->state_color[GetState(creature->material[j])]) < raws->descriptors.colors.size()) + { + ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); + } + } + } + //for (size_t i = 0; i < history->figures.size(); i++) + //{ + // df::historical_figure * figure = history->figures[i]; + // if (figure->race < 0) + // continue; + // df::creature_raw * creature = raws->creatures.all[figure->race]; + // for (int j = 0; j < creature->material.size(); j++) + // { + // mat.decode(j + MaterialInfo::FIGURE_BASE, i); + // MaterialDefinition *mat_def = out->add_material_list(); + // mat_def->mutable_mat_pair()->set_mat_type(j + MaterialInfo::FIGURE_BASE); + // mat_def->mutable_mat_pair()->set_mat_index(i); + // stringstream id; + // id << "HF" << i << mat.getToken(); + // mat_def->set_id(id.str()); + // mat_def->set_name(mat.toString()); //find the name at cave temperature; + // if (creature->material[j]->state_color[GetState(creature->material[j])] < raws->descriptors.colors.size()) + // { + // df::descriptor_color *color = raws->descriptors.colors[creature->material[j]->state_color[GetState(creature->material[j])]]; + // mat_def->mutable_state_color()->set_red(color->red * 255); + // mat_def->mutable_state_color()->set_green(color->green * 255); + // mat_def->mutable_state_color()->set_blue(color->blue * 255); + // } + // } + //} + for (size_t i = 0; i < raws->plants.all.size(); i++) + { + df::plant_raw * plant = raws->plants.all[i]; + for (size_t j = 0; j < plant->material.size(); j++) + { + mat.decode(j + 419, i); + MaterialDefinition *mat_def = out->add_material_list(); + mat_def->mutable_mat_pair()->set_mat_type(j + 419); + mat_def->mutable_mat_pair()->set_mat_index(i); + mat_def->set_id(mat.getToken()); + mat_def->set_name(mat.toString()); //find the name at cave temperature; + if (size_t(plant->material[j]->state_color[GetState(plant->material[j])]) < raws->descriptors.colors.size()) + { + ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); + } + } + } + return CR_OK; +} + +static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out) +{ + if (!Core::getInstance().isWorldLoaded()) { + //out->set_available(false); + return CR_OK; + } + + + + df::world_raws *raws = &world->raws; + if (!raws) + return CR_OK;//'. + + + for (size_t i = 0; i < raws->plants.all.size(); i++) + { + df::plant_raw * pp = raws->plants.all[i]; + if (!pp) + continue; + MaterialDefinition * basePlant = out->add_material_list(); + basePlant->set_id(pp->id + ":BASE"); + basePlant->set_name(pp->name); + basePlant->mutable_mat_pair()->set_mat_type(-1); + basePlant->mutable_mat_pair()->set_mat_index(i); +#if DF_VERSION_INT > 40001 + for (size_t g = 0; g < pp->growths.size(); g++) + { + df::plant_growth* growth = pp->growths[g]; + if (!growth) + continue; + for (int l = 0; l < GROWTH_LOCATIONS_SIZE; l++) + { + MaterialDefinition * out_growth = out->add_material_list(); + out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); + out_growth->set_name(growth->name); + out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); + out_growth->mutable_mat_pair()->set_mat_index(i); + } + } +#endif + } + return CR_OK; +} + +void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + + MapExtras::Block * block = MC->BlockAtTile(DfBlock->map_pos); + + int trunk_percent[16][16]; + int tree_x[16][16]; + int tree_y[16][16]; + int tree_z[16][16]; + for (int xx = 0; xx < 16; xx++) + for (int yy = 0; yy < 16; yy++) + { + trunk_percent[xx][yy] = 255; + tree_x[xx][yy] = -3000; + tree_y[xx][yy] = -3000; + tree_z[xx][yy] = -3000; + } + +#if DF_VERSION_INT > 34011 + df::map_block_column * column = df::global::world->map.column_index[(DfBlock->map_pos.x / 48) * 3][(DfBlock->map_pos.y / 48) * 3]; + for (size_t i = 0; i < column->plants.size(); i++) + { + df::plant* plant = column->plants[i]; + if (plant->tree_info == NULL) + continue; + df::plant_tree_info * tree_info = plant->tree_info; + if ( + plant->pos.z - tree_info->roots_depth > DfBlock->map_pos.z + || (plant->pos.z + tree_info->body_height) <= DfBlock->map_pos.z + || (plant->pos.x - tree_info->dim_x / 2) > (DfBlock->map_pos.x + 16) + || (plant->pos.x + tree_info->dim_x / 2) < (DfBlock->map_pos.x) + || (plant->pos.y - tree_info->dim_y / 2) > (DfBlock->map_pos.y + 16) + || (plant->pos.y + tree_info->dim_y / 2) < (DfBlock->map_pos.y) + ) + continue; + DFCoord localPos = plant->pos - DfBlock->map_pos; + for (int xx = 0; xx < tree_info->dim_x; xx++) + for (int yy = 0; yy < tree_info->dim_y; yy++) + { + int xxx = localPos.x - (tree_info->dim_x / 2) + xx; + int yyy = localPos.y - (tree_info->dim_y / 2) + yy; + if (xxx < 0 + || yyy < 0 + || xxx >= 16 + || yyy >= 16 + ) + continue; + df::plant_tree_tile tile; + if (-localPos.z < 0) + { + tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; + } + else + { + tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; + } + if (!tile.whole || tile.bits.blocked) + continue; + if (tree_info->body_height <= 1) + trunk_percent[xxx][yyy] = 0; + else + trunk_percent[xxx][yyy] = -localPos.z * 100 / (tree_info->body_height - 1); + tree_x[xxx][yyy] = xx - tree_info->dim_x / 2; + tree_y[xxx][yyy] = yy - tree_info->dim_y / 2; + tree_z[xxx][yyy] = localPos.z; + } + } +#endif + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + df::tiletype tile = DfBlock->tiletype[xx][yy]; + NetBlock->add_tiles(tile); + df::coord2d p = df::coord2d(xx, yy); + t_matpair baseMat = block->baseMaterialAt(p); + t_matpair staticMat = block->staticMaterialAt(p); + switch (tileMaterial(tile)) + { + case tiletype_material::FROZEN_LIQUID: + staticMat.mat_type = builtin_mats::WATER; + staticMat.mat_index = -1; + break; + default: + break; + } + CopyMat(NetBlock->add_materials(), staticMat.mat_type, staticMat.mat_index); + CopyMat(NetBlock->add_layer_materials(), 0, block->layerMaterialAt(p)); + CopyMat(NetBlock->add_vein_materials(), 0, block->veinMaterialAt(p)); + CopyMat(NetBlock->add_base_materials(), baseMat.mat_type, baseMat.mat_index); + RemoteFortressReader::MatPair * constructionItem = NetBlock->add_construction_items(); + CopyMat(constructionItem, -1, -1); + if (tileMaterial(tile) == tiletype_material::CONSTRUCTION) + { + df::construction *con = df::construction::find(DfBlock->map_pos + df::coord(xx, yy, 0)); + if (con) + { + CopyMat(constructionItem, con->item_type, con->item_subtype); + } + } + NetBlock->add_tree_percent(trunk_percent[xx][yy]); + NetBlock->add_tree_x(tree_x[xx][yy]); + NetBlock->add_tree_y(tree_y[xx][yy]); + NetBlock->add_tree_z(tree_z[xx][yy]); + } +} + +void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + df::tile_designation designation = DfBlock->designation[xx][yy]; + df::tile_occupancy occupancy = DfBlock->occupancy[xx][yy]; + int lava = 0; + int water = 0; + if (designation.bits.liquid_type == df::enums::tile_liquid::Magma) + lava = designation.bits.flow_size; + else + water = designation.bits.flow_size; + NetBlock->add_magma(lava); + NetBlock->add_water(water); + NetBlock->add_aquifer(designation.bits.water_table); + NetBlock->add_light(designation.bits.light); + NetBlock->add_outside(designation.bits.outside); + NetBlock->add_subterranean(designation.bits.subterranean); + NetBlock->add_water_salt(designation.bits.water_salt); + NetBlock->add_water_stagnant(designation.bits.water_stagnant); + if (gamemode && (*gamemode == game_mode::ADVENTURE)) + { + auto fog_of_war = DfBlock->fog_of_war[xx][yy]; + NetBlock->add_hidden(designation.bits.dig == TileDigDesignation::NO_DIG || designation.bits.hidden); + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + NetBlock->add_tile_dig_designation_marker(false); + NetBlock->add_tile_dig_designation_auto(false); + } + else + { + NetBlock->add_hidden(designation.bits.hidden); +#if DF_VERSION_INT > 34011 + NetBlock->add_tile_dig_designation_marker(occupancy.bits.dig_marked); + NetBlock->add_tile_dig_designation_auto(occupancy.bits.dig_auto); +#endif + switch (designation.bits.dig) + { + case df::enums::tile_dig_designation::No: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + case df::enums::tile_dig_designation::Default: + NetBlock->add_tile_dig_designation(TileDigDesignation::DEFAULT_DIG); + break; + case df::enums::tile_dig_designation::UpDownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::Channel: + NetBlock->add_tile_dig_designation(TileDigDesignation::CHANNEL_DIG); + break; + case df::enums::tile_dig_designation::Ramp: + NetBlock->add_tile_dig_designation(TileDigDesignation::RAMP_DIG); + break; + case df::enums::tile_dig_designation::DownStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::DOWN_STAIR_DIG); + break; + case df::enums::tile_dig_designation::UpStair: + NetBlock->add_tile_dig_designation(TileDigDesignation::UP_STAIR_DIG); + break; + default: + NetBlock->add_tile_dig_designation(TileDigDesignation::NO_DIG); + break; + } + } + } +#if DF_VERSION_INT > 34011 + for (size_t i = 0; i < world->jobs.postings.size(); i++) + { + auto job = world->jobs.postings[i]->job; + if (job == nullptr) + continue; + if ( + job->pos.z > DfBlock->map_pos.z + || job->pos.z < DfBlock->map_pos.z + || job->pos.x >= (DfBlock->map_pos.x + 16) + || job->pos.x < (DfBlock->map_pos.x) + || job->pos.y >= (DfBlock->map_pos.y + 16) + || job->pos.y < (DfBlock->map_pos.y) + ) + continue; + + int index = (job->pos.x - DfBlock->map_pos.x) + (16 * (job->pos.y - DfBlock->map_pos.y)); + + switch (job->job_type) + { + case job_type::Dig: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::CarveUpwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_STAIR_DIG); + break; + case job_type::CarveDownwardStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DOWN_STAIR_DIG); + break; + case job_type::CarveUpDownStaircase: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::UP_DOWN_STAIR_DIG); + break; + case job_type::CarveRamp: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::RAMP_DIG); + break; + case job_type::DigChannel: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::CHANNEL_DIG); + break; + case job_type::FellTree: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + case job_type::GatherPlants: + NetBlock->set_tile_dig_designation(index, TileDigDesignation::DEFAULT_DIG); + break; + default: + break; + } + } +#endif +} + +void CopyProjectiles(RemoteFortressReader::MapBlock * NetBlock) +{ + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(projectile, df::proj_itemst, proj->item); + if (projectile == NULL) + continue; + auto NetItem = NetBlock->add_items(); + CopyItem(NetItem, projectile->item); + NetItem->set_projectile(true); + if (projectile->flags.bits.parabolic) + { + NetItem->set_subpos_x(projectile->pos_x / 100000.0); + NetItem->set_subpos_y(projectile->pos_y / 100000.0); + NetItem->set_subpos_z(projectile->pos_z / 140000.0); + NetItem->set_velocity_x(projectile->speed_x / 100000.0); + NetItem->set_velocity_y(projectile->speed_y / 100000.0); + NetItem->set_velocity_z(projectile->speed_z / 140000.0); + } + else + { + DFCoord diff = projectile->target_pos - projectile->origin_pos; + float max_dist = max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); + NetItem->set_subpos_x(projectile->origin_pos.x + (diff.x / max_dist * projectile->distance_flown) - projectile->cur_pos.x); + NetItem->set_subpos_y(projectile->origin_pos.y + (diff.y / max_dist * projectile->distance_flown) - projectile->cur_pos.y); + NetItem->set_subpos_z(projectile->origin_pos.z + (diff.z / max_dist * projectile->distance_flown) - projectile->cur_pos.z); + NetItem->set_velocity_x(diff.x / max_dist); + NetItem->set_velocity_y(diff.y / max_dist); + NetItem->set_velocity_z(diff.z / max_dist); + } + } + for (size_t i = 0; i < world->vehicles.active.size(); i++) + { + bool isProj = false; + auto vehicle = world->vehicles.active[i]; + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(projectile, df::proj_itemst, proj->item); + if (!projectile) + continue; + if (projectile->item->id == vehicle->item_id) + { + isProj = true; + break; + } + } + if (isProj) + continue; + + auto item = Items::findItemByID(vehicle->item_id); + if (!item) + continue; + auto NetItem = NetBlock->add_items(); + CopyItem(NetItem, item); + NetItem->set_subpos_x(vehicle->offset_x / 100000.0); + NetItem->set_subpos_y(vehicle->offset_y / 100000.0); + NetItem->set_subpos_z(vehicle->offset_z / 140000.0); + NetItem->set_velocity_x(vehicle->speed_x / 100000.0); + NetItem->set_velocity_y(vehicle->speed_y / 100000.0); + NetItem->set_velocity_z(vehicle->speed_z / 140000.0); + + } +} + +void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC) +{ + + for (size_t i = 0; i < df::global::world->buildings.all.size(); i++) + { + df::building * bld = df::global::world->buildings.all[i]; + if (bld->x1 >= max.x || bld->y1 >= max.y || bld->x2 < min.x || bld->y2 < min.y) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); + continue; + } + + int z2 = bld->z; + + if (bld->getType() == building_type::Well) + { + df::building_wellst * well_building = virtual_cast(bld); + if (well_building) + { + z2 = well_building->bucket_z; + } + } + if (bld->z < min.z || z2 >= max.z) + { + auto out_bld = NetBlock->add_buildings(); + out_bld->set_index(bld->id); + continue; + } + auto out_bld = NetBlock->add_buildings(); + CopyBuilding(i, out_bld); + df::building_actual* actualBuilding = virtual_cast(bld); + if (actualBuilding) + { + for (size_t i = 0; i < actualBuilding->contained_items.size(); i++) + { + auto buildingItem = out_bld->add_items(); + buildingItem->set_mode(actualBuilding->contained_items[i]->use_mode); + CopyItem(buildingItem->mutable_item(), actualBuilding->contained_items[i]->item); + } + } + } +} + +void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + std::vector materials; +#if DF_VERSION_INT > 34011 + std::vector items; + std::vector grasses; + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, &grasses, NULL, NULL, &items)) + return; +#else + if (!Maps::SortBlockEvents(DfBlock, NULL, NULL, &materials, NULL, NULL)) + return; +#endif + for (int yy = 0; yy < 16; yy++) + for (int xx = 0; xx < 16; xx++) + { + auto send_pile = NetBlock->add_spatterpile(); + for (size_t i = 0; i < materials.size(); i++) + { + auto mat = materials[i]; + if (mat->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + send_spat->set_state((MatterState)mat->mat_state); + CopyMat(send_spat->mutable_material(), mat->mat_type, mat->mat_index); + send_spat->set_amount(mat->amount[xx][yy]); + } +#if DF_VERSION_INT > 34011 + for (size_t i = 0; i < items.size(); i++) + { + auto item = items[i]; + if (item->amount[xx][yy] == 0) + continue; + auto send_spat = send_pile->add_spatters(); + CopyMat(send_spat->mutable_material(), item->mattype, item->matindex); + send_spat->set_amount(item->amount[xx][yy]); + auto send_item = send_spat->mutable_item(); + send_item->set_mat_type(item->item_type); + send_item->set_mat_index(item->item_subtype); + } + int grassPercent = 0; + for (size_t i = 0; i < grasses.size(); i++) + { + auto grass = grasses[i]; + if (grass->amount[xx][yy] > grassPercent) + grassPercent = grass->amount[xx][yy]; + } + NetBlock->add_grass_percent(grassPercent); +#endif + } +} + +void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + for (size_t i = 0; i < DfBlock->items.size(); i++) + { + int id = DfBlock->items[i]; + + auto item = df::item::find(id); + if (item) + CopyItem(NetBlock->add_items(), item); + } +} + +void CopyFlow(df::flow_info * localFlow, RemoteFortressReader::FlowInfo * netFlow, int index) +{ + netFlow->set_type((FlowType)localFlow->type); + netFlow->set_density(localFlow->density); + ConvertDFCoord(localFlow->pos, netFlow->mutable_pos()); + ConvertDFCoord(localFlow->dest, netFlow->mutable_dest()); + netFlow->set_expanding(localFlow->expanding); + netFlow->set_reuse(localFlow->reuse); + netFlow->set_guide_id(localFlow->guide_id); + auto mat = netFlow->mutable_material(); + mat->set_mat_index(localFlow->mat_index); + mat->set_mat_type(localFlow->mat_type); + if (localFlow->guide_id >= 0 && localFlow->type == flow_type::ItemCloud) + { + auto guide = df::flow_guide::find(localFlow->guide_id); + if (guide) + { + VIRTUAL_CAST_VAR(cloud, df::flow_guide_item_cloudst, guide); + if (cloud) + { + mat->set_mat_index(cloud->matindex); + mat->set_mat_type(cloud->mattype); + auto item = netFlow->mutable_item(); + item->set_mat_index(cloud->item_subtype); + item->set_mat_type(cloud->item_type); + } + } + } +} + +void CopyFlows(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock) +{ + NetBlock->set_map_x(DfBlock->map_pos.x); + NetBlock->set_map_y(DfBlock->map_pos.y); + NetBlock->set_map_z(DfBlock->map_pos.z); + for (size_t i = 0; i < DfBlock->flows.size(); i++) + { + CopyFlow(DfBlock->flows[i], NetBlock->add_flows(), i); + } +} + +static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out) +{ + int x, y, z; + DFHack::Maps::getPosition(x, y, z); + out->set_map_x(x); + out->set_map_y(y); + MapExtras::MapCache MC; + int center_x = (in->min_x() + in->max_x()) / 2; + int center_y = (in->min_y() + in->max_y()) / 2; + + int NUMBER_OF_POINTS = ((in->max_x() - center_x + 1) * 2) * ((in->max_y() - center_y + 1) * 2); + int blocks_needed; + if (in->has_blocks_needed()) + blocks_needed = in->blocks_needed(); + else + blocks_needed = NUMBER_OF_POINTS * (in->max_z() - in->min_z()); + int blocks_sent = 0; + int min_x = in->min_x(); + int min_y = in->min_y(); + int max_x = in->max_x(); + int max_y = in->max_y(); + int min_z = in->min_z(); + int max_z = in->max_z(); + bool firstBlock = true; //Always send all the buildings needed on the first block, and none on the rest. + //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); + for (int zz = max_z - 1; zz >= min_z; zz--) + { + // (di, dj) is a vector - direction in which we move right now + int di = 1; + int dj = 0; + // length of current segment + int segment_length = 1; + // current position (i, j) and how much of current segment we passed + int i = center_x; + int j = center_y; + int segment_passed = 0; + for (int k = 0; k < NUMBER_OF_POINTS; ++k) + { + if (blocks_sent >= blocks_needed) + break; + if (!(i < min_x || i >= max_x || j < min_y || j >= max_y)) + { + DFCoord pos = DFCoord(i, j, zz); + df::map_block * block = DFHack::Maps::getBlock(pos); + if (block != NULL) + { + bool nonAir = false; + for (int xxx = 0; xxx < 16; xxx++) + for (int yyy = 0; yyy < 16; yyy++) + { + if ((DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::None && + DFHack::tileShapeBasic(DFHack::tileShape(block->tiletype[xxx][yyy])) != df::tiletype_shape_basic::Open) + || block->designation[xxx][yyy].bits.flow_size > 0 + || block->occupancy[xxx][yyy].bits.building > 0) + { + nonAir = true; + goto ItsAir; + } + } + ItsAir: + if (block->flows.size() > 0) + nonAir = true; + if (nonAir || firstBlock) + { + bool tileChanged = IsTiletypeChanged(pos); + bool desChanged = IsDesignationChanged(pos); + bool spatterChanged = IsspatterChanged(pos); + bool itemsChanged = block->items.size() > 0; + bool flows = block->flows.size() > 0; + RemoteFortressReader::MapBlock *net_block = nullptr; + if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged || flows) + net_block = out->add_map_blocks(); + if (tileChanged) + { + CopyBlock(block, net_block, &MC, pos); + blocks_sent++; + } + if (desChanged) + CopyDesignation(block, net_block, &MC, pos); + if (firstBlock) + { + CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC); + CopyProjectiles(net_block); + firstBlock = false; + } + if (spatterChanged) + Copyspatters(block, net_block, &MC, pos); + if (itemsChanged) + CopyItems(block, net_block, &MC, pos); + if (flows) + { + CopyFlows(block, net_block); + } + } + } + } + + // make a step, add 'direction' vector (di, dj) to current position (i, j) + i += di; + j += dj; + ++segment_passed; + //System.out.println(i + " " + j); + + if (segment_passed == segment_length) + { + // done with current segment + segment_passed = 0; + + // 'rotate' directions + int buffer = di; + di = -dj; + dj = buffer; + + // increase segment length if necessary + if (dj == 0) { + ++segment_length; + } + } + } + } + + for (size_t i = 0; i < world->engravings.size(); i++) + { + auto engraving = world->engravings[i]; + if (engraving->pos.x < (min_x * 16) || engraving->pos.x >(max_x * 16)) + continue; + if (engraving->pos.y < (min_y * 16) || engraving->pos.y >(max_y * 16)) + continue; + if (engraving->pos.z < min_z || engraving->pos.z > max_z) + continue; + if (!isEngravingNew(i)) + continue; + + df::art_image_chunk * chunk = NULL; + GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast(Core::getInstance().vinfo->getAddress("get_art_image_chunk")); + if (GetArtImageChunk) + { + chunk = GetArtImageChunk(&(world->art_image_chunks), engraving->art_id); + } + else + { + for (size_t i = 0; i < world->art_image_chunks.size(); i++) + { + if (world->art_image_chunks[i]->id == engraving->art_id) + chunk = world->art_image_chunks[i]; + } + } + if (!chunk) + { + engravingIsNotNew(i); + continue; + } + auto netEngraving = out->add_engravings(); + ConvertDFCoord(engraving->pos, netEngraving->mutable_pos()); + netEngraving->set_quality(engraving->quality); + netEngraving->set_tile(engraving->tile); + CopyImage(chunk->images[engraving->art_subid], netEngraving->mutable_image()); + netEngraving->set_floor(engraving->flags.bits.floor); + netEngraving->set_west(engraving->flags.bits.west); + netEngraving->set_east(engraving->flags.bits.east); + netEngraving->set_north(engraving->flags.bits.north); + netEngraving->set_south(engraving->flags.bits.south); + netEngraving->set_hidden(engraving->flags.bits.hidden); + netEngraving->set_northwest(engraving->flags.bits.northwest); + netEngraving->set_northeast(engraving->flags.bits.northeast); + netEngraving->set_southwest(engraving->flags.bits.southwest); + netEngraving->set_southeast(engraving->flags.bits.southeast); + } + for (size_t i = 0; i < world->ocean_waves.size(); i++) + { + auto wave = world->ocean_waves[i]; + auto netWave = out->add_ocean_waves(); + ConvertDFCoord(wave->x1, wave->y1, wave->z, netWave->mutable_dest()); + ConvertDFCoord(wave->x2, wave->y2, wave->z, netWave->mutable_pos()); + } + MC.trash(); + return CR_OK; +} + +static command_result GetTiletypeList(color_ostream &stream, const EmptyMessage *in, TiletypeList *out) +{ + int count = 0; + FOR_ENUM_ITEMS(tiletype, tt) + { + Tiletype * type = out->add_tiletype_list(); + type->set_id(tt); + type->set_name(ENUM_KEY_STR(tiletype, tt)); + const char * name = tileName(tt); + if (name != NULL && name[0] != 0) + type->set_caption(name); + type->set_shape(TranslateShape(tileShape(tt))); + type->set_special(TranslateSpecial(tileSpecial(tt))); + type->set_material(TranslateMaterial(tileMaterial(tt))); + type->set_variant(TranslateVariant(tileVariant(tt))); + type->set_direction(tileDirection(tt).getStr()); + count++; + } + return CR_OK; +} + +static command_result GetPlantList(color_ostream &stream, const BlockRequest *in, PlantList *out) +{ + int min_x = in->min_x() / 3; + int min_y = in->min_y() / 3; + int min_z = in->min_z(); + int max_x = in->max_x() / 3; + int max_y = in->max_y() / 3; + int max_z = in->max_z(); + +#if DF_VERSION_INT < 40001 + //plants are gotten differently here +#else + for (int xx = min_x; xx < max_x; xx++) + for (int yy = min_y; yy < max_y; yy++) + { + if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) + continue; + df::map_block_column * column = world->map.column_index[xx][yy]; + for (size_t i = 0; i < column->plants.size(); i++) + { + df::plant * plant = column->plants[i]; + if (!plant->tree_info) + { + if (plant->pos.z < min_z || plant->pos.z >= max_z) + continue; + if (plant->pos.x < in->min_x() * 16 || plant->pos.x >= in->max_x() * 16) + continue; + if (plant->pos.y < in->min_y() * 16 || plant->pos.y >= in->max_y() * 16) + continue; + } + else + { + if (plant->pos.z - plant->tree_info->roots_depth < min_z || plant->pos.z + plant->tree_info->body_height > max_z) + continue; + if (plant->pos.x - plant->tree_info->dim_x / 2 < in->min_x() * 16 || plant->pos.x + plant->tree_info->dim_x / 2 >= in->max_x() * 16) + continue; + if (plant->pos.y - plant->tree_info->dim_y / 2 < in->min_y() * 16 || plant->pos.y + plant->tree_info->dim_y / 2 >= in->max_y() * 16) + continue; + } + RemoteFortressReader::PlantDef * out_plant = out->add_plant_list(); + out_plant->set_index(plant->material); + out_plant->set_pos_x(plant->pos.x); + out_plant->set_pos_y(plant->pos.y); + out_plant->set_pos_z(plant->pos.z); + } + } +#endif + return CR_OK; +} + +static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, UnitList *out) +{ + return GetUnitListInside(stream, NULL, out); +} + +static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) +{ + auto world = df::global::world; + for (size_t i = 0; i < world->units.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + auto send_unit = out->add_creature_list(); + send_unit->set_id(unit->id); + send_unit->set_pos_x(unit->pos.x); + send_unit->set_pos_y(unit->pos.y); + send_unit->set_pos_z(unit->pos.z); + send_unit->mutable_race()->set_mat_type(unit->race); + send_unit->mutable_race()->set_mat_index(unit->caste); + if (in != NULL) + { + if (unit->pos.z < in->min_z() || unit->pos.z >= in->max_z()) + continue; + if (unit->pos.x < in->min_x() * 16 || unit->pos.x >= in->max_x() * 16) + continue; + if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) + continue; + } + ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); + send_unit->set_flags1(unit->flags1.whole); + send_unit->set_flags2(unit->flags2.whole); + send_unit->set_flags3(unit->flags3.whole); + send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession)); + auto size_info = send_unit->mutable_size_info(); + size_info->set_size_cur(unit->body.size_info.size_cur); + size_info->set_size_base(unit->body.size_info.size_base); + size_info->set_area_cur(unit->body.size_info.area_cur); + size_info->set_area_base(unit->body.size_info.area_base); + size_info->set_length_cur(unit->body.size_info.length_cur); + size_info->set_length_base(unit->body.size_info.length_base); + if (unit->name.has_name) + { + send_unit->set_name(DF2UTF(Translation::TranslateName(Units::getVisibleName(unit)))); + } + + auto appearance = send_unit->mutable_appearance(); + for (size_t j = 0; j < unit->appearance.body_modifiers.size(); j++) + appearance->add_body_modifiers(unit->appearance.body_modifiers[j]); + for (size_t j = 0; j < unit->appearance.bp_modifiers.size(); j++) + appearance->add_bp_modifiers(unit->appearance.bp_modifiers[j]); + for (size_t j = 0; j < unit->appearance.colors.size(); j++) + appearance->add_colors(unit->appearance.colors[j]); + appearance->set_size_modifier(unit->appearance.size_modifier); + + send_unit->set_profession_id(unit->profession); + + std::vector pvec; + + if (Units::getNoblePositions(&pvec, unit)) + { + for (size_t j = 0; j < pvec.size(); j++) + { + auto noble_positon = pvec[j]; + send_unit->add_noble_positions(noble_positon.position->code); + } + } + + send_unit->set_rider_id(unit->relationship_ids[df::unit_relationship_type::RiderMount]); + + auto creatureRaw = world->raws.creatures.all[unit->race]; + auto casteRaw = creatureRaw->caste[unit->caste]; + + for (size_t j = 0; j < unit->appearance.tissue_style_type.size(); j++) + { + auto type = unit->appearance.tissue_style_type[j]; + if (type < 0) + continue; + int style_raw_index = binsearch_index(casteRaw->tissue_styles, &df::tissue_style_raw::id, type); + auto styleRaw = casteRaw->tissue_styles[style_raw_index]; + if (styleRaw->token == "HAIR") + { + auto send_style = appearance->mutable_hair(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "BEARD") + { + auto send_style = appearance->mutable_beard(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "MOUSTACHE") + { + auto send_style = appearance->mutable_moustache(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + else if (styleRaw->token == "SIDEBURNS") + { + auto send_style = appearance->mutable_sideburns(); + send_style->set_length(unit->appearance.tissue_length[j]); + send_style->set_style((HairStyle)unit->appearance.tissue_style[j]); + } + } + + for (size_t j = 0; j < unit->inventory.size(); j++) + { + auto inventory_item = unit->inventory[j]; + auto sent_item = send_unit->add_inventory(); + sent_item->set_mode((InventoryMode)inventory_item->mode); + CopyItem(sent_item->mutable_item(), inventory_item->item); + } + + if (unit->flags1.bits.projectile) + { + for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next) + { + STRICT_VIRTUAL_CAST_VAR(item, df::proj_unitst, proj->item); + if (item == NULL) + continue; + if (item->unit != unit) + continue; + send_unit->set_subpos_x(item->pos_x / 100000.0); + send_unit->set_subpos_y(item->pos_y / 100000.0); + send_unit->set_subpos_z(item->pos_z / 140000.0); + } + } + } + return CR_OK; +} + +static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out) +{ + int x, y, z, w, h, cx, cy, cz; + Gui::getWindowSize(w, h); + Gui::getViewCoords(x, y, z); + Gui::getCursorCoords(cx, cy, cz); + +#if DF_VERSION_INT > 34011 + auto embark = Gui::getViewscreenByType(0); + if (embark) + { + df::embark_location location = embark->location; + df::world_data * data = df::global::world->world_data; + if (data && data->region_map) + { + z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; + } + } +#endif + + out->set_view_pos_x(x); + out->set_view_pos_y(y); + out->set_view_pos_z(z); + out->set_view_size_x(w); + out->set_view_size_y(h); + out->set_cursor_pos_x(cx); + out->set_cursor_pos_y(cy); + out->set_cursor_pos_z(cz); + + if (gamemode && *gamemode == GameMode::ADVENTURE) + out->set_follow_unit_id(world->units.active[0]->id); + else + out->set_follow_unit_id(ui->follow_unit); + out->set_follow_item_id(ui->follow_item); + return CR_OK; +} + +static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) +{ + if (!Maps::IsValid()) + return CR_FAILURE; + uint32_t size_x, size_y, size_z; + int32_t pos_x, pos_y, pos_z; + Maps::getSize(size_x, size_y, size_z); + Maps::getPosition(pos_x, pos_y, pos_z); + out->set_block_size_x(size_x); + out->set_block_size_y(size_y); + out->set_block_size_z(size_z); + out->set_block_pos_x(pos_x); + out->set_block_pos_y(pos_y); + out->set_block_pos_z(pos_z); + out->set_world_name(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, false))); + out->set_world_name_english(DF2UTF(Translation::TranslateName(&df::global::world->world_data->name, true))); + out->set_save_name(df::global::world->cur_savegame.save_dir); + return CR_OK; +} + +DFCoord GetMapCenter() +{ + DFCoord output; +#if DF_VERSION_INT > 34011 + auto embark = Gui::getViewscreenByType(0); + if (embark) + { + df::embark_location location = embark->location; + output.x = (location.region_pos.x * 16) + 8; + output.y = (location.region_pos.y * 16) + 8; + output.z = 100; + df::world_data * data = df::global::world->world_data; + if (data && data->region_map) + { + output.z = data->region_map[location.region_pos.x][location.region_pos.y].elevation; + } + } + else +#endif + if (Maps::IsValid()) + { + int x, y, z; + Maps::getPosition(x, y, z); + output = DFCoord(x, y, z); + } +#if DF_VERSION_INT > 34011 + else + for (size_t i = 0; i < df::global::world->armies.all.size(); i++) + { + df::army * thisArmy = df::global::world->armies.all[i]; + if (thisArmy->flags.is_set(df::enums::army_flags::player)) + { + output.x = (thisArmy->pos.x / 3) - 1; + output.y = (thisArmy->pos.y / 3) - 1; + output.z = thisArmy->pos.z; + } + } +#endif + return output; +} + +static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out) +{ + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; +} + +static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out) +{ + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); + auto poles = data->flip_latitude; +#if DF_VERSION_INT > 34011 + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } +#else + out->set_world_poles(WorldPoles::NO_POLES); +#endif + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + out->add_elevation(map_entry->elevation); + out->add_rainfall(map_entry->rainfall); + out->add_vegetation(map_entry->vegetation); + out->add_temperature(map_entry->temperature); + out->add_evilness(map_entry->evilness); + out->add_drainage(map_entry->drainage); + out->add_volcanism(map_entry->volcanism); + out->add_savagery(map_entry->savagery); + out->add_salinity(map_entry->salinity); + auto clouds = out->add_clouds(); +#if DF_VERSION_INT > 34011 + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); +#else + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); +#endif + if (region->type == world_region_type::Lake) + { + out->add_water_elevation(region->lake_surface); + } + else + out->add_water_elevation(99); + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + + + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; +} + +static void SetRegionTile(RegionTile * out, df::region_map_entry * e1) +{ + df::world_region * region = df::world_region::find(e1->region_id); + df::world_geo_biome * geoBiome = df::world_geo_biome::find(e1->geo_index); + out->set_rainfall(e1->rainfall); + out->set_vegetation(e1->vegetation); + out->set_temperature(e1->temperature); + out->set_evilness(e1->evilness); + out->set_drainage(e1->drainage); + out->set_volcanism(e1->volcanism); + out->set_savagery(e1->savagery); + out->set_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->set_water_elevation(region->lake_surface); + else + out->set_water_elevation(99); + + int topLayer = 0; + for (size_t i = 0; i < geoBiome->layers.size(); i++) + { + auto layer = geoBiome->layers[i]; + if (layer->top_height == 0) + { + topLayer = layer->mat_index; + } + if (layer->type != geo_layer_type::SOIL + && layer->type != geo_layer_type::SOIL_OCEAN + && layer->type != geo_layer_type::SOIL_SAND) + { + auto mat = out->add_stone_materials(); + mat->set_mat_index(layer->mat_index); + mat->set_mat_type(0); + } + } + auto surfaceMat = out->mutable_surface_material(); + surfaceMat->set_mat_index(topLayer); + surfaceMat->set_mat_type(0); + + for (size_t i = 0; i < region->population.size(); i++) + { + auto pop = region->population[i]; + if (pop->type == world_population_type::Grass) + { + auto plantMat = out->add_plant_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + else if (pop->type == world_population_type::Tree) + { + auto plantMat = out->add_tree_materials(); + + plantMat->set_mat_index(pop->plant); + plantMat->set_mat_type(419); + } + } +#if DF_VERSION_INT >= 43005 + out->set_snow(e1->snowfall); +#endif +} + +static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out) +{ + if (!df::global::world->world_data) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + if (!data->region_map) + { + out->set_world_width(0); + out->set_world_height(0); + return CR_FAILURE; + } + int width = data->world_width; + int height = data->world_height; + out->set_world_width(width); + out->set_world_height(height); + out->set_name(Translation::TranslateName(&(data->name), false)); + out->set_name_english(Translation::TranslateName(&(data->name), true)); +#if DF_VERSION_INT > 34011 + auto poles = data->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } +#else + out->set_world_poles(WorldPoles::NO_POLES); +#endif + for (int yy = 0; yy < height; yy++) + for (int xx = 0; xx < width; xx++) + { + df::region_map_entry * map_entry = &data->region_map[xx][yy]; + df::world_region * region = data->regions[map_entry->region_id]; + + auto regionTile = out->add_region_tiles(); + regionTile->set_elevation(map_entry->elevation); + SetRegionTile(regionTile, map_entry); + auto clouds = out->add_clouds(); +#if DF_VERSION_INT > 34011 + clouds->set_cirrus(map_entry->clouds.bits.cirrus); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus); +#else + clouds->set_cirrus(map_entry->clouds.bits.striped); + clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.density); + clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog); + clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.darkness); +#endif + } + DFCoord pos = GetMapCenter(); + out->set_center_x(pos.x); + out->set_center_y(pos.y); + out->set_center_z(pos.z); + + + out->set_cur_year(World::ReadCurrentYear()); + out->set_cur_year_tick(World::ReadCurrentTick()); + return CR_OK; +} + +static void AddRegionTiles(WorldMap * out, df::region_map_entry * e1, df::world_data * worldData) +{ + df::world_region * region = worldData->regions[e1->region_id]; + out->add_rainfall(e1->rainfall); + out->add_vegetation(e1->vegetation); + out->add_temperature(e1->temperature); + out->add_evilness(e1->evilness); + out->add_drainage(e1->drainage); + out->add_volcanism(e1->volcanism); + out->add_savagery(e1->savagery); + out->add_salinity(e1->salinity); + if (region->type == world_region_type::Lake) + out->add_water_elevation(region->lake_surface); + else + out->add_water_elevation(99); +} + + +static void AddRegionTiles(WorldMap * out, df::coord2d pos, df::world_data * worldData) +{ + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + AddRegionTiles(out, &worldData->region_map[pos.x][pos.y], worldData); +} + +static void AddRegionTiles(RegionTile * out, df::coord2d pos, df::world_data * worldData) +{ + if (pos.x < 0) + pos.x = 0; + if (pos.y < 0) + pos.y = 0; + if (pos.x >= worldData->world_width) + pos.x = worldData->world_width - 1; + if (pos.y >= worldData->world_height) + pos.y = worldData->world_height - 1; + SetRegionTile(out, &worldData->region_map[pos.x][pos.y]); +} + +static df::coord2d ShiftCoords(df::coord2d source, int direction) +{ + switch (direction) + { + case 1: + return df::coord2d(source.x - 1, source.y + 1); + case 2: + return df::coord2d(source.x, source.y + 1); + case 3: + return df::coord2d(source.x + 1, source.y + 1); + case 4: + return df::coord2d(source.x - 1, source.y); + case 5: + return source; + case 6: + return df::coord2d(source.x + 1, source.y); + case 7: + return df::coord2d(source.x - 1, source.y - 1); + case 8: + return df::coord2d(source.x, source.y - 1); + case 9: + return df::coord2d(source.x + 1, source.y - 1); + default: + return source; + } +} + +static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, WorldMap * out) +{ + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + out->set_world_width(17); + out->set_world_height(17); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); +#if DF_VERSION_INT > 34011 + auto poles = worldData->flip_latitude; + switch (poles) + { + case df::world_data::None: + out->set_world_poles(WorldPoles::NO_POLES); + break; + case df::world_data::North: + out->set_world_poles(WorldPoles::NORTH_POLE); + break; + case df::world_data::South: + out->set_world_poles(WorldPoles::SOUTH_POLE); + break; + case df::world_data::Both: + out->set_world_poles(WorldPoles::BOTH_POLES); + break; + default: + break; + } +#else + out->set_world_poles(WorldPoles::NO_POLES); +#endif + + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (size_t i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + out->add_elevation(southEast->elevation[0][0]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + out->add_elevation(east->elevation[0][yy]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + out->add_elevation(south->elevation[xx][0]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + out->add_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(out, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + if (xx == 16 || yy == 16) + { + out->add_river_tiles(); + } + else + { + auto riverTile = out->add_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + } +} + +static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, RegionMap * out) +{ + int pos_x = worldRegionDetails->pos.x; + int pos_y = worldRegionDetails->pos.y; + out->set_map_x(pos_x); + out->set_map_y(pos_y); + char name[256]; + sprintf(name, "Region %d, %d", pos_x, pos_y); + out->set_name_english(name); + out->set_name(name); + + + df::world_region_details * south = NULL; + df::world_region_details * east = NULL; + df::world_region_details * southEast = NULL; + + for (size_t i = 0; i < worldData->region_details.size(); i++) + { + auto region = worldData->region_details[i]; + if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1) + southEast = region; + else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y) + east = region; + else if (region->pos.x == pos_x && region->pos.y == pos_y + 1) + south = region; + } + + RegionTile* outputTiles[17][17]; + + for (int yy = 0; yy < 17; yy++) + for (int xx = 0; xx < 17; xx++) + { + auto tile = out->add_tiles(); + outputTiles[xx][yy] = tile; + //This is because the bottom row doesn't line up. + if (xx == 16 && yy == 16 && southEast != NULL) + { + tile->set_elevation(southEast->elevation[0][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y + 1), (southEast->biome[0][0])), worldData); + } + else if (xx == 16 && east != NULL) + { + tile->set_elevation(east->elevation[0][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x + 1, pos_y), (east->biome[0][yy])), worldData); + } + else if (yy == 16 && south != NULL) + { + tile->set_elevation(south->elevation[xx][0]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y + 1), (south->biome[xx][0])), worldData); + } + else + { + tile->set_elevation(worldRegionDetails->elevation[xx][yy]); + AddRegionTiles(tile, ShiftCoords(df::coord2d(pos_x, pos_y), (worldRegionDetails->biome[xx][yy])), worldData); + } + + auto riverTile = tile->mutable_river_tiles(); + auto east = riverTile->mutable_east(); + auto north = riverTile->mutable_north(); + auto south = riverTile->mutable_south(); + auto west = riverTile->mutable_west(); + + if (xx < 16) + { + north->set_active(worldRegionDetails->rivers_vertical.active[xx][yy]); + north->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy]); + north->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy]); + north->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy]); + } + else + { + north->set_active(0); + north->set_elevation(100); + north->set_min_pos(-30000); + north->set_max_pos(-30000); + } + + if (yy < 16 && xx < 16) + { + south->set_active(worldRegionDetails->rivers_vertical.active[xx][yy + 1]); + south->set_elevation(worldRegionDetails->rivers_vertical.elevation[xx][yy + 1]); + south->set_min_pos(worldRegionDetails->rivers_vertical.x_min[xx][yy + 1]); + south->set_max_pos(worldRegionDetails->rivers_vertical.x_max[xx][yy + 1]); + } + else + { + south->set_active(0); + south->set_elevation(100); + south->set_min_pos(-30000); + south->set_max_pos(-30000); + } + + if (yy < 16) + { + west->set_active(worldRegionDetails->rivers_horizontal.active[xx][yy]); + west->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx][yy]); + west->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx][yy]); + west->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx][yy]); + } + else + { + west->set_active(0); + west->set_elevation(100); + west->set_min_pos(-30000); + west->set_max_pos(-30000); + } + + if (xx < 16 && yy < 16) + { + east->set_active(worldRegionDetails->rivers_horizontal.active[xx + 1][yy]); + east->set_elevation(worldRegionDetails->rivers_horizontal.elevation[xx + 1][yy]); + east->set_min_pos(worldRegionDetails->rivers_horizontal.y_min[xx + 1][yy]); + east->set_max_pos(worldRegionDetails->rivers_horizontal.y_max[xx + 1][yy]); + } + else + { + east->set_active(0); + east->set_elevation(100); + east->set_min_pos(-30000); + east->set_max_pos(-30000); + } + } + + auto regionMap = worldData->region_map[pos_x][pos_y]; + + for (size_t i = 0; i < worldData->sites.size(); i++) + { + df::world_site* site = worldData->sites[i]; + if (!site) + continue; + + int region_min_x = pos_x * 16; + int region_min_y = pos_y * 16; + + if ((site->global_min_x > (region_min_x + 16)) || + (site->global_min_y > (region_min_y + 16)) || + (site->global_max_x < (region_min_x)) || + (site->global_max_y < (region_min_y))) + continue; + + if (site->realization == NULL) + continue; + + auto realization = site->realization; + for (int site_x = 0; site_x < 17; site_x++) + for (int site_y = 0; site_y < 17; site_y++) + { + int region_x = site->global_min_x - region_min_x + site_x; + int region_y = site->global_min_y - region_min_y + site_y; + + if (region_x < 0 || region_y < 0 || region_x >= 16 || region_y >= 16) + continue; + + for (size_t j = 0; j < realization->building_map[site_x][site_y].buildings.size(); j++) + { + auto in_building = realization->building_map[site_x][site_y].buildings[j]; + auto out_building = outputTiles[region_x][region_y]->add_buildings(); + + out_building->set_id(in_building->id); +#if DF_VERSION_INT > 34011 + out_building->set_type((SiteRealizationBuildingType)in_building->type); +#endif + out_building->set_min_x(in_building->min_x - (site_x * 48)); + out_building->set_min_y(in_building->min_y - (site_y * 48)); + out_building->set_max_x(in_building->max_x - (site_x * 48)); + out_building->set_max_y(in_building->max_y - (site_y * 48)); + + CopyMat(out_building->mutable_material(), in_building->item.mat_type, in_building->item.mat_index); + +#if DF_VERSION_INT >= 43005 + STRICT_VIRTUAL_CAST_VAR(tower_info, df::site_realization_building_info_castle_towerst, in_building->building_info); + if (tower_info) + { + CopyMat(out_building->mutable_material(), tower_info->wall_item.mat_type, tower_info->wall_item.mat_index); + + auto out_tower = out_building->mutable_tower_info(); + out_tower->set_roof_z(tower_info->roof_z); + out_tower->set_round(tower_info->shape.bits.round); + out_tower->set_goblin(tower_info->shape.bits.goblin); + } + STRICT_VIRTUAL_CAST_VAR(wall_info, df::site_realization_building_info_castle_wallst, in_building->building_info); + if (wall_info) + { + CopyMat(out_building->mutable_material(), wall_info->wall_item.mat_type, wall_info->wall_item.mat_index); + + auto out_wall = out_building->mutable_wall_info(); + + out_wall->set_start_x(wall_info->start_x - (site_x * 48)); + out_wall->set_start_y(wall_info->start_y - (site_y * 48)); + out_wall->set_start_z(wall_info->start_z); + out_wall->set_end_x(wall_info->end_x - (site_x * 48)); + out_wall->set_end_y(wall_info->end_y - (site_y * 48)); + out_wall->set_end_z(wall_info->end_z); + } +#endif + } + + } + } +} + +static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) +{ + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (size_t i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + WorldMap * regionMap = out->add_world_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; +} + +static command_result GetRegionMapsNew(color_ostream &stream, const EmptyMessage *in, RegionMaps *out) +{ + if (!df::global::world->world_data) + { + return CR_FAILURE; + } + df::world_data * data = df::global::world->world_data; + for (size_t i = 0; i < data->region_details.size(); i++) + { + df::world_region_details * region = data->region_details[i]; + if (!region) + continue; + RegionMap * regionMap = out->add_region_maps(); + CopyLocalMap(data, region, regionMap); + } + return CR_OK; +} + +static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out) +{ + return GetPartialCreatureRaws(stream, NULL, out); +} + +static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRequest *in, CreatureRawList *out) +{ + if (!df::global::world) + return CR_FAILURE; + + df::world * world = df::global::world; + + int list_start = 0; + int list_end = world->raws.creatures.all.size(); + + if (in != nullptr) + { + list_start = in->list_start(); + if (in->list_end() < list_end) + list_end = in->list_end(); + } + + for (int i = list_start; i < list_end; i++) + { + df::creature_raw * orig_creature = world->raws.creatures.all[i]; + + auto send_creature = out->add_creature_raws(); + + send_creature->set_index(i); + send_creature->set_creature_id(orig_creature->creature_id); + send_creature->add_name(orig_creature->name[0]); + send_creature->add_name(orig_creature->name[1]); + send_creature->add_name(orig_creature->name[2]); + + send_creature->add_general_baby_name(orig_creature->general_baby_name[0]); + send_creature->add_general_baby_name(orig_creature->general_baby_name[1]); + + send_creature->add_general_child_name(orig_creature->general_child_name[0]); + send_creature->add_general_child_name(orig_creature->general_child_name[1]); + + send_creature->set_creature_tile(orig_creature->creature_tile); + send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile); + + ConvertDfColor(orig_creature->color, send_creature->mutable_color()); + + send_creature->set_adultsize(orig_creature->adultsize); + + for (size_t j = 0; j < orig_creature->caste.size(); j++) + { + auto orig_caste = orig_creature->caste[j]; + if (!orig_caste) + continue; + auto send_caste = send_creature->add_caste(); + + send_caste->set_index(j); + + send_caste->set_caste_id(orig_caste->caste_id); + + send_caste->add_caste_name(orig_caste->caste_name[0]); + send_caste->add_caste_name(orig_caste->caste_name[1]); + send_caste->add_caste_name(orig_caste->caste_name[2]); + + send_caste->add_baby_name(orig_caste->baby_name[0]); + send_caste->add_baby_name(orig_caste->baby_name[1]); + + send_caste->add_child_name(orig_caste->child_name[0]); + send_caste->add_child_name(orig_caste->child_name[1]); + send_caste->set_gender(orig_caste->gender); + + for (size_t partIndex = 0; partIndex < orig_caste->body_info.body_parts.size(); partIndex++) + { + auto orig_part = orig_caste->body_info.body_parts[partIndex]; + if (!orig_part) + continue; + auto send_part = send_caste->add_body_parts(); + + send_part->set_token(orig_part->token); + send_part->set_category(orig_part->category); + send_part->set_parent(orig_part->con_part_id); + + for (int partFlagIndex = 0; partFlagIndex <= ENUM_LAST_ITEM(body_part_raw_flags); partFlagIndex++) + { + send_part->add_flags(orig_part->flags.is_set((body_part_raw_flags::body_part_raw_flags)partFlagIndex)); + } + + for (size_t layerIndex = 0; layerIndex < orig_part->layers.size(); layerIndex++) + { + auto orig_layer = orig_part->layers[layerIndex]; + if (!orig_layer) + continue; + auto send_layer = send_part->add_layers(); + + send_layer->set_layer_name(orig_layer->layer_name); + send_layer->set_tissue_id(orig_layer->tissue_id); + send_layer->set_layer_depth(orig_layer->layer_depth); + for (size_t layerModIndex = 0; layerModIndex < orig_layer->bp_modifiers.size(); layerModIndex++) + { + send_layer->add_bp_modifiers(orig_layer->bp_modifiers[layerModIndex]); + } + } + + send_part->set_relsize(orig_part->relsize); + } + + send_caste->set_total_relsize(orig_caste->body_info.total_relsize); + + for (size_t k = 0; k < orig_caste->bp_appearance.modifiers.size(); k++) + { + auto send_mod = send_caste->add_modifiers(); + auto orig_mod = orig_caste->bp_appearance.modifiers[k]; + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + +#if DF_VERSION_INT > 34011 + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else +#endif + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + + } + for (size_t k = 0; k < orig_caste->bp_appearance.modifier_idx.size(); k++) + { + send_caste->add_modifier_idx(orig_caste->bp_appearance.modifier_idx[k]); + send_caste->add_part_idx(orig_caste->bp_appearance.part_idx[k]); + send_caste->add_layer_idx(orig_caste->bp_appearance.layer_idx[k]); + } + for (size_t k = 0; k < orig_caste->body_appearance_modifiers.size(); k++) + { + auto send_mod = send_caste->add_body_appearance_modifiers(); + auto orig_mod = orig_caste->body_appearance_modifiers[k]; + + send_mod->set_type(ENUM_KEY_STR(appearance_modifier_type, orig_mod->type)); + +#if DF_VERSION_INT > 34011 + if (orig_mod->growth_rate > 0) + { + send_mod->set_mod_min(orig_mod->growth_min); + send_mod->set_mod_max(orig_mod->growth_max); + } + else +#endif + { + send_mod->set_mod_min(orig_mod->ranges[0]); + send_mod->set_mod_max(orig_mod->ranges[6]); + } + } + for (size_t k = 0; k < orig_caste->color_modifiers.size(); k++) + { + auto send_mod = send_caste->add_color_modifiers(); + auto orig_mod = orig_caste->color_modifiers[k]; + + for (size_t l = 0; l < orig_mod->pattern_index.size(); l++) + { + auto orig_pattern = world->raws.descriptors.patterns[orig_mod->pattern_index[l]]; + auto send_pattern = send_mod->add_patterns(); + + for (size_t m = 0; m < orig_pattern->colors.size(); m++) + { + auto send_color = send_pattern->add_colors(); + auto orig_color = world->raws.descriptors.colors[orig_pattern->colors[m]]; + send_color->set_red(orig_color->red * 255.0); + send_color->set_green(orig_color->green * 255.0); + send_color->set_blue(orig_color->blue * 255.0); + } + + send_pattern->set_id(orig_pattern->id); + send_pattern->set_pattern((PatternType)orig_pattern->pattern); + } + + for (size_t l = 0; l < orig_mod->body_part_id.size(); l++) + { + send_mod->add_body_part_id(orig_mod->body_part_id[l]); + send_mod->add_tissue_layer_id(orig_mod->tissue_layer_id[l]); + send_mod->set_start_date(orig_mod->start_date); + send_mod->set_end_date(orig_mod->end_date); + send_mod->set_part(orig_mod->part); + } + } + + send_caste->set_description(orig_caste->description); + send_caste->set_adult_size(orig_caste->misc.adult_size); + } + + for (size_t j = 0; j < orig_creature->tissue.size(); j++) + { + auto orig_tissue = orig_creature->tissue[j]; + auto send_tissue = send_creature->add_tissues(); + + send_tissue->set_id(orig_tissue->id); + send_tissue->set_name(orig_tissue->tissue_name_singular); + send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); + + CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); + } + } + + return CR_OK; +} + +static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in, PlantRawList *out) +{ + GetPartialPlantRaws(stream, nullptr, out); + return CR_OK; +} + +static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out) +{ + if (!df::global::world) + return CR_FAILURE; + + df::world * world = df::global::world; + + for (size_t i = 0; i < world->raws.plants.all.size(); i++) + { + df::plant_raw* plant_local = world->raws.plants.all[i]; + PlantRaw* plant_remote = out->add_plant_raws(); + + plant_remote->set_index(i); + plant_remote->set_id(plant_local->id); + plant_remote->set_name(plant_local->name); + if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) + plant_remote->set_tile(plant_local->tiles.shrub_tile); + else + plant_remote->set_tile(plant_local->tiles.tree_tile); +#if DF_VERSION_INT > 34011 + for (size_t j = 0; j < plant_local->growths.size(); j++) + { + df::plant_growth* growth_local = plant_local->growths[j]; + TreeGrowth * growth_remote = plant_remote->add_growths(); + growth_remote->set_index(j); + growth_remote->set_id(growth_local->id); + growth_remote->set_name(growth_local->name); + for (size_t k = 0; k < growth_local->prints.size(); k++) + { + df::plant_growth_print* print_local = growth_local->prints[k]; + GrowthPrint* print_remote = growth_remote->add_prints(); + print_remote->set_priority(print_local->priority); + print_remote->set_color(print_local->color[0] | (print_local->color[2] * 8)); + print_remote->set_timing_start(print_local->timing_start); + print_remote->set_timing_end(print_local->timing_end); + print_remote->set_tile(print_local->tile_growth); + } + growth_remote->set_timing_start(growth_local->timing_1); + growth_remote->set_timing_end(growth_local->timing_2); + growth_remote->set_twigs(growth_local->locations.bits.twigs); + growth_remote->set_light_branches(growth_local->locations.bits.light_branches); + growth_remote->set_heavy_branches(growth_local->locations.bits.heavy_branches); + growth_remote->set_trunk(growth_local->locations.bits.trunk); + growth_remote->set_roots(growth_local->locations.bits.roots); + growth_remote->set_cap(growth_local->locations.bits.cap); + growth_remote->set_sapling(growth_local->locations.bits.sapling); + growth_remote->set_timing_start(growth_local->timing_1); + growth_remote->set_timing_end(growth_local->timing_2); + growth_remote->set_trunk_height_start(growth_local->trunk_height_perc_1); + growth_remote->set_trunk_height_end(growth_local->trunk_height_perc_2); + CopyMat(growth_remote->mutable_mat(), growth_local->mat_type, growth_local->mat_index); + } +#endif + } + return CR_OK; +} + +static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out) +{ + df::graphic * gps = df::global::gps; + out->set_width(gps->dimx); + out->set_height(gps->dimy); + for (int i = 0; i < (gps->dimx * gps->dimy); i++) + { + int index = i * 4; + auto tile = out->add_tiles(); + tile->set_character(gps->screen[index]); + tile->set_foreground(gps->screen[index + 1] | (gps->screen[index + 3] * 8)); + tile->set_background(gps->screen[index + 2]); + } + + return CR_OK; +} + +static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in) +{ +#if DF_VERSION_INT > 34011 + SDL::Event e; + e.key.type = in->type(); + e.key.state = in->state(); + e.key.ksym.mod = (SDL::Mod)in->mod(); + e.key.ksym.scancode = in->scancode(); + e.key.ksym.sym = (SDL::Key)in->sym(); + e.key.ksym.unicode = in->unicode(); + SDL_PushEvent(&e); +#endif + return CR_OK; +} + +static command_result SendDigCommand(color_ostream &stream, const DigCommand *in) +{ + MapExtras::MapCache mc; + + for (int i = 0; i < in->locations_size(); i++) + { + auto pos = in->locations(i); + auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z())); + switch (in->designation()) + { + case NO_DIG: + des.bits.dig = tile_dig_designation::No; + break; + case DEFAULT_DIG: + des.bits.dig = tile_dig_designation::Default; + break; + case UP_DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpDownStair; + break; + case CHANNEL_DIG: + des.bits.dig = tile_dig_designation::Channel; + break; + case RAMP_DIG: + des.bits.dig = tile_dig_designation::Ramp; + break; + case DOWN_STAIR_DIG: + des.bits.dig = tile_dig_designation::DownStair; + break; + case UP_STAIR_DIG: + des.bits.dig = tile_dig_designation::UpStair; + break; + default: + break; + } + mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des); + +#if DF_VERSION_INT >= 43005 + //remove and job postings related. + for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next) + { + if (listing->item == NULL) + continue; + auto type = listing->item->job_type; + switch (type) + { + case df::enums::job_type::CarveFortification: + case df::enums::job_type::DetailWall: + case df::enums::job_type::DetailFloor: + case df::enums::job_type::Dig: + case df::enums::job_type::CarveUpwardStaircase: + case df::enums::job_type::CarveDownwardStaircase: + case df::enums::job_type::CarveUpDownStaircase: + case df::enums::job_type::CarveRamp: + case df::enums::job_type::DigChannel: + case df::enums::job_type::FellTree: + case df::enums::job_type::GatherPlants: + case df::enums::job_type::RemoveConstruction: + case df::enums::job_type::CarveTrack: + { + if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z())) + { + Job::removeJob(listing->item); + goto JOB_FOUND; + } + break; + } + default: + continue; + } + } + JOB_FOUND: + continue; +#endif + } + + mc.WriteAll(); + return CR_OK; +} + +static command_result SetPauseState(color_ostream &stream, const SingleBool *in) +{ + DFHack::World::SetPauseState(in->value()); + return CR_OK; +} + +static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out) +{ + out->set_value(World::ReadPauseState()); + return CR_OK; +} + +static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out) +{ + out->set_dfhack_version(DFHACK_VERSION); +#if DF_VERSION_INT == 34011 + out->set_dwarf_fortress_version("0.34.11"); +#else + out->set_dwarf_fortress_version(DF_VERSION); +#endif + out->set_remote_fortress_reader_version(RFR_VERSION); + return CR_OK; +} + +int lastSentReportID = -1; + +static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out) +{ + //First find the last report we sent, so it doesn't get resent. + int lastSentIndex = -1; + for (int i = world->status.reports.size() - 1; i >= 0; i--) + { + auto local_rep = world->status.reports[i]; + if (local_rep->id <= lastSentReportID) + { + lastSentIndex = i; + break; + } + } + for (size_t i = lastSentIndex + 1; i < world->status.reports.size(); i++) + { + auto local_rep = world->status.reports[i]; + if (!local_rep) + continue; + auto send_rep = out->add_reports(); + send_rep->set_type(local_rep->type); + send_rep->set_text(DF2UTF(local_rep->text)); + ConvertDfColor(local_rep->color | (local_rep->bright ? 8 : 0), send_rep->mutable_color()); + send_rep->set_duration(local_rep->duration); + send_rep->set_continuation(local_rep->flags.bits.continuation); + send_rep->set_unconscious(local_rep->flags.bits.unconscious); + send_rep->set_announcement(local_rep->flags.bits.announcement); + send_rep->set_repeat_count(local_rep->repeat_count); + ConvertDFCoord(local_rep->pos, send_rep->mutable_pos()); + send_rep->set_id(local_rep->id); + send_rep->set_year(local_rep->year); + send_rep->set_time(local_rep->time); + lastSentReportID = local_rep->id; + } + return CR_OK; +} + +static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out) +{ + if (!world) + return CR_FAILURE; + + for (size_t i = 0; i < world->raws.descriptors.shapes.size(); i++) + { + auto shape = world->raws.descriptors.shapes[i]; + auto netShape = out->add_shapes(); + netShape->set_id(shape->id); + netShape->set_tile(shape->tile); + } + return CR_OK; +} diff --git a/plugins/rendermax/CMakeLists.txt b/plugins/rendermax/CMakeLists.txt index ba589732d..c83790412 100644 --- a/plugins/rendermax/CMakeLists.txt +++ b/plugins/rendermax/CMakeLists.txt @@ -15,27 +15,7 @@ SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) # mash them together (headers are marked as headers and nothing will try to compile them) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) - -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - lua - dfhack-tinythread - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra windows libs here - lua - dfhack-tinythread - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) # this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(rendermax ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +DFHACK_PLUGIN(rendermax ${PROJECT_SRCS} LINK_LIBRARIES lua dfhack-tinythread) install(FILES rendermax.lua DESTINATION ${DFHACK_DATA_DESTINATION}/raw) diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 5e6f2e2de..90c38a06a 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -1,34 +1,33 @@ #include "renderer_light.hpp" #include -#include #include +#include +#include #include "tinythread.h" #include "LuaTools.h" #include "modules/Gui.h" -#include "modules/Screen.h" #include "modules/Maps.h" - +#include "modules/Screen.h" #include "modules/Units.h" -#include "df/graphic.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/viewscreen_dungeonmodest.h" -#include "df/flow_info.h" -#include "df/world.h" +#include "df/block_square_event_material_spatterst.h" #include "df/building.h" #include "df/building_doorst.h" #include "df/building_floodgatest.h" -#include "df/plant.h" -#include "df/plant_raw.h" +#include "df/flow_info.h" +#include "df/graphic.h" #include "df/item.h" #include "df/items_other_id.h" +#include "df/plant.h" +#include "df/plant_raw.h" #include "df/unit.h" - -#include +#include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" using df::global::gps; using namespace DFHack; @@ -89,8 +88,8 @@ rect2d getMapViewport() int menu_x1=area_x2-MENU_WIDTH-1; int view_rb=w-1; - int area_pos=*df::global::ui_area_map_width; - int menu_pos=*df::global::ui_menu_width; + int area_pos=(*df::global::ui_menu_width)[1]; + int menu_pos=(*df::global::ui_menu_width)[0]; if(area_pos<3) { view_rb=area_x2; @@ -105,7 +104,7 @@ rect2d getMapViewport() } return mkrect_wh(1,1,view_rb,view_height+1); } -lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),doDebug(false),threading(this) +lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),threading(this),doDebug(false) { reinit(); defaultSettings(); @@ -336,7 +335,6 @@ void lightingEngineViewscreen::fixAdvMode(int mode) int window_x=*df::global::window_x; int window_y=*df::global::window_y; int window_z=*df::global::window_z; - coord2d vpSize=rect_size(vp); //mode 0-> make dark non-visible parts if(mode==0) { @@ -393,7 +391,7 @@ rgbf getStandartColor(int colorId) int getPlantNumber(const std::string& id) { std::vector& vec=df::plant_raw::get_vector(); - for(int i=0;iid==id) return i; @@ -607,7 +605,7 @@ rgbf lightingEngineViewscreen::getSkyColor(float v) float pos=v*(dayColors.size()-1); int pre=floor(pos); pos-=pre; - if(pre==dayColors.size()-1) + if(pre==int(dayColors.size())-1) return dayColors[pre]; return dayColors[pre]*(1-pos)+dayColors[pre+1]*pos; } @@ -721,7 +719,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() if(!block) continue; //flows - for(int i=0;iflows.size();i++) + for(size_t i=0;iflows.size();i++) { df::flow_info* f=block->flows[i]; if(f && f->density>0 && (f->type==df::flow_type::Dragonfire || f->type==df::flow_type::Fire)) @@ -751,7 +749,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() } //blood and other goo - for(int i=0;iblock_events.size();i++) + for(size_t i=0;iblock_events.size();i++) { df::block_square_event* ev=block->block_events[i]; df::block_square_event_type ev_type=ev->getType(); @@ -791,7 +789,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() //citizen only emit light, if defined //or other creatures if(matCitizen.isEmiting || creatureDefs.size()>0) - for (int i=0;iunits.active.size();++i) + for (size_t i=0;iunits.active.size();++i) { df::unit *u = df::global::world->units.active[i]; coord2d pos=worldToViewportCoord(coord2d(u->pos.x,u->pos.y),vp,window2d); @@ -940,14 +938,14 @@ matLightDef lua_parseMatDef(lua_State* L) matLightDef ret; lua_getfield(L,-1,"tr"); - if(ret.isTransparent=!lua_isnil(L,-1)) + if((ret.isTransparent=!lua_isnil(L,-1))) { ret.transparency=lua_parseLightCell(L); } lua_pop(L,1); lua_getfield(L,-1,"em"); - if(ret.isEmiting=!lua_isnil(L,-1)) + if((ret.isEmiting=!lua_isnil(L,-1))) { ret.emitColor=lua_parseLightCell(L); lua_pop(L,1); @@ -1249,7 +1247,7 @@ void lightingEngineViewscreen::loadSettings() /* * Threading stuff */ -lightThread::lightThread( lightThreadDispatch& dispatch ):dispatch(dispatch),isDone(false),myThread(0) +lightThread::lightThread( lightThreadDispatch& dispatch ):dispatch(dispatch),myThread(0),isDone(false) { } @@ -1311,7 +1309,7 @@ void lightThread::work() void lightThread::combine() { - for(int i=0;ilights),occlusion(parent->ocupancy),num_diffusion(parent->num_diffuse), - lightMap(parent->lightMap),writeCount(0),occlusionReady(false) +lightThreadDispatch::lightThreadDispatch( lightingEngineViewscreen* p ):parent(p),lights(parent->lights), + occlusionReady(false),occlusion(parent->ocupancy),num_diffusion(parent->num_diffuse), + lightMap(parent->lightMap),writeCount(0) { } void lightThreadDispatch::shutdown() { - for(int i=0;iisDone=true; } occlusionDone.notify_all();//if stuck signal that you are done with stuff. - for(int i=0;imyThread->join(); } @@ -1475,7 +1474,7 @@ void lightThreadDispatch::start(int count) void lightThreadDispatch::waitForWrites() { tthread::lock_guard guard(writeLock); - while(threadPool.size()>writeCount)//missed it somehow already. + while(threadPool.size()>size_t(writeCount))//missed it somehow already. { writesDone.wait(writeLock); //if not, wait a bit } diff --git a/plugins/rendermax/renderer_light.hpp b/plugins/rendermax/renderer_light.hpp index ec1561c76..b0fb8c5d5 100644 --- a/plugins/rendermax/renderer_light.hpp +++ b/plugins/rendermax/renderer_light.hpp @@ -181,11 +181,11 @@ struct matLightDef bool flicker; rgbf emitColor; int radius; - matLightDef():isTransparent(false),isEmiting(false),transparency(0,0,0),emitColor(0,0,0),radius(0){} - matLightDef(rgbf transparency,rgbf emit,int rad):isTransparent(true),isEmiting(true), - transparency(transparency),emitColor(emit),radius(rad){} - matLightDef(rgbf emit,int rad):isTransparent(false),isEmiting(true),emitColor(emit),radius(rad),transparency(0,0,0){} - matLightDef(rgbf transparency):isTransparent(true),isEmiting(false),transparency(transparency){} + matLightDef():isTransparent(false),transparency(0,0,0),isEmiting(false),emitColor(0,0,0),radius(0){} + matLightDef(rgbf transparency,rgbf emit,int rad):isTransparent(true),transparency(transparency), + isEmiting(true),emitColor(emit),radius(rad){} + matLightDef(rgbf emit,int rad):isTransparent(false),transparency(0,0,0),isEmiting(true),emitColor(emit),radius(rad){} + matLightDef(rgbf transparency):isTransparent(true),transparency(transparency),isEmiting(false){} lightSource makeSource(float size=1) const { if(size>0.999 && size<1.001) diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index 2140743ca..be8be07ba 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -36,8 +36,6 @@ REQUIRE_GLOBAL(enabler); REQUIRE_GLOBAL(gametype); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(ui); -REQUIRE_GLOBAL(ui_area_map_width); -REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(window_x); REQUIRE_GLOBAL(window_y); REQUIRE_GLOBAL(window_z); diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 8553fd4f0..5909c0956 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -27,6 +27,8 @@ #include "modules/World.h" +#include "uicommon.h" + using std::map; using std::string; using std::vector; @@ -50,30 +52,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -template -static void for_each_(vector &v, Fn func) -{ - for_each(v.begin(), v.end(), func); -} - -template -static void transform_(vector &src, vector &dst, Fn func) -{ - transform(src.begin(), src.end(), back_inserter(dst), func); -} - -void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - if (newline) - { - ++y; - x = left_margin; - } - else - x += text.length(); -} - df::job *get_suspended_job(df::building *bld) { if (bld->getBuildStage() != 0) @@ -147,7 +125,7 @@ void show_suspended_buildings() auto dims = Gui::getDwarfmodeViewDims(); int left_margin = vx + dims.map_x2; - int bottom_margin = vy + dims.y2; + int bottom_margin = vy + dims.map_y2 - 1; for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();) { @@ -168,7 +146,7 @@ void show_suspended_buildings() else if (sb->was_resumed) color = COLOR_RED; - OutputString(color, x, y, "X"); + OutputString(color, x, y, "X", false, 0, 0, true /* map */); } sb++; diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 01c007d05..6908225ea 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -10,8 +10,11 @@ #include "modules/World.h" #include "modules/MapCache.h" #include "modules/Gui.h" -#include "df/construction.h" + #include "df/block_square_event_frozen_liquidst.h" +#include "df/construction.h" +#include "df/world.h" + using MapExtras::MapCache; using std::string; @@ -41,7 +44,7 @@ bool isSafe(df::coord c) if (local_feature.type == feature_type::deep_special_tube || local_feature.type == feature_type::deep_surface_portal) return false; // And Hell *is* Hell. - if (global_feature.type == feature_type::feature_underworld_from_layer) + if (global_feature.type == feature_type::underworld_from_layer) return false; // otherwise it's safe. return true; @@ -398,12 +401,27 @@ command_result revflood(color_ostream &out, vector & params) if(!MCache->testCoord(current)) continue; - df::tiletype tt = MCache->baseTiletypeAt(current); df::tile_designation des = MCache->designationAt(current); if(!des.bits.hidden) { continue; } + + // use base tile (beneath constructions/ice), to avoid bug #1871 + df::tiletype tt = MCache->baseTiletypeAt(current); + + // unless the actual tile is a downward stairway + df::tiletype ctt = MCache->tiletypeAt(current); + switch (tileShape(ctt)) + { + case tiletype_shape::STAIR_UPDOWN: + case tiletype_shape::STAIR_DOWN: + tt = ctt; + break; + default: + break; + } + bool below = 0; bool above = 0; bool sides = 0; @@ -417,6 +435,7 @@ command_result revflood(color_ostream &out, vector & params) unhide = 0; break; // air/free space + case tiletype_shape::NONE: case tiletype_shape::EMPTY: case tiletype_shape::RAMP_TOP: case tiletype_shape::STAIR_UPDOWN: diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 3aeac6dc5..c2c80e79e 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,17 +1,59 @@ -IF (APPLE) - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.osx.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - SET(RUBYLIB ${CMAKE_CURRENT_BINARY_DIR}/libruby.dylib) -ELSEIF(UNIX) - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.linux.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - FILE(RENAME ${CMAKE_CURRENT_BINARY_DIR}/libruby1.8.so.1.8.7 ${CMAKE_CURRENT_BINARY_DIR}/libruby.so) - SET(RUBYLIB ${CMAKE_CURRENT_BINARY_DIR}/libruby.so) -ELSE() - EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - FILE(RENAME ${CMAKE_CURRENT_BINARY_DIR}/msvcrt-ruby18.dll ${CMAKE_CURRENT_BINARY_DIR}/libruby.dll) - SET(RUBYLIB ${CMAKE_CURRENT_BINARY_DIR}/libruby.dll) +# Allow build system to turn off downloading of libruby.so. +OPTION(DOWNLOAD_RUBY "Download prebuilt libruby.so for ruby plugin." ON) + +IF (DOWNLOAD_RUBY) + + IF (APPLE) + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib) + SET(RUBYLIB_INSTALL_NAME "libruby.dylib") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + # MESSAGE("No ruby lib for 64-bit OS X yet") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-libruby187.dylib.gz" + "gz" + ${RUBYLIB}.gz + "e9bc4263557e652121b055a46abb4f97" + ${RUBYLIB} + "3ee5356759f764a440be5b5b44649826") + ENDIF() + ELSEIF(UNIX) + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so) + SET(RUBYLIB_INSTALL_NAME "libruby.so") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux64-libruby187.so.gz" + "gz" + ${RUBYLIB}.gz + "8eb757bb9ada08608914d8ca8906c427" + ${RUBYLIB} + "e8c36a06f031cfbf02def28169bb5f1f") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux32-libruby187.so.gz" + "gz" + ${RUBYLIB}.gz + "2d06f5069ff07ea934ecd40db55a4ac5" + ${RUBYLIB} + "b00d8d7086cb39f6fde793f9d89cb2d7") + ENDIF() + ELSE() + SET(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll) + SET(RUBYLIB_INSTALL_NAME "libruby.dll") + IF(${DFHACK_BUILD_ARCH} STREQUAL 64) + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-libruby200.dll.gz" + "gz" + ${RUBYLIB}.gz + "81db54a8b8b3090c94c6ae2147d30b8f" + ${RUBYLIB} + "8a8564418aebddef3dfee1e96690e713") + ELSE() + DOWNLOAD_FILE_UNZIP("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-libruby187.dll.gz" + "gz" + ${RUBYLIB}.gz + "ffc0f1b5b33748e2a36128e90c97f6b2" + ${RUBYLIB} + "482c1c418f4ee1a5f04203eee1cda0ef") + ENDIF() + ENDIF() + ENDIF() IF (APPLE OR UNIX) @@ -22,7 +64,7 @@ ENDIF (APPLE OR UNIX) ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} - COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} ${CMAKE_SYSTEM_NAME} ${DFHACK_BUILD_ARCH} # cmake quirk: depending on codegen.out.xml or generate_headers only is not enough, needs both # test by manually patching any library/xml/moo.xml, run make ruby-autogen-rb -j2, and check build/plugins/ruby/ruby-autogen.rb for patched xml data DEPENDS generate_headers ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl @@ -35,7 +77,14 @@ INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread") DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) ADD_DEPENDENCIES(ruby ruby-autogen-rb) -INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) +IF(EXISTS ${RUBYLIB}) + INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) +ELSE() + # Only fire this warning if DOWNLOAD_RUBY was set. + IF(NOT(APPLE AND ${DFHACK_BUILD_ARCH} STREQUAL 64) AND DOWNLOAD_RUBY) + MESSAGE(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed") + ENDIF() +ENDIF() INSTALL(DIRECTORY . DESTINATION hack/ruby diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 8a670718e..3f5ea4ffe 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -7,13 +7,33 @@ use XML::LibXML; our @lines_rb; -my $os; -if ($^O =~ /linux/i or $^O =~ /darwin/i) { +my $os = $ARGV[2] or die('os not provided (argv[2])'); +if ($os =~ /linux/i or $os =~ /darwin/i) { $os = 'linux'; -} else { +} elsif ($os =~ /windows/i) { $os = 'windows'; +} else { + die "Unknown OS: " . $ARGV[2] . "\n"; +} + +my $arch = $ARGV[3] or die('arch not provided (argv[3])'); +if ($arch =~ /64/i) { + $arch = 64; +} elsif ($arch =~ /32/i) { + $arch = 32; +} else { + die "Unknown architecture: " . $ARGV[3] . "\n"; } -$os = $ARGV[2] if ($ARGV[2]); + +# 32 bits on Windows and 32-bit *nix, 64 bits on 64-bit *nix +my $SIZEOF_LONG; +if ($os eq 'windows' || $arch == 32) { + $SIZEOF_LONG = 4; +} else { + $SIZEOF_LONG = 8; +} + +my $SIZEOF_PTR = ($arch == 64) ? 8 : 4; sub indent_rb(&) { my ($sub) = @_; @@ -207,7 +227,7 @@ sub render_global_class { $seen_class{$name}++; local $compound_off = 0; - $compound_off = 4 if ($meta eq 'class-type'); + $compound_off = $SIZEOF_PTR if ($meta eq 'class-type'); $compound_off = sizeof($global_types{$parent}) if $parent; local $current_typename = $rbname; @@ -224,7 +244,7 @@ sub render_global_class { indent_rb { my $sz = sizeof($type); # see comment is sub sizeof ; but gcc has sizeof(cls) aligned - $sz = align_field($sz, 4) if $os eq 'linux' and $meta eq 'class-type'; + $sz = align_field($sz, $SIZEOF_PTR) if $os eq 'linux' and $meta eq 'class-type'; push @lines_rb, "sizeof $sz\n"; push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name; @@ -405,8 +425,8 @@ sub render_class_vmethods_voff { for my $meth ($vms->findnodes('child::vmethod')) { - $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; - $voff += 4; + $voff += $SIZEOF_PTR if $meth->getAttribute('is-destructor') and $os eq 'linux'; + $voff += $SIZEOF_PTR; } return $voff; @@ -450,8 +470,8 @@ sub render_class_vmethods { } # on linux, the destructor uses 2 entries - $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; - $voff += 4; + $voff += $SIZEOF_PTR if $meth->getAttribute('is-destructor') and $os eq 'linux'; + $voff += $SIZEOF_PTR; } } @@ -486,7 +506,7 @@ sub render_class_vmethod_ret { { # raw method call returns an int32, mask according to actual return type my $retsubtype = $ret->getAttribute('ld:subtype'); - my $retbits = $ret->getAttribute('ld:bits'); + my $retbits = sizeof($ret) * 8; push @lines_rb, "val = $call"; if ($retsubtype eq 'bool') { @@ -557,10 +577,14 @@ sub render_global_objects { # define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world indent_rb { push @lines_rb, "Global = GlobalObjects.new._at(0)"; - for my $obj (@global_objects) + for my $oname (@global_objects) { - push @lines_rb, "def self.$obj ; Global.$obj ; end"; - push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end"; + push @lines_rb, "if DFHack.get_global_address('$oname') != 0"; + indent_rb { + push @lines_rb, "def self.$oname ; Global.$oname ; end"; + push @lines_rb, "def self.$oname=(v) ; Global.$oname = v ; end"; + }; + push @lines_rb, "end"; } }; } @@ -578,14 +602,14 @@ sub align_field { sub get_field_align { my ($field) = @_; - my $al = 4; + my $al = $SIZEOF_PTR; my $meta = $field->getAttribute('ld:meta'); if ($meta eq 'number') { - $al = $field->getAttribute('ld:bits')/8; - # linux aligns int64_t to 4, windows to 8 + $al = sizeof($field); + # linux aligns int64_t to $SIZEOF_PTR, windows to 8 # floats are 4 bytes so no pb - $al = 4 if ($al > 4 and ($os eq 'linux' or $al != 8)); + $al = 4 if ($al > 4 and (($os eq 'linux' and $arch == 32) or $al != 8)); } elsif ($meta eq 'global') { $al = get_global_align($field); } elsif ($meta eq 'compound') { @@ -637,6 +661,9 @@ sub get_compound_align { if ($st eq 'bitfield' or $st eq 'enum') { my $base = $field->getAttribute('base-type') || 'uint32_t'; + if ($base eq 'long') { + return $SIZEOF_LONG; + } print "$st type $base\n" if $base !~ /int(\d+)_t/; return $1/8; } @@ -655,10 +682,14 @@ sub sizeof { my $meta = $field->getAttribute('ld:meta'); if ($meta eq 'number') { + if ($field->getAttribute('ld:subtype') eq 'long') { + return $SIZEOF_LONG; + } + return $field->getAttribute('ld:bits')/8; } elsif ($meta eq 'pointer') { - return 4; + return $SIZEOF_PTR; } elsif ($meta eq 'static-array') { my $count = $field->getAttribute('count'); @@ -692,37 +723,35 @@ sub sizeof { my $subtype = $field->getAttribute('ld:subtype'); if ($subtype eq 'stl-vector') { - if ($os eq 'linux') { - return 12; - } elsif ($os eq 'windows') { - return 16; + if ($os eq 'linux' or $os eq 'windows') { + return ($arch == 64) ? 24 : 12; } else { print "sizeof stl-vector on $os\n"; } } elsif ($subtype eq 'stl-bit-vector') { if ($os eq 'linux') { - return 20; + return ($arch == 64) ? 40 : 20; } elsif ($os eq 'windows') { - return 20; + return ($arch == 64) ? 32 : 16; } else { print "sizeof stl-bit-vector on $os\n"; } } elsif ($subtype eq 'stl-deque') { if ($os eq 'linux') { - return 40; + return ($arch == 64) ? 80 : 40; } elsif ($os eq 'windows') { - return 24; + return ($arch == 64) ? 40 : 20; } else { print "sizeof stl-deque on $os\n"; } } elsif ($subtype eq 'df-linked-list') { - return 12; + return 3 * $SIZEOF_PTR; } elsif ($subtype eq 'df-flagarray') { - return 8; + return 2 * $SIZEOF_PTR; # XXX length may be 4 on windows? } elsif ($subtype eq 'df-static-flagarray') { return $field->getAttribute('count'); } elsif ($subtype eq 'df-array') { - return 8; # XXX 6 ? + return 4 + $SIZEOF_PTR; # XXX 4->2 ? } else { print "sizeof container $subtype\n"; } @@ -730,18 +759,20 @@ sub sizeof { } elsif ($meta eq 'primitive') { my $subtype = $field->getAttribute('ld:subtype'); - if ($subtype eq 'stl-string') { if ($os eq 'linux') { - return 4; + if ($subtype eq 'stl-string') { + if ($os eq 'linux') { + return ($arch == 64) ? 8 : 4; } elsif ($os eq 'windows') { - return 28; + return ($arch == 64) ? 32 : 24; } else { print "sizeof stl-string on $os\n"; } print "sizeof stl-string\n"; - } elsif ($subtype eq 'stl-fstream') { if ($os eq 'linux') { - return 284; + } elsif ($subtype eq 'stl-fstream') { + if ($os eq 'linux') { + return 284; # TODO: fix on x64 } elsif ($os eq 'windows') { - return 184; + return ($arch == 64) ? 280 : 192; } else { print "sizeof stl-fstream on $os\n"; } @@ -769,6 +800,10 @@ sub sizeof_compound { if ($st eq 'bitfield' or $st eq 'enum') { my $base = $field->getAttribute('base-type') || 'uint32_t'; + if ($base eq 'long') { + $sizeof_cache{$typename} = $SIZEOF_LONG if $typename; + return $SIZEOF_LONG; + } print "$st type $base\n" if $base !~ /int(\d+)_t/; $sizeof_cache{$typename} = $1/8 if $typename; return $1/8; @@ -787,11 +822,11 @@ sub sizeof_compound { my $parent = $field->getAttribute('inherits-from'); my $off = 0; - $off = 4 if ($meta eq 'class-type'); + $off = $SIZEOF_PTR if ($meta eq 'class-type'); $off = sizeof($global_types{$parent}) if ($parent); my $al = 1; - $al = 4 if ($meta eq 'class-type'); + $al = $SIZEOF_PTR if ($meta eq 'class-type'); for my $f ($field->findnodes('child::ld:field')) { @@ -859,7 +894,9 @@ sub render_item_number { $subtype ||= $g->getAttribute('base-type') if ($g); $subtype = 'int32_t' if (!$subtype); - if ($subtype eq 'int64_t') { + if ($subtype eq 'uint64_t') { + push @lines_rb, 'number 64, false'; + } elsif ($subtype eq 'int64_t') { push @lines_rb, 'number 64, true'; } elsif ($subtype eq 'uint32_t') { push @lines_rb, 'number 32, false'; @@ -877,6 +914,8 @@ sub render_item_number { push @lines_rb, 'number 8, true'; $initvalue ||= 'nil'; $typename ||= 'BooleanEnum'; + } elsif ($subtype eq 'long') { + push @lines_rb, 'number ' . $SIZEOF_LONG . ', true'; } elsif ($subtype eq 's-float') { push @lines_rb, 'float'; return; diff --git a/plugins/ruby/job.rb b/plugins/ruby/job.rb index e489dcc91..bf033fbb3 100644 --- a/plugins/ruby/job.rb +++ b/plugins/ruby/job.rb @@ -1,9 +1,9 @@ module DFHack class << self # link a job to the world - # allocate & set job.id, allocate a JobListLink, link to job & world.job_list + # allocate & set job.id, allocate a JobListLink, link to job & world.jobs.list def job_link(job) - lastjob = world.job_list + lastjob = world.jobs.list lastjob = lastjob.next while lastjob.next joblink = JobListLink.cpp_new joblink.prev = lastjob diff --git a/plugins/ruby/libruby187.linux.tar.gz b/plugins/ruby/libruby187.linux.tar.gz deleted file mode 100644 index e6e9e86ab..000000000 Binary files a/plugins/ruby/libruby187.linux.tar.gz and /dev/null differ diff --git a/plugins/ruby/libruby187.osx.tar.gz b/plugins/ruby/libruby187.osx.tar.gz deleted file mode 100644 index 421533957..000000000 Binary files a/plugins/ruby/libruby187.osx.tar.gz and /dev/null differ diff --git a/plugins/ruby/linux32/.gitignore b/plugins/ruby/linux32/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/linux32/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/linux64/.gitignore b/plugins/ruby/linux64/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/linux64/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index f6f69de75..5c6295946 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -269,7 +269,7 @@ module DFHack def dig(mode=:Default) if mode == :Smooth if (tilemat == :STONE or tilemat == :MINERAL) and caption !~ /smooth|pillar|fortification/i and # XXX caption.. - designation.smooth == 0 and (designation.hidden or not df.world.job_list.find { |j| + designation.smooth == 0 and (designation.hidden or not df.world.jobs.list.find { |j| # the game removes 'smooth' designation as soon as it assigns a job, if we # re-set it the game may queue another :DetailWall that will carve a fortification (j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self) @@ -279,7 +279,7 @@ module DFHack mapblock.flags.designated = true end else - return if mode != :No and designation.dig == :No and not designation.hidden and df.world.job_list.find { |j| + return if mode != :No and designation.dig == :No and not designation.hidden and df.world.jobs.list.find { |j| # someone already enroute to dig here, avoid 'Inappropriate dig square' spam JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self) } diff --git a/plugins/ruby/msvcrtruby187.tar.gz b/plugins/ruby/msvcrtruby187.tar.gz deleted file mode 100644 index 4a770275a..000000000 Binary files a/plugins/ruby/msvcrtruby187.tar.gz and /dev/null differ diff --git a/plugins/ruby/osx32/.gitignore b/plugins/ruby/osx32/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/osx32/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/osx64/.gitignore b/plugins/ruby/osx64/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/osx64/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 7e26e93ac..c635efc2e 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -1,5 +1,32 @@ # definition of classes used by ruby-autogen +$sizeof_ptr = case RUBY_PLATFORM + when /x86_64|x64/i; 64 + else 32 + end + module DFHack + def self.memory_read_int64(addr) + (memory_read_int32(addr) & 0xffffffff) + (memory_read_int32(addr+4) << 32) + end + def self.memory_write_int64(addr, v) + memory_write_int32(addr, v & 0xffffffff) ; memory_write_int32(addr+4, v>>32) + end + if $sizeof_ptr == 64 + def self.memory_read_ptr(addr) + memory_read_int64(addr) & 0xffffffff_ffffffff + end + def self.memory_write_ptr(addr, v) + memory_write_int64(addr, v) + end + else + def self.memory_read_ptr(addr) + memory_read_int32(addr) & 0xffffffff + end + def self.memory_write_ptr(addr, v) + memory_write_int32(addr, v) + end + end + module MemHack INSPECT_SIZE_LIMIT=16384 class MemStruct @@ -62,6 +89,8 @@ module DFHack case tglen when 1; StlVector8.new(tg) when 2; StlVector16.new(tg) + when 4; StlVector32.new(tg) + when 8; StlVector64.new(tg) else StlVector32.new(tg) end end @@ -109,7 +138,7 @@ module DFHack def cpp_new(init=nil) ptr = DFHack.malloc(_sizeof) if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname) - DFHack.memory_write_int32(ptr, vt) + DFHack.memory_write_ptr(ptr, vt) # TODO call constructor end o = new._at(ptr) @@ -207,10 +236,10 @@ module DFHack def _get v = case @_bits + when 64; DFHack.memory_read_int64(@_memaddr) when 32; DFHack.memory_read_int32(@_memaddr) when 16; DFHack.memory_read_int16(@_memaddr) when 8; DFHack.memory_read_int8( @_memaddr) - when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32) end v &= (1 << @_bits) - 1 if not @_signed v = @_enum.sym(v) if @_enum @@ -220,10 +249,10 @@ module DFHack def _set(v) v = @_enum.int(v) if @_enum case @_bits + when 64; DFHack.memory_write_int64(@_memaddr, v) when 32; DFHack.memory_write_int32(@_memaddr, v) when 16; DFHack.memory_write_int16(@_memaddr, v) when 8; DFHack.memory_write_int8( @_memaddr, v) - when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32) end end @@ -299,11 +328,11 @@ module DFHack end def _getp - DFHack.memory_read_int32(@_memaddr) & 0xffffffff + DFHack.memory_read_ptr(@_memaddr) end def _setp(v) - DFHack.memory_write_int32(@_memaddr, v) + DFHack.memory_write_ptr(@_memaddr, v) end def _get @@ -316,8 +345,8 @@ module DFHack # XXX shaky... def _set(v) case v - when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) - when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) + when Pointer; DFHack.memory_write_ptr(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_ptr(@_memaddr, v._memaddr) when Integer if @_tg and @_tg.kind_of?(MemHack::Number) if _getp == 0 @@ -325,9 +354,9 @@ module DFHack end @_tg._at(_getp)._set(v) else - DFHack.memory_write_int32(@_memaddr, v) + DFHack.memory_write_ptr(@_memaddr, v) end - when nil; DFHack.memory_write_int32(@_memaddr, 0) + when nil; DFHack.memory_write_ptr(@_memaddr, 0) else @_tg._at(_getp)._set(v) end end @@ -353,7 +382,7 @@ module DFHack def _getp(i=0) delta = (i != 0 ? i*@_tglen : 0) - (DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta + DFHack.memory_read_ptr(@_memaddr) + delta end def _get @@ -364,10 +393,10 @@ module DFHack def _set(v) case v - when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) - when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) - when Integer; DFHack.memory_write_int32(@_memaddr, v) - when nil; DFHack.memory_write_int32(@_memaddr, 0) + when Pointer; DFHack.memory_write_ptr(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_ptr(@_memaddr, v._memaddr) + when Integer; DFHack.memory_write_ptr(@_memaddr, v) + when nil; DFHack.memory_write_ptr(@_memaddr, 0) else raise "cannot PointerAry._set(#{v.inspect})" end end @@ -557,6 +586,20 @@ module DFHack end end end + class StlVector64 < StlVector32 + def length + DFHack.memory_vector64_length(@_memaddr) + end + def valueptr_at(idx) + DFHack.memory_vector64_ptrat(@_memaddr, idx) + end + def insert_at(idx, val) + DFHack.memory_vector64_insertat(@_memaddr, idx, val) + end + def delete_at(idx) + DFHack.memory_vector64_deleteat(@_memaddr, idx) + end + end class StlVector16 < StlVector32 def length DFHack.memory_vector16_length(@_memaddr) @@ -733,8 +776,8 @@ module DFHack @_tg = tg end - field(:_ptr, 0) { number 32, false } - field(:_length, 4) { number 16, false } + field(:_ptr, 0) { number $sizeof_ptr, false } + field(:_length, $sizeof_ptr/8) { number 16, false } def length ; _length ; end def size ; _length ; end @@ -769,8 +812,8 @@ module DFHack end field(:_ptr, 0) { pointer } - field(:_prev, 4) { pointer } - field(:_next, 8) { pointer } + field(:_prev, $sizeof_ptr/8) { pointer } + field(:_next, 2*$sizeof_ptr/8) { pointer } def item # With the current xml structure, currently _tg designate @@ -946,7 +989,7 @@ module DFHack def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0, a5=0) this = obj._memaddr vt = df.get_vtable_ptr(this) - fptr = df.memory_read_int32(vt + voff) & 0xffffffff + fptr = df.memory_read_ptr(vt + voff) vmethod_do_call(this, fptr, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3), vmethod_arg(a4), vmethod_arg(a5)) end diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index adc451a19..7133dbed8 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -5,9 +5,11 @@ #include "PluginManager.h" #include "VersionInfo.h" #include "MemAccess.h" - #include "DataDefs.h" + +#include "modules/Gui.h" #include "df/global_objects.h" +#include "df/unit.h" #include "tinythread.h" @@ -214,15 +216,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (!onupdate_active) return CR_OK; - if (df::global::cur_year && (*df::global::cur_year < onupdate_minyear)) + if (df::global::cur_year && *df::global::cur_year < onupdate_minyear) return CR_OK; if (df::global::cur_year_tick && onupdate_minyeartick >= 0 && - (*df::global::cur_year == onupdate_minyear && - *df::global::cur_year_tick < onupdate_minyeartick)) + *df::global::cur_year_tick < onupdate_minyeartick) return CR_OK; if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && - (*df::global::cur_year == onupdate_minyear && - *df::global::cur_year_tick_advmode < onupdate_minyeartickadv)) + *df::global::cur_year_tick_advmode < onupdate_minyeartickadv) return CR_OK; return plugin_eval_ruby(out, "DFHack.onupdate"); @@ -236,6 +236,7 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch std::string cmd = "DFHack.onstatechange "; switch (e) { #define SCASE(s) case SC_ ## s : cmd += ":" # s ; break + case SC_UNKNOWN : return CR_OK; SCASE(WORLD_LOADED); SCASE(WORLD_UNLOADED); SCASE(MAP_LOADED); @@ -281,19 +282,16 @@ static command_result df_rubyeval(color_ostream &out, std::vector // - ruby.h with gcc -m32 on linux 64 is broken // so we dynamically load libruby with dlopen/LoadLibrary // lib path is hardcoded here, and by default downloaded by cmake -// this code should work with ruby1.9, but ruby1.9 doesn't like running -// in a dedicated non-main thread, so use ruby1.8 binaries only for now -// these ruby definitions are invalid for windows 64bit (need long long) -typedef unsigned long VALUE; -typedef unsigned long ID; +typedef uintptr_t VALUE; +typedef uintptr_t ID; -#define Qfalse ((VALUE)0) -#define Qtrue ((VALUE)2) -#define Qnil ((VALUE)4) +static VALUE Qfalse = 0; +static VALUE Qtrue = 2; +static VALUE Qnil = 4; -#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1)) -#define FIX2INT(i) (((long)i) >> 1) +#define INT2FIX(i) ((VALUE)((((intptr_t)i) << 1) | 1)) +#define FIX2INT(i) (((intptr_t)i) >> 1) #define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func) void (*ruby_init_stack)(VALUE*); @@ -301,7 +299,7 @@ void (*ruby_sysinit)(int *, const char ***); void (*ruby_init)(void); void (*ruby_init_loadpath)(void); void (*ruby_script)(const char*); -void (*ruby_finalize)(void); +int (*ruby_cleanup)(int); ID (*rb_intern)(const char*); VALUE (*rb_funcall)(VALUE, ID, int, ...); VALUE (*rb_define_module)(const char*); @@ -313,9 +311,9 @@ VALUE (*rb_eval_string_protect)(const char*, int*); VALUE (*rb_ary_shift)(VALUE); VALUE (*rb_float_new)(double); double (*rb_num2dbl)(VALUE); -VALUE (*rb_int2inum)(long); -VALUE (*rb_uint2inum)(unsigned long); -unsigned long (*rb_num2ulong)(VALUE); +VALUE (*rb_int2inum)(intptr_t); // XXX check on win64 long vs intptr_t +VALUE (*rb_uint2inum)(uintptr_t); +uintptr_t (*rb_num2ulong)(VALUE); // end of rip(ruby.h) DFHack::DFLibrary *libruby_handle; @@ -323,29 +321,40 @@ DFHack::DFLibrary *libruby_handle; // load the ruby library, initialize function pointers static int df_loadruby(void) { - const char *libpath = + const char *libpaths[] = { #if defined(WIN32) - "./libruby.dll"; + "./libruby.dll", #elif defined(__APPLE__) - "hack/libruby.dylib"; + "hack/libruby.dylib", + "/System/Library/Frameworks/Ruby.framework/Ruby", #else - "hack/libruby.so"; + "hack/libruby.so", + "libruby.so", #endif + NULL + }; + + for (const char **path = libpaths; *path; path++) { + if ((libruby_handle = OpenPlugin(*path))) + break; + else + fprintf(stderr, "ruby: warning: Failed to load %s\n", *path); + } - libruby_handle = OpenPlugin(libpath); if (!libruby_handle) { - fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath); + Core::printerr("Cannot initialize ruby plugin: failed to load ruby library\n"); return 0; } // ruby_sysinit is optional (ruby1.9 only) ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit"); -#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0 +#define rbloadsyma(s,a) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #a))) return 0 +#define rbloadsym(s) rbloadsyma(s,s) rbloadsym(ruby_init_stack); rbloadsym(ruby_init); rbloadsym(ruby_init_loadpath); rbloadsym(ruby_script); - rbloadsym(ruby_finalize); + rbloadsym(ruby_cleanup); rbloadsym(rb_intern); rbloadsym(rb_funcall); rbloadsym(rb_define_module); @@ -355,12 +364,21 @@ static int df_loadruby(void) rbloadsym(rb_string_value_ptr); rbloadsym(rb_eval_string_protect); rbloadsym(rb_ary_shift); - rbloadsym(rb_float_new); rbloadsym(rb_num2dbl); rbloadsym(rb_int2inum); +#if defined(_WIN64) + rbloadsyma(rb_uint2inum, rb_ull2inum); + rbloadsyma(rb_num2ulong, rb_num2ull); +#else rbloadsym(rb_uint2inum); rbloadsym(rb_num2ulong); +#endif + #undef rbloadsym + // rb_float_new_in_heap in ruby 2 + if (!((rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new"))) || + (rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new_in_heap"))))) + return 0; return 1; } @@ -435,6 +453,11 @@ static void df_rubythread(void *p) r_result = CR_OK; r_type = RB_IDLE; + // initialize ruby constants (may depend on libruby compilation flags/version) + Qnil = rb_eval_string_protect("nil", &state); + Qtrue = rb_eval_string_protect("true", &state); + Qfalse = rb_eval_string_protect("false", &state); + // load the default ruby-level definitions in the background state=0; rb_eval_string_protect("require './hack/ruby/ruby'", &state); @@ -456,7 +479,7 @@ static void df_rubythread(void *p) case RB_DIE: running = 0; - ruby_finalize(); + ruby_cleanup(0); break; case RB_EVAL: @@ -572,7 +595,7 @@ static VALUE rb_dfget_global_address(VALUE self, VALUE name) static VALUE rb_dfget_vtable(VALUE self, VALUE name) { - return rb_uint2inum((uint32_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name))); + return rb_uint2inum((uintptr_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name))); } // read the c++ class name from a vtable pointer, inspired from doReadClassName @@ -580,14 +603,22 @@ static VALUE rb_dfget_vtable(VALUE self, VALUE name) static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) { char *ptr = (char*)rb_num2ulong(vptr); -#ifdef WIN32 +#if defined(_WIN64) + // win64 + char *rtti = *(char**)(ptr - 0x8); + char *typeinfo = (char*)Core::getInstance().p->getBase() + *(uint32_t*)(rtti + 0xC); + // skip the .?AV, trim @@ from end + return rb_str_new(typeinfo+0x14, strlen(typeinfo+0x14)-2); +#elif defined(WIN32) + // win32 char *rtti = *(char**)(ptr - 0x4); char *typeinfo = *(char**)(rtti + 0xC); // skip the .?AV, trim @@ from end return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2); #else - char *typeinfo = *(char**)(ptr - 0x4); - char *typestring = *(char**)(typeinfo + 0x4); + // linux/osx 32/64 + char *typeinfo = *(char**)(ptr - sizeof(void*)); + char *typestring = *(char**)(typeinfo + sizeof(void*)); while (*typestring >= '0' && *typestring <= '9') typestring++; return rb_str_new(typestring, strlen(typestring)); @@ -596,15 +627,20 @@ static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) { - // actually, rb_dfmemory_read_int32 - return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr)); + return rb_uint2inum(*(uintptr_t*)rb_num2ulong(objptr)); +} + +static VALUE rb_dfget_selected_unit_id(VALUE self) +{ + df::unit *u = Gui::getAnyUnit(Core::getTopViewscreen()); + return rb_int2inum(u ? u->id : -1); } // run a dfhack command, as if typed from the dfhack console static VALUE rb_dfhack_run(VALUE self, VALUE cmd) { std::string s; - int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(cmd, rb_intern("bytesize"), 0)); s.assign(rb_string_value_ptr(&cmd), strlen); dfhack_run_queue->push_back(s); return Qtrue; @@ -622,7 +658,7 @@ static VALUE rb_dfmalloc(VALUE self, VALUE len) if (!ptr) return Qnil; memset(ptr, 0, FIX2INT(len)); - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dffree(VALUE self, VALUE ptr) @@ -666,7 +702,7 @@ static VALUE rb_dfmemory_read_double(VALUE self, VALUE addr) static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw) { // no stable api for raw.length between rb1.8/rb1.9 ... - int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(raw, rb_intern("bytesize"), 0)); memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen); @@ -732,7 +768,7 @@ static VALUE rb_dfmemory_check(VALUE self, VALUE addr) // memory write (tmp override page permissions, eg patch code) static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) { - int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(raw, rb_intern("bytesize"), 0)); bool ret; ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr), @@ -746,7 +782,7 @@ static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len) { void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len)); - return (ret == (void*)-1) ? Qnil : rb_uint2inum((uint32_t)ret); + return (ret == (void*)-1) ? Qnil : rb_uint2inum((uintptr_t)ret); } // free memory from pagealloc @@ -789,7 +825,7 @@ static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE pro static VALUE rb_dfmemory_stlstring_new(VALUE self) { std::string *ptr = new std::string; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_stlstring_delete(VALUE self, VALUE addr) { @@ -811,7 +847,7 @@ static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr) static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val) { std::string *s = (std::string*)rb_num2ulong(addr); - int strlen = FIX2INT(rb_funcall(val, rb_intern("length"), 0)); + int strlen = FIX2INT(rb_funcall(val, rb_intern("bytesize"), 0)); s->assign(rb_string_value_ptr(&val), strlen); return Qtrue; } @@ -821,7 +857,7 @@ static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val) static VALUE rb_dfmemory_vec_new(VALUE self) { std::vector *ptr = new std::vector; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_vec_delete(VALUE self, VALUE addr) { @@ -844,7 +880,7 @@ static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr) static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx) { std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx))); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); } static VALUE rb_dfmemory_vec8_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) { @@ -868,7 +904,7 @@ static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr) static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx) { std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx))); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); } static VALUE rb_dfmemory_vec16_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) { @@ -892,7 +928,7 @@ static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr) static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx) { std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx))); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); } static VALUE rb_dfmemory_vec32_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) { @@ -907,11 +943,35 @@ static VALUE rb_dfmemory_vec32_deleteat(VALUE self, VALUE addr, VALUE idx) return Qtrue; } +// vector +static VALUE rb_dfmemory_vec64_length(VALUE self, VALUE addr) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + return rb_uint2inum(v->size()); +} +static VALUE rb_dfmemory_vec64_ptrat(VALUE self, VALUE addr, VALUE idx) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); +} +static VALUE rb_dfmemory_vec64_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val)); + return Qtrue; +} +static VALUE rb_dfmemory_vec64_deleteat(VALUE self, VALUE addr, VALUE idx) +{ + std::vector *v = (std::vector*)rb_num2ulong(addr); + v->erase(v->begin()+FIX2INT(idx)); + return Qtrue; +} + // vector static VALUE rb_dfmemory_vecbool_new(VALUE self) { std::vector *ptr = new std::vector; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr) { @@ -983,7 +1043,7 @@ static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE v static VALUE rb_dfmemory_set_new(VALUE self) { std::set *ptr = new std::set; - return rb_uint2inum((uint32_t)ptr); + return rb_uint2inum((uintptr_t)ptr); } static VALUE rb_dfmemory_set_delete(VALUE self, VALUE set) @@ -1023,9 +1083,9 @@ static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set) /* call an arbitrary object virtual method */ -#ifdef WIN32 -__declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) +#if defined(_WIN32) && !defined(_WIN64) +__declspec(naked) static intptr_t raw_vcall(void *that, void *fptr, uintptr_t a0, + uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) { // __thiscall requires that the callee cleans up the stack // here we dont know how many arguments it will take, so @@ -1051,10 +1111,11 @@ __declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, } } #else -static int raw_vcall(void *that, void *fptr, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) +static intptr_t raw_vcall(void *that, void *fptr, uintptr_t a0, + uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) { - int (*t_fptr)(void *me, int, int, int, int, int, int); + intptr_t (*t_fptr)(void *me, uintptr_t, uintptr_t, uintptr_t, + uintptr_t, uintptr_t, uintptr_t); t_fptr = (decltype(t_fptr))fptr; return t_fptr(that, a0, a1, a2, a3, a4, a5); } @@ -1086,6 +1147,7 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1); rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1); + rb_define_singleton_method(rb_cDFHack, "get_selected_unit_id", RUBY_METHOD_FUNC(rb_dfget_selected_unit_id), 0); rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); rb_define_singleton_method(rb_cDFHack, "print_color", RUBY_METHOD_FUNC(rb_dfprint_color), 2); @@ -1134,6 +1196,10 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2); rb_define_singleton_method(rb_cDFHack, "memory_vector32_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insertat), 3); rb_define_singleton_method(rb_cDFHack, "memory_vector32_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_deleteat), 2); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_length", RUBY_METHOD_FUNC(rb_dfmemory_vec64_length), 1); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_ptrat), 2); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_insertat), 3); + rb_define_singleton_method(rb_cDFHack, "memory_vector64_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_deleteat), 2); rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_new", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_new), 0); rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 1); rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_init", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_init), 1); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 850ca0912..c696c23e5 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -2,14 +2,24 @@ module Kernel def puts(*a) a.flatten.each { |l| - DFHack.print_str(l.to_s.chomp + "\n") + # XXX looks like print_str crashes with strings longer than 4096... maybe not nullterminated ? + # this workaround fixes it + s = l.to_s.chomp + "\n" + while s.length > 0 + DFHack.print_str(s[0, 4000]) + s[0, 4000] = '' + end } nil end def puts_err(*a) a.flatten.each { |l| - DFHack.print_err(l.to_s.chomp + "\n") + s = l.to_s.chomp + "\n" + while s.length > 0 + DFHack.print_err(s[0, 4000]) + s[0, 4000] = '' + end } nil end @@ -99,13 +109,14 @@ module DFHack def onupdate @onupdate_list ||= [] - y = cur_year + y = yt = 0 + y = cur_year rescue 0 ytmax = TICKS_PER_YEAR if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode) yt = cur_year_tick_advmode ytmax *= 144 else - yt = cur_year_tick + yt = cur_year_tick rescue 0 end @onupdate_list.each { |o| diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index a9dd05437..9268dcb9e 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -15,15 +15,15 @@ module DFHack x, y, z = x.x, x.y, x.z if x.respond_to?(:x) # compute screen 'map' size (tiles) - menuwidth = ui_menu_width + menuwidth = ui_menu_width[0] # ui_menu_width shows only the 'tab' status - menuwidth = 1 if menuwidth == 2 and ui_area_map_width == 2 and cursor.x != -30000 + menuwidth = 1 if menuwidth == 2 and ui_menu_width[1] == 2 and cursor.x != -30000 menuwidth = 2 if menuwidth == 3 and cursor.x != -30000 w_w = gps.dimx - 2 w_h = gps.dimy - 2 case menuwidth when 1; w_w -= 55 - when 2; w_w -= (ui_area_map_width == 2 ? 24 : 31) + when 2; w_w -= (ui_menu_width[1] == 2 ? 24 : 31) end # center view diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 0ce02ae71..3aaaf7653 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -6,43 +6,7 @@ module DFHack # with an argument that respond to x/y/z (eg cursor), find first unit at this position def unit_find(what=:selected, y=nil, z=nil) if what == :selected - case curview._rtti_classname - when :viewscreen_itemst - ref = curview.entry_ref[curview.cursor_pos] - ref.unit_tg if ref.kind_of?(GeneralRefUnit) - when :viewscreen_unitlistst - v = curview - v.units[v.page][v.cursor_pos[v.page]] - when :viewscreen_petst - v = curview - case v.mode - when :List - v.animal[v.cursor].unit if !v.is_vermin[v.cursor] - when :SelectTrainer - v.trainer_unit[v.trainer_cursor] - end - when :viewscreen_dwarfmodest - case ui.main.mode - when :ViewUnits - # nobody selected => idx == 0 - v = world.units.active[ui_selected_unit] - v if v and v.pos.z == cursor.z - when :LookAround - k = ui_look_list.items[ui_look_cursor] - k.unit if k.type == :Unit - else - ui.follow_unit_tg if ui.follow_unit != -1 - end - when :viewscreen_dungeonmodest - case ui_advmode.menu - when :Default - world.units.active[0] - else - unit_find(cursor) # XXX - end - when :viewscreen_dungeon_monsterstatusst - curview.unit - end + return world.units.all.binsearch(df.get_selected_unit_id) elsif what.kind_of?(Integer) # search by id return world.units.all.binsearch(what) if not z diff --git a/plugins/ruby/win32/.gitignore b/plugins/ruby/win32/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/win32/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/ruby/win64/.gitignore b/plugins/ruby/win64/.gitignore new file mode 100644 index 000000000..ef44e3942 --- /dev/null +++ b/plugins/ruby/win64/.gitignore @@ -0,0 +1 @@ +libruby* diff --git a/plugins/search.cpp b/plugins/search.cpp index 0b7556552..b43daa942 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1,40 +1,42 @@ -#include -#include -#include -#include - -#include - +#include "MiscUtils.h" +#include "VTableInterpose.h" #include "uicommon.h" +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Screen.h" +#include "modules/Translation.h" +#include "modules/Units.h" + #include "df/creature_raw.h" -#include "df/ui_look_list.h" -#include "df/viewscreen_announcelistst.h" -#include "df/viewscreen_petst.h" -#include "df/viewscreen_storesst.h" -#include "df/viewscreen_layer_stockpilest.h" -#include "df/viewscreen_layer_militaryst.h" -#include "df/viewscreen_layer_noblelistst.h" -#include "df/viewscreen_workshop_profilest.h" -#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" -#include "df/viewscreen_tradegoodsst.h" -#include "df/viewscreen_unitlistst.h" -#include "df/viewscreen_buildinglistst.h" -#include "df/viewscreen_joblistst.h" +#include "df/global_objects.h" #include "df/historical_figure.h" -#include "df/viewscreen_locationsst.h" #include "df/interface_key.h" #include "df/interfacest.h" -#include "df/layer_object_listst.h" #include "df/job.h" +#include "df/layer_object_listst.h" +#include "df/misc_trait_type.h" #include "df/report.h" -#include "modules/Job.h" -#include "df/global_objects.h" -#include "df/viewscreen_dwarfmodest.h" -#include "modules/Gui.h" +#include "df/ui_look_list.h" #include "df/unit.h" -#include "df/misc_trait_type.h" #include "df/unit_misc_trait.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_joblistst.h" +#include "df/viewscreen_kitchenprefst.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_stone_restrictionst.h" +#include "df/viewscreen_locationsst.h" +#include "df/viewscreen_petst.h" +#include "df/viewscreen_storesst.h" +#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" +#include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_workshop_profilest.h" using namespace std; using std::set; @@ -55,6 +57,7 @@ REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_look_cursor); REQUIRE_GLOBAL(ui_look_list); +REQUIRE_GLOBAL(world); /* Search Plugin @@ -957,14 +960,14 @@ class animal_trainer_search : public animal_trainer_search_base public: void render() const { - Screen::paintTile(Screen::Pen(186, 8, 0), 14, 2); - Screen::paintTile(Screen::Pen(186, 8, 0), gps->dimx - 14, 2); - Screen::paintTile(Screen::Pen(201, 8, 0), 14, 1); - Screen::paintTile(Screen::Pen(187, 8, 0), gps->dimx - 14, 1); + Screen::paintTile(Screen::Pen('\xBA', 8, 0), 14, 2); + Screen::paintTile(Screen::Pen('\xBA', 8, 0), gps->dimx - 14, 2); + Screen::paintTile(Screen::Pen('\xC9', 8, 0), 14, 1); + Screen::paintTile(Screen::Pen('\xBB', 8, 0), gps->dimx - 14, 1); for (int x = 15; x <= gps->dimx - 15; ++x) { - Screen::paintTile(Screen::Pen(205, 8, 0), x, 1); - Screen::paintTile(Screen::Pen(0, 0, 0), x, 2); + Screen::paintTile(Screen::Pen('\xCD', 8, 0), x, 1); + Screen::paintTile(Screen::Pen('\x00', 0, 0), x, 2); } print_search_option(16, 2); } @@ -1154,19 +1157,26 @@ private: bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + if (input->count(interface_key::STANDARDSCROLL_LEFT) || + input->count(interface_key::STANDARDSCROLL_RIGHT) || (!in_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!in_entry_mode()) { // Changing screens, reset search + int32_t *cursor_pos = get_viewscreen_cursor(); + if (cursor_pos && *cursor_pos < 0) + *cursor_pos = 0; clear_search(); reset_all(); + return false; } else - input->clear(); // Ignore cursor keys when typing - - return false; + { + // Ignore cursor keys when typing + input->erase(interface_key::STANDARDSCROLL_LEFT); + input->erase(interface_key::STANDARDSCROLL_RIGHT); + } } return true; @@ -1452,12 +1462,12 @@ public: // About to make an assignment, so restore original list (it will be changed by the game) int32_t *cursor = get_viewscreen_cursor(); auto list = get_primary_list(); - if (*cursor >= list->size()) + if (size_t(*cursor) >= list->size()) return false; df::unit *selected_unit = list->at(*cursor); clear_search(); - for (*cursor = 0; *cursor < list->size(); (*cursor)++) + for (*cursor = 0; size_t(*cursor) < list->size(); (*cursor)++) { if (list->at(*cursor) == selected_unit) break; @@ -1480,8 +1490,6 @@ IMPLEMENT_HOOKS_PRIO(df::viewscreen_layer_militaryst, military_search, 100); // // START: Room list search // -static map< df::building_type, vector > room_quality_names; -static int32_t room_value_bounds[] = {1, 100, 250, 500, 1000, 1500, 2500, 10000}; typedef search_twocolumn_modifiable roomlist_search_base; class roomlist_search : public roomlist_search_base { @@ -1502,33 +1510,21 @@ private: { if (!bld) return ""; - bool is_ownable_room = (bld->is_room && room_quality_names.find(bld->getType()) != room_quality_names.end()); string desc; desc.reserve(100); if (bld->owner) desc += get_unit_description(bld->owner); - else if (is_ownable_room) - desc += "no owner"; desc += "."; - if (is_ownable_room) + string room_desc = Buildings::getRoomDescription(bld, nullptr); + desc += room_desc; + if (room_desc.empty()) { - int32_t value = bld->getRoomValue(NULL); - vector *names = &room_quality_names[bld->getType()]; - string *room_name = &names->at(0); - for (int i = 1; i < 8; i++) - { - if (room_value_bounds[i] > value) - break; - room_name = &names->at(i); - } + if (!bld->owner) + desc += "no owner"; - desc += *room_name; - } - else - { string name; bld->getName(&name); if (!name.empty()) @@ -1671,7 +1667,7 @@ public: void render() const { - print_search_option(2, 23); + print_search_option(2, gps->dimy - 5); } vector *get_primary_list() @@ -2123,6 +2119,216 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); // END: Location occupation assignment search // +// +// START: Kitchen preferences search +// + +typedef search_multicolumn_modifiable kitchen_pref_search_base; +class kitchen_pref_search : public kitchen_pref_search_base +{ +public: + + string get_element_description(string *s) const override + { + return s ? *s : ""; + } + + void render() const override + { + print_search_option(40, gps->dimy - 2); + } + + int32_t *get_viewscreen_cursor() override + { + return &viewscreen->cursor; + } + + vector *get_primary_list() override + { + return &viewscreen->item_str[viewscreen->page]; + } + + bool should_check_input(set *input) override + { + if (input->count(interface_key::CHANGETAB) || input->count(interface_key::SEC_CHANGETAB)) + { + // Restore original list + clear_search(); + reset_all(); + } + + return true; + } + + +#define KITCHEN_VECTORS \ + KVEC(df::item_type, item_type); \ + KVEC(int16_t, item_subtype); \ + KVEC(int16_t, mat_type); \ + KVEC(int32_t, mat_index); \ + KVEC(int32_t, count); \ + KVEC(df::kitchen_pref_flag, forbidden); \ + KVEC(df::kitchen_pref_flag, possible) + + + virtual void do_post_init() + { + kitchen_pref_search_base::do_post_init(); + #define KVEC(type, name) name = &viewscreen->name[viewscreen->page] + KITCHEN_VECTORS; + #undef KVEC + } + + void save_secondary_values() + { + #define KVEC(type, name) name##_s = *name + KITCHEN_VECTORS; + #undef KVEC + } + + void reset_secondary_viewscreen_vectors() + { + #define KVEC(type, name) name = nullptr + KITCHEN_VECTORS; + #undef KVEC + } + + virtual void update_saved_secondary_list_item(size_t i, size_t j) + { + #define KVEC(type, name) name##_s[i] = (*name)[j]; + KITCHEN_VECTORS; + #undef KVEC + } + + void clear_secondary_viewscreen_vectors() + { + #define KVEC(type, name) name->clear() + KITCHEN_VECTORS; + #undef KVEC + } + + void add_to_filtered_secondary_lists(size_t i) + { + #define KVEC(type, name) name->push_back(name##_s[i]) + KITCHEN_VECTORS; + #undef KVEC + } + + void clear_secondary_saved_lists() + { + #define KVEC(type, name) name##_s.clear() + KITCHEN_VECTORS; + #undef KVEC + } + + void restore_secondary_values() + { + #define KVEC(type, name) *name = name##_s + KITCHEN_VECTORS; + #undef KVEC + } + + #define KVEC(type, name) vector *name, name##_s + KITCHEN_VECTORS; + #undef KVEC +#undef KITCHEN_VECTORS +}; + +IMPLEMENT_HOOKS(df::viewscreen_kitchenprefst, kitchen_pref_search); + +// +// END: Kitchen preferences search +// + + +// +// START: Stone status screen search +// +typedef layered_search stone_search_layer; +class stone_search : public search_twocolumn_modifiable +{ + // bool in_update = false; +public: + void render() const override + { + print_search_option(21, 23); + } + + vector *get_primary_list() override + { + return &viewscreen->stone_type[viewscreen->type_tab]; + } + + vector *get_secondary_list() override + { + return &viewscreen->stone_economic[viewscreen->type_tab]; + } + + string get_element_description(int32_t stone_type) const override + { + auto iraw = vector_get(world->raws.inorganics, stone_type); + if (!iraw) + return ""; + return iraw->material.stone_name + " " + iraw->material.state_name[0]; + } + + bool should_check_input(set *input) override + { + // if (in_update) + // return false; + + if (input->count(interface_key::CHANGETAB)) + { + // Restore original list + clear_search(); + reset_all(); + } + + return true; + } + + // virtual void do_post_input_feed() override + // { + // auto *list1 = get_primary_list(); + // auto *list2 = get_secondary_list(); + // bool appended = false; + // if (list1->empty()) + // { + // // Clear uses + // auto *use_list = virtual_cast(viewscreen->layer_objects[4]); + // if (use_list) + // use_list->num_entries = 0; + // return; + // } + // else if (list1->size() == 1) + // { + // list1->push_back(list1->back()); + // list2->push_back(list2->back()); + // appended = true; + // } + + // in_update = true; + // Core::printerr("updating\n"); + // viewscreen->feed_key(interface_key::STANDARDSCROLL_DOWN); + // viewscreen->feed_key(interface_key::STANDARDSCROLL_UP); + // Core::printerr("updating done\n"); + // in_update = false; + + // if (appended) + // { + // list1->pop_back(); + // list2->pop_back(); + // } + // } +}; + +IMPLEMENT_HOOKS(df::viewscreen_layer_stone_restrictionst, stone_search); + +// +// END: Stone status screen search +// + + #define SEARCH_HOOKS \ HOOK_ACTION(unitlist_search_hook) \ HOOK_ACTION(roomlist_search_hook) \ @@ -2142,7 +2348,10 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); HOOK_ACTION(stockpile_search_hook) \ HOOK_ACTION(room_assign_search_hook) \ HOOK_ACTION(noble_suggest_search_hook) \ - HOOK_ACTION(location_assign_occupation_search_hook) + HOOK_ACTION(location_assign_occupation_search_hook) \ + HOOK_ACTION(kitchen_pref_search_hook) \ + HOOK_ACTION(stone_search_hook) \ + DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { @@ -2161,26 +2370,13 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) is_enabled = enable; } +#undef HOOK_ACTION return CR_OK; } DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { -#undef HOOK_ACTION - - const string a[] = {"Meager Quarters", "Modest Quarters", "Quarters", "Decent Quarters", "Fine Quarters", "Great Bedroom", "Grand Bedroom", "Royal Bedroom"}; - room_quality_names[df::building_type::Bed] = vector(a, a + 8); - - const string b[] = {"Meager Dining Room", "Modest Dining Room", "Dining Room", "Decent Dining Room", "Fine Dining Room", "Great Dining Room", "Grand Dining Room", "Royal Dining Room"}; - room_quality_names[df::building_type::Table] = vector(b, b + 8); - - const string c[] = {"Meager Office", "Modest Office", "Office", "Decent Office", "Splendid Office", "Throne Room", "Opulent Throne Room", "Royal Throne Room"}; - room_quality_names[df::building_type::Chair] = vector(c, c + 8); - - const string d[] = {"Grave", "Servants Burial Chamber", "Burial Chamber", "Tomb", "Fine Tomb", "Mausoleum", "Grand Mausoleum", "Royal Mausoleum"}; - room_quality_names[df::building_type::Coffin] = vector(d, d + 8); - return CR_OK; } diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index b7767e046..5f254a955 100755 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -9,7 +9,7 @@ #include "Export.h" #include "PluginManager.h" #include "modules/World.h" -#include "modules/kitchen.h" +#include "modules/Kitchen.h" #include "VersionInfo.h" #include "df/world.h" #include "df/plant_raw.h" diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 05e9a67ad..9e3fa7ff7 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -43,7 +43,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) CoreSuspender suspend; bool found = false; - for (df::job_list_link *cur = world->job_list.next; cur != NULL; cur = cur->next) + for (df::job_list_link *cur = world->jobs.list.next; cur != NULL; cur = cur->next) { df::job *job = cur->item; if ((job->job_type < job_type::StrangeMoodCrafter) || (job->job_type > job_type::StrangeMoodMechanics)) @@ -98,6 +98,8 @@ command_result df_showmood (color_ostream &out, vector & parameters) case mood_type::Possessed: out.print("possessed"); break; + default: + break; } out.print(" with intent to "); switch (job->job_type) @@ -275,7 +277,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) int count_got = 0; for (size_t j = 0; j < job->items.size(); j++) { - if(job->items[j]->job_item_idx == i) + if(job->items[j]->job_item_idx == int32_t(i)) { if (item->item_type == item_type::BAR || item->item_type == item_type::CLOTH) count_got += job->items[j]->item->getTotalDimension(); diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 4ad8fa341..0d29e8c48 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -22,40 +22,41 @@ #include #include -#include "df/graphic.h" +#include "df/building_drawbuffer.h" #include "df/building_siegeenginest.h" -#include "df/builtin_mats.h" -#include "df/world.h" +#include "df/building_stockpilest.h" #include "df/buildings_other_id.h" -#include "df/job.h" -#include "df/building_drawbuffer.h" -#include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" -#include "df/proj_itemst.h" -#include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" -#include "df/physical_attribute_type.h" -#include "df/creature_raw.h" +#include "df/builtin_mats.h" #include "df/caste_raw.h" #include "df/caste_raw_flags.h" -#include "df/identity.h" +#include "df/creature_raw.h" +#include "df/flow_info.h" +#include "df/flow_type.h" #include "df/game_mode.h" -#include "df/unit_misc_trait.h" -#include "df/job.h" -#include "df/job_item.h" +#include "df/graphic.h" +#include "df/identity.h" +#include "df/invasion_info.h" #include "df/item_actual.h" #include "df/items_other_id.h" -#include "df/building_stockpilest.h" +#include "df/job.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/material.h" +#include "df/physical_attribute_type.h" +#include "df/proj_itemst.h" +#include "df/report.h" #include "df/stockpile_links.h" -#include "df/workshop_profile.h" #include "df/strain_type.h" -#include "df/material.h" -#include "df/flow_type.h" -#include "df/invasion_info.h" +#include "df/ui.h" +#include "df/ui_build_selector.h" +#include "df/unit.h" +#include "df/unit_misc_trait.h" +#include "df/unit_relationship_type.h" +#include "df/unit_skill.h" +#include "df/unit_soul.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/workshop_profile.h" +#include "df/world.h" #include "MiscUtils.h" @@ -1120,7 +1121,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: if (is_in_range(engine->building_rect, tile_pos)) continue; - Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); + Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y, true); if (!cur_tile.valid()) continue; @@ -1158,7 +1159,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; - Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y); + Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y, true); } } } @@ -1193,7 +1194,7 @@ struct UnitPath { { if (unit->flags1.bits.rider) { - auto mount = df::unit::find(unit->relations.rider_mount_id); + auto mount = df::unit::find(unit->relationship_ids[df::unit_relationship_type::RiderMount]); if (mount) { diff --git a/plugins/skeleton/CMakeLists.txt b/plugins/skeleton/CMakeLists.txt index 794f4a5d8..69fac825a 100644 --- a/plugins/skeleton/CMakeLists.txt +++ b/plugins/skeleton/CMakeLists.txt @@ -14,20 +14,23 @@ LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) # option to use a thread for no particular reason OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -#linux IF(UNIX) - add_definitions(-DLINUX_BUILD) + IF(APPLE) + SET(PROJECT_LIBS + # add any extra mac libraries here + ${PROJECT_LIBS} + ) + ELSE() + SET(PROJECT_LIBS + # add any extra linux libraries here + ${PROJECT_LIBS} + ) + ENDIF() +ELSE() SET(PROJECT_LIBS - # add any extra linux libs here + # add any extra windows libraries here ${PROJECT_LIBS} ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra windows libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) +ENDIF() # this makes sure all the stuff is put in proper places and linked to dfhack DFHACK_PLUGIN(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index cc9455914..fbc575e36 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -1,12 +1,3 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -14,26 +5,37 @@ #include #include -#include -#include "df/graphic.h" -#include "df/building_workshopst.h" +#include "Console.h" +#include "Core.h" +#include "Export.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "TileTypes.h" +#include "VTableInterpose.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Screen.h" +#include "modules/World.h" + #include "df/building_def_workshopst.h" -#include "df/item_liquid_miscst.h" -#include "df/power_info.h" -#include "df/workshop_type.h" -#include "df/builtin_mats.h" -#include "df/world.h" +#include "df/building_drawbuffer.h" +#include "df/building_workshopst.h" #include "df/buildings_other_id.h" -#include "df/machine.h" +#include "df/builtin_mats.h" +#include "df/flow_info.h" +#include "df/graphic.h" +#include "df/item_liquid_miscst.h" #include "df/job.h" -#include "df/building_drawbuffer.h" +#include "df/machine.h" +#include "df/power_info.h" +#include "df/report.h" +#include "df/tile_designation.h" #include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" #include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" - -#include "MiscUtils.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/workshop_type.h" +#include "df/world.h" /* * This plugin implements a steam engine workshop. It activates diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index ff87bdddc..7cfc8ce0d 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -69,7 +69,7 @@ public: } else { // Gather orders when the bookkeeper starts updating stockpile records, // and enqueue them when the job is done. - for (df::job_list_link* link = &world->job_list; link != NULL; link = link->next) { + for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next) { if (link->item == NULL) continue; if (link->item->job_type == job_type::UpdateStockpileRecords) { found = true; diff --git a/plugins/stockpiles/CMakeLists.txt b/plugins/stockpiles/CMakeLists.txt index 713c3d6d1..3f4dcb28e 100644 --- a/plugins/stockpiles/CMakeLists.txt +++ b/plugins/stockpiles/CMakeLists.txt @@ -14,31 +14,10 @@ SET(PROJECT_SRCS ) SET(PROJECT_PROTOS -${CMAKE_CURRENT_SOURCE_DIR}/proto/stockpiles.proto + stockpiles ) -#Create new lists of what sources and headers protoc will output after we invoke it -STRING(REPLACE ".proto" ".pb.cc;" PROJECT_PROTO_SRCS ${PROJECT_PROTOS}) -STRING(REPLACE ".proto" ".pb.h;" PROJECT_PROTO_HDRS ${PROJECT_PROTOS}) - -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_HDRS} PROPERTIES GENERATED TRUE) -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_SRCS} PROPERTIES GENERATED TRUE) - -LIST(APPEND PROJECT_HDRS ${PROJECT_PROTO_HDRS}) -LIST(APPEND PROJECT_SRCS ${PROJECT_PROTO_SRCS}) - SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) -#Generate sources from our proto files and store them in the source tree -ADD_CUSTOM_COMMAND( -OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} -COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ ${PROJECT_PROTOS} -DEPENDS protoc-bin ${PROJECT_PROTOS} -) - -IF(WIN32) - DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua) -ELSE() - DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua) -ENDIF() +DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} PROTOBUFS ${PROJECT_PROTOS} LINK_LIBRARIES protobuf-lite lua) diff --git a/plugins/stockpiles/OrganicMatLookup.cpp b/plugins/stockpiles/OrganicMatLookup.cpp index ca9670a96..4f1076cd1 100644 --- a/plugins/stockpiles/OrganicMatLookup.cpp +++ b/plugins/stockpiles/OrganicMatLookup.cpp @@ -71,8 +71,8 @@ void OrganicMatLookup::food_build_map ( std::ostream &out ) df::world_raws &raws = world->raws; df::special_mat_table table = raws.mat_table; using df::enums::organic_mat_category::organic_mat_category; - df::enum_traits traits; - for ( int32_t mat_category = traits.first_item_value; mat_category <= traits.last_item_value; ++mat_category ) + using traits = df::enum_traits; + for ( int32_t mat_category = traits::first_item_value; mat_category <= traits::last_item_value; ++mat_category ) { for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i ) { diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index fa3d648ba..80e438701 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -214,7 +214,7 @@ void StockpileSerializer::unserialize_list_organic_mat ( FuncReadImport get_valu std::string token = get_value ( i ); int16_t idx = OrganicMatLookup::food_idx_by_token ( debug(), cat, token ); debug() << " organic_material " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) + if ( size_t(idx) >= pile_list->size() ) { debug() << "error organic mat index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -227,14 +227,14 @@ void StockpileSerializer::unserialize_list_organic_mat ( FuncReadImport get_valu void StockpileSerializer::serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) { using df::enums::item_type::item_type; - df::enum_traits type_traits; - debug() << "item_type size = " << list.size() << " size limit = " << type_traits.last_item_value << " typecasted: " << ( size_t ) type_traits.last_item_value << endl; - for ( size_t i = 0; i <= ( size_t ) type_traits.last_item_value; ++i ) + using type_traits = df::enum_traits; + debug() << "item_type size = " << list.size() << " size limit = " << type_traits::last_item_value << " typecasted: " << ( size_t ) type_traits::last_item_value << endl; + for ( size_t i = 0; i <= ( size_t ) type_traits::last_item_value; ++i ) { if ( list.at ( i ) ) { const item_type type = ( item_type ) ( ( df::enum_traits::base_type ) i ); - std::string r_type ( type_traits.key_table[i+1] ); + std::string r_type ( type_traits::key_table[i+1] ); if ( !is_allowed ( type ) ) continue; add_value ( r_type ); debug() << "item_type key_table[" << i+1 << "] type[" << ( int16_t ) type << "] is " << r_type <clear(); pile_list->resize ( 112, '\0' ); // TODO remove hardcoded list size value - for ( int i = 0; i < pile_list->size(); ++i ) + for ( size_t i = 0; i < pile_list->size(); ++i ) { pile_list->at ( i ) = is_allowed ( ( item_type::item_type ) i ) ? 0 : 1; } @@ -261,7 +261,7 @@ void StockpileSerializer::unserialize_list_item_type ( FuncItemAllowed is_allowe const item_type type = ( item_type ) idx; if ( !is_allowed ( type ) ) continue; debug() << " item_type " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) + if ( size_t(idx) >= pile_list->size() ) { debug() << "error item_type index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -296,7 +296,7 @@ void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_all std::set idx_set; pile_list->clear(); pile_list->resize ( world->raws.inorganics.size(), 0 ); - for ( int i = 0; i < pile_list->size(); ++i ) + for ( size_t i = 0; i < pile_list->size(); ++i ) { MaterialInfo mi ( 0, i ); pile_list->at ( i ) = is_allowed ( mi ) ? 0 : 1; @@ -308,7 +308,7 @@ void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_all mi.find ( token ); if ( !is_allowed ( mi ) ) continue; debug() << " material " << mi.index << " is " << token << endl; - if ( mi.index >= pile_list->size() ) + if ( size_t(mi.index) >= pile_list->size() ) { debug() << "error material index too large! idx[" << mi.index << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -321,12 +321,12 @@ void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_all void StockpileSerializer::serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] ) { using df::enums::item_quality::item_quality; - df::enum_traits quality_traits; + using quality_traits = df::enum_traits; for ( size_t i = 0; i < 7; ++i ) { if ( quality_list[i] ) { - const std::string f_type ( quality_traits.key_table[i] ); + const std::string f_type ( quality_traits::key_table[i] ); add_value ( f_type ); debug() << " quality: " << i << " is " << f_type <= pile_list->size() ) + if ( size_t(ii.subtype) >= pile_list->size() ) { debug() << "error itemdef index too large! idx[" << ii.subtype << "] max_size[" << pile_list->size() << "]" << endl; continue; @@ -465,8 +465,9 @@ int StockpileSerializer::other_mats_token ( const std::map oth void StockpileSerializer::write_general() { - mBuffer.set_max_bins ( mPile->max_barrels ); + mBuffer.set_max_bins ( mPile->max_bins ); mBuffer.set_max_wheelbarrows ( mPile->max_wheelbarrows ); + mBuffer.set_max_barrels ( mPile->max_barrels ); mBuffer.set_use_links_only ( mPile->use_links_only ); mBuffer.set_unknown1 ( mPile->settings.unk1 ); mBuffer.set_allow_inorganic ( mPile->settings.allow_inorganic ); @@ -527,7 +528,7 @@ void StockpileSerializer::read_animals() std::string id = mBuffer.animals().enabled ( i ); int idx = find_creature ( id ); debug() << id << " " << idx << endl; - if ( idx < 0 || idx >= mPile->settings.animals.enabled.size() ) + if ( idx < 0 || size_t(idx) >= mPile->settings.animals.enabled.size() ) { debug() << "WARNING: animal index invalid: " << idx << endl; continue; @@ -755,12 +756,12 @@ void StockpileSerializer::write_food() food->set_prepared_meals ( mPile->settings.food.prepared_meals ); using df::enums::organic_mat_category::organic_mat_category; - df::enum_traits traits; - for ( int32_t mat_category = traits.first_item_value; mat_category ; + for ( int32_t mat_category = traits::first_item_value; mat_category traits; + using traits = df::enum_traits; if ( mBuffer.has_food() ) { mPile->settings.flags.bits.food = 1; @@ -783,7 +784,7 @@ void StockpileSerializer::read_food() debug() << " prepared_meals: " << mPile->settings.food.prepared_meals << endl; - for ( int32_t mat_category = traits.first_item_value; mat_category type_traits; + using type_traits = df::enum_traits; for ( size_t i = 0; i < mPile->settings.furniture.type.size(); ++i ) { if ( mPile->settings.furniture.type.at ( i ) ) { - std::string f_type ( type_traits.key_table[i] ); + std::string f_type ( type_traits::key_table[i] ); furniture->add_type ( f_type ); debug() << "furniture_type " << i << " is " << f_type <::base_type idx = linear_index ( debug(), type_traits, type ); debug() << " type " << idx << " is " << type << endl; - if ( idx < 0 || idx >= mPile->settings.furniture.type.size() ) + if ( idx < 0 || size_t(idx) >= mPile->settings.furniture.type.size() ) { debug() << "WARNING: furniture type index invalid " << type << ", idx=" << idx << endl; continue; @@ -1047,7 +1048,7 @@ void StockpileSerializer::refuse_read_helper ( std::function= pile_list->size() ) + if ( idx < 0 || !refuse_creature_is_allowed ( creature ) || size_t(idx) >= pile_list->size() ) { debug() << "WARNING invalid refuse creature " << creature_id << ", idx=" << idx << endl; continue; @@ -1518,7 +1519,7 @@ void StockpileSerializer::read_gems() const std::string token = gems.rough_other_mats ( i ); MaterialInfo mi; mi.find ( token ); - if ( !mi.isValid() || mi.type >= builtin_size ) + if ( !mi.isValid() || size_t(mi.type) >= builtin_size ) { debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; continue; @@ -1535,7 +1536,7 @@ void StockpileSerializer::read_gems() const std::string token = gems.cut_other_mats ( i ); MaterialInfo mi; mi.find ( token ); - if ( !mi.isValid() || mi.type >= builtin_size ) + if ( !mi.isValid() || size_t(mi.type) >= builtin_size ) { debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; continue; diff --git a/plugins/stockpiles/StockpileUtils.h b/plugins/stockpiles/StockpileUtils.h index 4cb5669df..65672128e 100644 --- a/plugins/stockpiles/StockpileUtils.h +++ b/plugins/stockpiles/StockpileUtils.h @@ -21,7 +21,7 @@ /** * Retrieve creature raw from index */ -static df::creature_raw* find_creature ( int32_t idx ) +static inline df::creature_raw* find_creature ( int32_t idx ) { return df::global::world->raws.creatures.all[idx]; } @@ -30,7 +30,7 @@ static df::creature_raw* find_creature ( int32_t idx ) * Retrieve creature index from id string * @return -1 if not found */ -static int16_t find_creature ( const std::string &creature_id ) +static inline int16_t find_creature ( const std::string &creature_id ) { return linear_index ( df::global::world->raws.creatures.all, &df::creature_raw::creature_id, creature_id ); } @@ -38,7 +38,7 @@ static int16_t find_creature ( const std::string &creature_id ) /** * Retrieve plant raw from index */ -static df::plant_raw* find_plant ( size_t idx ) +static inline df::plant_raw* find_plant ( size_t idx ) { return df::global::world->raws.plants.all[idx]; } @@ -47,7 +47,7 @@ static df::plant_raw* find_plant ( size_t idx ) * Retrieve plant index from id string * @return -1 if not found */ -static size_t find_plant ( const std::string &plant_id ) +static inline size_t find_plant ( const std::string &plant_id ) { return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id ); } @@ -60,7 +60,7 @@ struct less_than_no_case: public std::binary_function< char,char,bool > } }; -static bool CompareNoCase(const std::string &a, const std::string &b) +static inline bool CompareNoCase(const std::string &a, const std::string &b) { return std::lexicographical_compare( a.begin(),a.end(), b.begin(),b.end(), less_than_no_case() ); } @@ -70,7 +70,7 @@ static bool CompareNoCase(const std::string &a, const std::string &b) * Checks if the parameter has the dfstock extension. * Doesn't check if the file exists or not. */ -static bool is_dfstockfile ( const std::string& filename ) +static inline bool is_dfstockfile ( const std::string& filename ) { return filename.rfind ( ".dfstock" ) != std::string::npos; } diff --git a/plugins/stockpiles/proto/tmp/.gitignore b/plugins/stockpiles/proto/tmp/.gitignore new file mode 100644 index 000000000..75feca5b1 --- /dev/null +++ b/plugins/stockpiles/proto/tmp/.gitignore @@ -0,0 +1 @@ +*.pb.* diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 3cd2cb31a..51d4ccb53 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -244,11 +244,20 @@ static command_result savestock ( color_ostream &out, vector & paramete cereal.enable_debug ( out ); if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - if ( !cereal.serialize_to_file ( file ) ) + try { - out.printerr ( "could not save to %s\n", file.c_str() ); + if ( !cereal.serialize_to_file ( file ) ) + { + out.printerr ( "could not save to %s\n", file.c_str() ); + return CR_FAILURE; + } + } + catch ( std::exception &e ) + { + out.printerr ( "serialization failed: protobuf exception: %s\n", e.what() ); return CR_FAILURE; } + return CR_OK; } @@ -296,9 +305,17 @@ static command_result loadstock ( color_ostream &out, vector & paramete StockpileSerializer cereal ( sp ); if ( debug ) cereal.enable_debug ( out ); - if ( !cereal.unserialize_from_file ( file ) ) + try + { + if ( !cereal.unserialize_from_file ( file ) ) + { + out.printerr ( "unserialization failed: %s\n", file.c_str() ); + return CR_FAILURE; + } + } + catch ( std::exception &e ) { - out.printerr ( "unserialization failed\n" ); + out.printerr ( "unserialization failed: protobuf exception: %s\n", e.what() ); return CR_FAILURE; } return CR_OK; @@ -359,6 +376,9 @@ struct stockpiles_import_hook : public df::viewscreen_dwarfmodest bool handleInput ( set *input ) { + if ( Gui::inRenameBuilding() ) + return false; + df::building_stockpilest *sp = get_selected_stockpile(); if ( !sp ) return false; @@ -389,7 +409,7 @@ struct stockpiles_import_hook : public df::viewscreen_dwarfmodest auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - int y = dims.y2 - 7; + int y = dims.y2 - 3; int links = 0; links += sp->links.give_to_pile.size(); @@ -487,12 +507,6 @@ static std::vector clean_dfstock_list ( const std::string &path ) return files; } -static bool isEnabled( lua_State *L ) -{ - Lua::Push(L, is_enabled); - return 1; -} - static int stockpiles_list_settings ( lua_State *L ) { auto path = luaL_checkstring ( L, 1 ); @@ -508,13 +522,16 @@ static int stockpiles_list_settings ( lua_State *L ) return 1; } +const std::string err_title = "Stockpile Settings Error"; +const std::string err_help = "Does the folder exist?\nCheck the console for more information."; + static void stockpiles_load ( color_ostream &out, std::string filename ) { std::vector params; params.push_back ( filename ); command_result r = loadstock ( out, params ); if ( r != CR_OK ) - show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true ); + show_message_box ( err_title, "Couldn't load. " + err_help, true ); } @@ -524,7 +541,7 @@ static void stockpiles_save ( color_ostream &out, std::string filename ) params.push_back ( filename ); command_result r = savestock ( out, params ); if ( r != CR_OK ) - show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true ); + show_message_box ( err_title, "Couldn't save. " + err_help, true ); } DFHACK_PLUGIN_LUA_FUNCTIONS diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 8565c703f..a533f4669 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -42,17 +42,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) #define MAX_NAME 30 #define SIDEBAR_WIDTH 30 -static bool show_debugging = false; - -static void debug(const string &msg) -{ - if (!show_debugging) - return; - - color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG (stocks): " << msg << endl; -} - /* * Utility @@ -83,89 +72,6 @@ static df::item *get_container_of(df::unit *unit) * Trade Info */ -static bool check_mandates(df::item *item) -{ - for (auto it = world->mandates.begin(); it != world->mandates.end(); it++) - { - auto mandate = *it; - - if (mandate->mode != 0) - continue; - - if (item->getType() != mandate->item_type || - (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) - continue; - - if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) - continue; - - if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) - continue; - - return false; - } - - return true; -} - -static bool can_trade_item(df::item *item) -{ - if (item->flags.bits.owned || item->flags.bits.artifact || item->flags.bits.spider_web || item->flags.bits.in_job) - return false; - - for (size_t i = 0; i < item->general_refs.size(); i++) - { - df::general_ref *ref = item->general_refs[i]; - - switch (ref->getType()) - { - case general_ref_type::UNIT_HOLDER: - return false; - - case general_ref_type::BUILDING_HOLDER: - return false; - - default: - break; - } - } - - for (size_t i = 0; i < item->specific_refs.size(); i++) - { - df::specific_ref *ref = item->specific_refs[i]; - - if (ref->type == specific_ref_type::JOB) - { - // Ignore any items assigned to a job - return false; - } - } - - return check_mandates(item); -} - -static bool can_trade_item_and_container(df::item *item) -{ - item = get_container_of(item); - - if (item->flags.bits.in_inventory) - return false; - - if (!can_trade_item(item)) - return false; - - vector contained_items; - Items::getContainedItems(item, &contained_items); - for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++) - { - if (!can_trade_item(*cit)) - return false; - } - - return true; -} - - class TradeDepotInfo { public: @@ -196,7 +102,7 @@ public: { auto item = *it; item = get_container_of(item); - if (!can_trade_item_and_container(item)) + if (!Items::canTradeWithContents(item)) return false; auto href = df::allocate(); @@ -981,8 +887,7 @@ public: void move_cursor(const df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); + Gui::refreshSidebar(); } void send_key(const df::interface_key &key) @@ -1062,6 +967,18 @@ public: std::string getFocusString() { return "stocks_view"; } + df::item *getSelectedItem() override + { + if (is_grouped) + return nullptr; + vector items = getSelectedItems(); + if (items.size() != 1) + return nullptr; + if (items[0]->entries.size() != 1) + return nullptr; + return items[0]->entries[0]; + } + private: StockListColumn items_column; int selected_column; @@ -1198,7 +1115,7 @@ private: if (state_to_apply == -1) state_to_apply = (item->flags.whole & flags.whole) ? 0 : 1; - grouped_entry->setFlags(flags.whole, state_to_apply); + grouped_entry->setFlags(flags, state_to_apply); } } @@ -1451,6 +1368,9 @@ struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + df::building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/plugins/stonesense b/plugins/stonesense index bd6e8f9f9..164d2cd13 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit bd6e8f9f90af1586f5b1eb0003d7e0e133acecc8 +Subproject commit 164d2cd1349c090a91c1c19200ad03df9bb16650 diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 783f73d1f..9a7f7f5fb 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -2,6 +2,7 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" #include "Export.h" #include "PluginManager.h" #include "modules/Gui.h" @@ -11,24 +12,24 @@ #include "modules/Translation.h" #include "modules/Random.h" -#include "DataDefs.h" +#include "df/builtin_mats.h" +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/creature_raw.h" #include "df/d_init.h" -#include "df/world.h" +#include "df/entity_raw.h" +#include "df/general_ref_unit_workerst.h" +#include "df/historical_entity.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/map_block.h" #include "df/ui.h" #include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" #include "df/unit_preference.h" -#include "df/map_block.h" -#include "df/job.h" -#include "df/job_item.h" -#include "df/historical_entity.h" -#include "df/entity_raw.h" -#include "df/builtin_mats.h" -#include "df/general_ref_unit_workerst.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/caste_raw_flags.h" +#include "df/unit_relationship_type.h" +#include "df/unit_skill.h" +#include "df/unit_soul.h" +#include "df/world.h" using std::string; using std::vector; @@ -105,6 +106,8 @@ df::job_skill getMoodSkill (df::unit *unit) if (skill->rating == level) skills.push_back(skill->id); break; + default: + break; } } if (!skills.size() && civ) @@ -174,7 +177,7 @@ void generateName(df::language_name &output, int language, int mode, const df::l int32_t word; df::enum_field part; output.first_name.clear(); selectWord(table1, word, part, 2); - if (word >= 0 && word < world->raws.language.words.size()) + if (word >= 0 && size_t(word) < world->raws.language.words.size()) output.first_name = *world->raws.language.translations[language]->words[word]; } if (mode != 10) @@ -579,9 +582,9 @@ command_result df_strangemood (color_ostream &out, vector & parameters) df::unit *cur = moodable_units[i]; if (cur->flags1.bits.had_mood) continue; - if (cur->relations.dragger_id != -1) + if (cur->relationship_ids[df::unit_relationship_type::Dragger] != -1) continue; - if (cur->relations.draggee_id != -1) + if (cur->relationship_ids[df::unit_relationship_type::Draggee] != -1) continue; tickets.push_back(i); for (int j = 0; j < 5; j++) @@ -615,6 +618,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) for (int j = 0; j < 15; j++) tickets.push_back(i); break; + default: + break; } } if (!tickets.size()) @@ -699,7 +704,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } unit->mood = type; - unit->relations.mood_copy = unit->mood; + unit->mood_copy = unit->mood; Gui::showAutoAnnouncement(announcement_type::STRANGE_MOOD, unit->pos, msg, color, bright); // TODO: make sure unit drops any wrestle items @@ -764,6 +769,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::MECHANICS: job->job_type = job_type::StrangeMoodMechanics; break; + default: + break; } } // Check which types of glass are available - we'll need this information later @@ -910,6 +917,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) filter = NULL; continue; } + break; } if (filter) { @@ -993,6 +1001,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) filter = NULL; continue; } + break; } if (filter) { @@ -1108,6 +1117,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } item->quantity = base_item_count; break; + default: + break; } } @@ -1154,8 +1165,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::GLASSMAKER: avoid_glass = 1; break; + default: + break; } - for (size_t i = 0; i < extra_items; i++) + for (int i = 0; i < extra_items; i++) { if ((job->job_type == job_type::StrangeMoodBrooding) && (rng.df_trandom(2))) { diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index ae5362e15..36bde1ac7 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -19,27 +19,33 @@ // Options (tiletypes-command): // (anything) - run the given command +#include #include -#include #include #include -#include #include +#include + using std::vector; using std::string; using std::endl; using std::set; -#include "Core.h" #include "Console.h" +#include "Core.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Maps.h" -#include "modules/Gui.h" #include "TileTypes.h" + +#include "modules/Gui.h" #include "modules/MapCache.h" +#include "modules/Maps.h" + #include "df/tile_dig_designation.h" +#include "df/world.h" + #include "Brushes.h" + using namespace MapExtras; using namespace DFHack; using namespace df::enums; @@ -72,7 +78,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) void help( color_ostream & out, std::vector &commands, int start, int end) { - std::string option = commands.size() > start ? commands[start] : ""; + std::string option = commands.size() > size_t(start) ? commands[start] : ""; if (option.empty()) { out << "Commands:" << std::endl @@ -806,7 +812,7 @@ command_result executePaintJob(color_ostream &out) */ // Remove direction from directionless tiles DFHack::TileDirection direction = tileDirection(source); - if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || special == tiletype_special::TRACK || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH))) + if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || special == tiletype_special::TRACK || (shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH)))) { direction.whole = 0; } @@ -888,7 +894,7 @@ command_result executePaintJob(color_ostream &out) command_result processCommand(color_ostream &out, std::vector &commands, int start, int end, bool & endLoop, bool hasConsole = false) { - if (commands.size() == start) + if (commands.size() == size_t(start)) { return executePaintJob(out); } diff --git a/plugins/title-folder.cpp b/plugins/title-folder.cpp new file mode 100644 index 000000000..eae381b95 --- /dev/null +++ b/plugins/title-folder.cpp @@ -0,0 +1,116 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "MemAccess.h" +#include "PluginManager.h" + +using namespace DFHack; + +DFHACK_PLUGIN("title-folder"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +// SDL frees the old window title when changed +static std::string original_title; + +static DFLibrary *sdl_handle = NULL; +static const std::vector sdl_libs { + "SDLreal.dll", + "SDL.framework/Versions/A/SDL", + "SDL.framework/SDL", + "libSDL-1.2.so.0" +}; + +void (*_SDL_WM_GetCaption)(const char**, const char**) = NULL; +void SDL_WM_GetCaption(const char **title, const char **icon) { + _SDL_WM_GetCaption(title, icon); +} + +void (*_SDL_WM_SetCaption)(const char*, const char*) = NULL; +void SDL_WM_SetCaption(const char *title, const char *icon) { + _SDL_WM_SetCaption(title, icon); +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool state); +DFhackCExport command_result plugin_shutdown (color_ostream &out); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + for (auto it = sdl_libs.begin(); it != sdl_libs.end(); ++it) + { + if ((sdl_handle = OpenPlugin(it->c_str()))) + break; + } + if (!sdl_handle) + { + out.printerr("title-folder: Could not load SDL.\n"); + return CR_FAILURE; + } + + #define bind(name) \ + _##name = (decltype(_##name))LookupPlugin(sdl_handle, #name); \ + if (!_##name) { \ + out.printerr("title-folder: Bind failed: " #name "\n"); \ + plugin_shutdown(out); \ + return CR_FAILURE; \ + } + + bind(SDL_WM_GetCaption); + bind(SDL_WM_SetCaption); + #undef bind + + const char *title = NULL; + SDL_WM_GetCaption(&title, NULL); + if (!title) + { + out.printerr("title-folder: Failed to get original title\n"); + title = "Dwarf Fortress"; + } + original_title = title; + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + if (is_enabled) + { + plugin_enable(out, false); + } + if (sdl_handle) + { + ClosePlugin(sdl_handle); + sdl_handle = NULL; + } + return CR_OK; +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool state) +{ + if (state == is_enabled) + return CR_OK; + + if (state) + { + std::string path = Core::getInstance().p->getPath(); + std::string folder; + size_t pos = path.find_last_of('/'); + if (pos == std::string::npos) + pos = path.find_last_of('\\'); + + if (pos != std::string::npos) + folder = path.substr(pos + 1); + else + folder = path; + + std::string title = original_title + " (" + folder + ")"; + SDL_WM_SetCaption(title.c_str(), NULL); + } + else + { + SDL_WM_SetCaption(original_title.c_str(), NULL); + } + + is_enabled = state; + return CR_OK; +} diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp index 78a694211..be7bc7e79 100644 --- a/plugins/title-version.cpp +++ b/plugins/title-version.cpp @@ -31,15 +31,23 @@ struct title_version_hook : df::viewscreen_titlest { DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); + if (loading) + return; + int x = 0, y = 0; OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION); if (!DFHACK_IS_RELEASE) { OutputString(COLOR_WHITE, x, y, " (dev)"); - x = 0; y = 1; + x = 0; y++; OutputString(COLOR_WHITE, x, y, "Git: "); OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION); } + if (DFHACK_IS_PRERELEASE) + { + x = 0; y++; + OutputString(COLOR_LIGHTRED, x, y, "Pre-release build"); + } } }; diff --git a/plugins/treefarm.cpp b/plugins/treefarm.cpp deleted file mode 100644 index 0e24ad28f..000000000 --- a/plugins/treefarm.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "Console.h" -#include "Core.h" -#include "DataDefs.h" -#include "Export.h" -#include "PluginManager.h" - -#include "modules/EventManager.h" -#include "modules/Once.h" - -#include "df/block_burrow.h" -#include "df/block_burrow_link.h" -#include "df/burrow.h" -#include "df/map_block.h" -#include "df/tile_bitmask.h" -#include "df/tile_dig_designation.h" -#include "df/tiletype.h" -#include "df/tiletype_shape.h" -#include "df/world.h" - -//#include "df/world.h" - -using namespace DFHack; -/* -treefarm -======== -Automatically manages special burrows and regularly schedules tree chopping -and digging when appropriate. - -Every time the plugin runs, it checks for burrows with a name containing the -string ``"treefarm"``. For each such burrow, it checks every tile in it for -fully-grown trees and for diggable walls. For each fully-grown tree it finds, -it designates the tree to be chopped, and for each natural wall it finds, it -designates the wall to be dug. - -Usage: - -:treefarm: Enables treefarm monitoring, starting next frame -:treefarm n: Enables treefarm monitoring, starting next frame, and sets - interval to n frames. If n is less than one, disables monitoring. -*/ - -DFHACK_PLUGIN("treefarm"); -DFHACK_PLUGIN_IS_ENABLED(enabled); - -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(ui); - -void checkFarms(color_ostream& out, void* ptr); -command_result treefarm (color_ostream &out, std::vector & parameters); - -EventManager::EventHandler handler(&checkFarms, -1); -int32_t frequency = 1200*30; - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "treefarm", - "automatically manages special burrows and regularly schedules tree chopping and digging when appropriate", - treefarm, - false, //allow non-interactive use - "treefarm\n" - " enables treefarm monitoring, starting next frame\n" - "treefarm n\n" - " enables treefarm monitoring, starting next frame\n" - " sets monitoring interval to n frames\n" - " if n is less than one, disables monitoring\n" - "\n" - "Every time the plugin runs, it checks for burrows with a name containing the string \"treefarm\". For each such burrow, it checks every tile in it for fully-grown trees and for diggable walls. For each fully-grown tree it finds, it designates the tree to be chopped, and for each natural wall it finds, it designates the wall to be dug.\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { - enabled = enable; - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -void checkFarms(color_ostream& out, void* ptr) { - EventManager::unregisterAll(plugin_self); - if ( !enabled ) - return; - EventManager::registerTick(handler, frequency, plugin_self); - CoreSuspender suspend; - - int32_t xOffset = world->map.region_x*3; - int32_t yOffset = world->map.region_y*3; - int32_t zOffset = world->map.region_z; - //for each burrow named treefarm or obsidianfarm, check if you can dig/chop any obsidian/trees - for ( size_t a = 0; a < df::burrow::get_vector().size(); a++ ) { - df::burrow* burrow = df::burrow::get_vector()[a]; - if ( !burrow || burrow->name.find("treefarm") == std::string::npos ) - continue; - - if ( burrow->block_x.size() != burrow->block_y.size() || burrow->block_x.size() != burrow->block_z.size() ) - continue; - - for ( size_t b = 0; b < burrow->block_x.size(); b++ ) { - int32_t x=burrow->block_x[b] - xOffset; - int32_t y=burrow->block_y[b] - yOffset; - int32_t z=burrow->block_z[b] - zOffset; - - df::map_block* block = world->map.block_index[x][y][z]; - if ( !block ) - continue; - - df::block_burrow_link* link = &block->block_burrows; - df::tile_bitmask mask; - for ( ; link != NULL; link = link->next ) { - if ( link->item == NULL ) - continue; - if ( link->item->id == burrow->id ) { - mask = link->item->tile_bitmask; - break; - } - } - if ( link == NULL ) - continue; - - for ( int32_t x = 0; x < 16; x++ ) { - for ( int32_t y = 0; y < 16; y++ ) { - if ( !mask.getassignment(x,y) ) - continue; - df::tiletype type = block->tiletype[x][y]; - df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, type); - if ( !block->designation[x][y].bits.hidden && - shape != df::enums::tiletype_shape::WALL && - shape != df::enums::tiletype_shape::TREE ) - continue; - if ( shape != df::enums::tiletype_shape::TREE ) { - if ( x == 0 && (block->map_pos.x/16) == 0 ) - continue; - if ( y == 0 && (block->map_pos.y/16) == 0 ) - continue; - if ( x == 15 && (block->map_pos.x/16) == world->map.x_count_block-1 ) - continue; - if ( y == 15 && (block->map_pos.y/16) == world->map.y_count_block-1 ) - continue; - } - - block->designation[x][y].bits.dig = df::enums::tile_dig_designation::Default; - } - } - } - } -} - -command_result treefarm (color_ostream &out, std::vector & parameters) -{ - EventManager::unregisterAll(plugin_self); - - if ( parameters.size() > 1 ) - return CR_WRONG_USAGE; - if ( parameters.size() == 1 ) { - int32_t i = atoi(parameters[0].c_str()); - if ( i < 1 ) { - plugin_enable(out, false); - out.print("treefarm disabled\n"); - return CR_OK; - } - plugin_enable(out, true); - frequency = i; - } - - if ( enabled ) { - EventManager::registerTick(handler, 1, plugin_self); - out.print("treefarm enabled with update frequency %d ticks\n", frequency); - } - return CR_OK; -} - diff --git a/plugins/tubefill.cpp b/plugins/tubefill.cpp index 73c615c9b..1b77d466c 100644 --- a/plugins/tubefill.cpp +++ b/plugins/tubefill.cpp @@ -14,6 +14,8 @@ #include "TileTypes.h" #include "df/deep_vein_hollow.h" +#include "df/map_block.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 88c57fb62..35f0f4705 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -80,7 +80,10 @@ #include "tweaks/adamantine-cloth-wear.h" #include "tweaks/advmode-contained.h" #include "tweaks/block-labors.h" +#include "tweaks/burrow-name-cancel.h" +#include "tweaks/cage-butcher.h" #include "tweaks/civ-agreement-ui.h" +#include "tweaks/condition-material.h" #include "tweaks/craft-age-wear.h" #include "tweaks/eggs-fertile.h" #include "tweaks/embark-profile-name.h" @@ -89,15 +92,18 @@ #include "tweaks/fast-trade.h" #include "tweaks/fps-min.h" #include "tweaks/hide-priority.h" +#include "tweaks/hotkey-clear.h" #include "tweaks/import-priority-category.h" -#include "tweaks/kitchen-keys.h" +#include "tweaks/kitchen-prefs-all.h" #include "tweaks/kitchen-prefs-color.h" #include "tweaks/kitchen-prefs-empty.h" #include "tweaks/max-wheelbarrow.h" #include "tweaks/military-assign.h" +#include "tweaks/pausing-fps-counter.h" #include "tweaks/nestbox-color.h" #include "tweaks/shift-8-scroll.h" #include "tweaks/stable-cursor.h" +#include "tweaks/stone-status-all.h" #include "tweaks/title-start-rename.h" #include "tweaks/tradereq-pet-gender.h" @@ -113,11 +119,12 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(enabler); REQUIRE_GLOBAL(ui); -REQUIRE_GLOBAL(ui_area_map_width); REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(ui_building_in_assign); +REQUIRE_GLOBAL(ui_building_in_resize); REQUIRE_GLOBAL(ui_building_item_cursor); -REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(ui_look_cursor); +REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(ui_unit_view_mode); REQUIRE_GLOBAL(ui_workshop_in_add); @@ -128,8 +135,8 @@ using namespace DFHack::Gui; class tweak_onupdate_hookst { public: typedef void(*T_callback)(void); - tweak_onupdate_hookst(std::string name_, T_callback cb) - :name(name_), callback(cb), enabled(false) {} +tweak_onupdate_hookst(std::string name_, T_callback cb) + :enabled(false), name(name_), callback(cb) {} bool enabled; std::string name; T_callback callback; @@ -179,10 +186,16 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode == df::ui_sidebar_mode::ViewUnits && - ui_unit_view_mode->value == df::ui_unit_view_mode::T_value::PrefLabor; + ui_unit_view_mode->value == df::ui_unit_view_mode::T_value::PrefLabor && + Gui::getAnyUnit(this); } inline bool forbidden_labor (df::unit *unit, df::unit_labor labor) { - return is_valid_enum_item(labor) && !Units::isValidLabor(unit, labor); + return is_valid_enum_item(labor) && unit && !Units::isValidLabor(unit, labor); } inline bool all_labors_enabled (df::unit *unit, df::unit_labor_category cat) @@ -56,7 +57,7 @@ struct block_labors_hook : df::viewscreen_dwarfmodest { df::unit *unit = Gui::getAnyUnit(this); for (int y = 5, i = (*ui_look_cursor/13)*13; - y <= 17 && i < unit_labors_sidemenu.size(); + y <= 17 && size_t(i) < unit_labors_sidemenu.size(); ++y, ++i) { df::unit_labor labor = unit_labors_sidemenu[i]; @@ -74,12 +75,12 @@ struct block_labors_hook : df::viewscreen_dwarfmodest { DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { using namespace df::enums::interface_key; - if (valid_mode()) - { - df::unit *unit = Gui::getAnyUnit(this); - df::unit_labor labor = unit_labors_sidemenu[*ui_look_cursor]; - df::unit_labor_category cat = df::unit_labor_category(labor); + df::unit *unit = Gui::getAnyUnit(this); + df::unit_labor labor = vector_get(unit_labors_sidemenu, *ui_look_cursor, df::unit_labor::NONE); + df::unit_labor_category cat = df::unit_labor_category(labor); + if (valid_mode() && labor != df::unit_labor::NONE) + { if ((input->count(SELECT) || input->count(SELECT_ALL)) && forbidden_labor(unit, labor)) { unit->status.labors[labor] = false; diff --git a/plugins/tweak/tweaks/burrow-name-cancel.h b/plugins/tweak/tweaks/burrow-name-cancel.h new file mode 100644 index 000000000..2ee84508e --- /dev/null +++ b/plugins/tweak/tweaks/burrow-name-cancel.h @@ -0,0 +1,44 @@ +#include "df/burrow.h" + +using df::global::ui; + +struct burrow_name_cancel_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + static std::string old_name; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + if (ui->main.mode == df::ui_sidebar_mode::Burrows) + { + bool was_naming = ui->burrows.in_edit_name_mode; + INTERPOSE_NEXT(feed)(input); + df::burrow *burrow = vector_get(ui->burrows.list, ui->burrows.sel_index); + if (!burrow) + return; + + if (ui->burrows.in_edit_name_mode) + { + if (!was_naming) + { + // Just started renaming - make a copy of the old name + old_name = burrow->name; + } + if (input->count(df::interface_key::LEAVESCREEN)) + { + // Cancel and restore the old name + ui->burrows.in_edit_name_mode = false; + burrow->name = old_name; + } + } + } + else + { + INTERPOSE_NEXT(feed)(input); + } + } +}; + +std::string burrow_name_cancel_hook::old_name; + +IMPLEMENT_VMETHOD_INTERPOSE(burrow_name_cancel_hook, feed); diff --git a/plugins/tweak/tweaks/cage-butcher.h b/plugins/tweak/tweaks/cage-butcher.h new file mode 100644 index 000000000..61a989d50 --- /dev/null +++ b/plugins/tweak/tweaks/cage-butcher.h @@ -0,0 +1,98 @@ +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "df/building_cagest.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/ui_sidebar_mode.h" +#include "df/unit.h" + +using namespace DFHack; +using df::global::ui; +using df::global::ui_building_in_assign; +using df::global::ui_building_in_resize; +using df::global::ui_building_item_cursor; + +struct cage_butcher_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + inline df::building_cagest *get_cage() + { + if (*ui_building_in_assign || *ui_building_in_resize) + return nullptr; + + if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding) + return nullptr; + + auto cage = virtual_cast(Gui::getAnyBuilding(this)); + if (!cage) + return nullptr; + if (cage->getBuildStage() < cage->getMaxBuildStage()) + return nullptr; + if (cage->flags.bits.justice) + return nullptr; + if (Buildings::markedForRemoval(cage)) + return nullptr; + + return cage; + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + using namespace df::enums::interface_key; + INTERPOSE_NEXT(render)(); + + auto cage = get_cage(); + if (!cage) + return; + + std::vector units; + if (!Buildings::getCageOccupants(cage, units)) + return; + + auto dims = Gui::getDwarfmodeViewDims(); + for (int y = 4, i = (*ui_building_item_cursor/11)*11; + y <= 14 && size_t(i) < units.size(); + ++y, ++i) + { + df::unit *unit = vector_get(units, i); + if (unit && unit->flags2.bits.slaughter) + { + int x = dims.menu_x2 - 2; + OutputString(COLOR_LIGHTMAGENTA, x, y, "Bu"); + } + } + + int x = dims.menu_x1 + 1, y = dims.y2; + OutputHotkeyString(x, y, "Butcher ", CUSTOM_B, false, 0, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, "all", CUSTOM_SHIFT_B, false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + auto cage = get_cage(); + if (cage) + { + std::vector units; + if (Buildings::getCageOccupants(cage, units)) + { + df::unit *unit = vector_get(units, *ui_building_item_cursor); + if (unit) + { + if (input->count(CUSTOM_B)) + { + unit->flags2.bits.slaughter = !unit->flags2.bits.slaughter; + } + } + if (input->count(CUSTOM_SHIFT_B)) + { + bool state = unit ? !unit->flags2.bits.slaughter : true; + for (auto u : units) + u->flags2.bits.slaughter = state; + } + } + } + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(cage_butcher_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(cage_butcher_hook, render); diff --git a/plugins/tweak/tweaks/condition-material.h b/plugins/tweak/tweaks/condition-material.h new file mode 100644 index 000000000..c9aec73c9 --- /dev/null +++ b/plugins/tweak/tweaks/condition-material.h @@ -0,0 +1,134 @@ +#include "df/viewscreen_workquota_conditionst.h" + +using namespace DFHack; + +struct condition_material_hook : df::viewscreen_workquota_conditionst { + typedef df::viewscreen_workquota_conditionst interpose_base; + typedef df::viewscreen_workquota_conditionst T_screen; + + struct T_order_mat_data { + std::vector list_entries; + std::vector mat_types; + std::vector mat_indices; + std::vector list_unk3; + std::vector list_visible; + }; + + static std::map order_mat_data; + + static void register_screen(T_screen *scr) + { + if (order_mat_data.find(scr) != order_mat_data.end()) + { + unregister_screen(scr); + } + auto data = new T_order_mat_data; + data->list_entries = scr->list_entries; + data->mat_types = scr->mat_types; + data->mat_indices = scr->mat_indices; + data->list_unk3 = scr->list_unk3; + data->list_visible = scr->list_visible; + order_mat_data[scr] = data; + } + + static void unregister_screen(T_screen *scr) + { + if (order_mat_data.find(scr) != order_mat_data.end() && order_mat_data[scr]) + { + T_order_mat_data *data = order_mat_data[scr]; + scr->list_entries = data->list_entries; + scr->mat_types = data->mat_types; + scr->mat_indices = data->mat_indices; + scr->list_unk3 = data->list_unk3; + scr->list_visible = data->list_visible; + delete data; + order_mat_data.erase(scr); + } + } + + void apply_filter() + { + if (order_mat_data.find(this) != order_mat_data.end() && order_mat_data[this]) + { + list_idx = 0; + T_order_mat_data *data = order_mat_data[this]; + // keep the first item ("no material") around, because attempts to delete it + // result in it still being displayed first, regardless of list_entries[0] + list_entries.resize(1); + mat_types.resize(1); + mat_indices.resize(1); + list_unk3.resize(1); + list_visible.resize(1); + // skip "no material" here + for (size_t i = 1; i < data->list_entries.size(); i++) + { + // cap it at 32767 elements to be safe + if (list_entries.size() >= INT16_MAX) + { + break; + } + std::string *s = data->list_entries[i]; + if (s->find(filter) != std::string::npos) + { + list_entries.push_back(data->list_entries[i]); + mat_types.push_back(data->mat_types[i]); + mat_indices.push_back(data->mat_indices[i]); + list_unk3.push_back(data->list_unk3[i]); + // this should be small enough to fit in an int16_t + list_visible.push_back(int16_t(list_entries.size() - 1)); + } + } + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + if (mode == T_mode::Material) + { + for (auto key : *input) + { + if (key == LEAVESCREEN || key == SELECT) + { + INTERPOSE_NEXT(feed)(input); + unregister_screen(this); + return; + } + else if (key == STANDARDSCROLL_UP || key == STANDARDSCROLL_DOWN || + key == STANDARDSCROLL_PAGEUP || key == STANDARDSCROLL_PAGEDOWN) + { + INTERPOSE_NEXT(feed)(input); + } + int ch = Screen::keyToChar(key); + if (ch != -1) + { + if (ch == 0) + { + if (!filter.empty()) + { + filter.erase(filter.size() - 1); + } + } + else + { + filter += tolower(char(ch)); + } + apply_filter(); + } + } + } + else + { + INTERPOSE_NEXT(feed)(input); + if (mode == T_mode::Material) + { + register_screen(this); + apply_filter(); + } + } + } +}; + +std::map condition_material_hook::order_mat_data; + +IMPLEMENT_VMETHOD_INTERPOSE(condition_material_hook, feed); diff --git a/plugins/tweak/tweaks/craft-age-wear.h b/plugins/tweak/tweaks/craft-age-wear.h index edc3e9a18..ca822ced6 100644 --- a/plugins/tweak/tweaks/craft-age-wear.h +++ b/plugins/tweak/tweaks/craft-age-wear.h @@ -3,7 +3,7 @@ struct craft_age_wear_hook : df::item_crafted { DEFINE_VMETHOD_INTERPOSE(bool, ageItem, (int amount)) { - int orig_age = age; + uint32_t orig_age = age; age += amount; if (age > 200000000) age = 200000000; @@ -24,7 +24,7 @@ struct craft_age_wear_hook : df::item_crafted { wear = 1; else return false; - wear = ((orig_age % wear) + (age - orig_age)) / wear; + wear = ((orig_age % wear) + int32_t(age - orig_age)) / wear; if (wear > 0) return incWearTimer(wear); else diff --git a/library/doc/img/.dot b/plugins/tweak/tweaks/cursor-cross.h similarity index 100% rename from library/doc/img/.dot rename to plugins/tweak/tweaks/cursor-cross.h diff --git a/plugins/tweak/tweaks/hotkey-clear.h b/plugins/tweak/tweaks/hotkey-clear.h new file mode 100644 index 000000000..bc85c112e --- /dev/null +++ b/plugins/tweak/tweaks/hotkey-clear.h @@ -0,0 +1,42 @@ +#include "df/viewscreen_dwarfmodest.h" + +using df::global::ui; + +struct hotkey_clear_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (ui->main.mode == df::ui_sidebar_mode::Hotkeys) + { + auto dims = Gui::getDwarfmodeViewDims(); + int x = dims.menu_x1 + 1, y = 19; + OutputHotkeyString(x, y, "Clear", df::interface_key::CUSTOM_C, false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (ui->main.mode == df::ui_sidebar_mode::Hotkeys && + input->count(df::interface_key::CUSTOM_C) && + !ui->main.in_rename_hotkey) + { + auto &hotkey = ui->main.hotkeys[ui->main.selected_hotkey]; + hotkey.name = ""; + hotkey.cmd = df::ui_hotkey::T_cmd::None; + hotkey.x = 0; + hotkey.y = 0; + hotkey.z = 0; + hotkey.unit_id = 0; + hotkey.item_id = 0; + } + else + { + INTERPOSE_NEXT(feed)(input); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(hotkey_clear_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(hotkey_clear_hook, render); diff --git a/plugins/tweak/tweaks/kitchen-keys.h b/plugins/tweak/tweaks/kitchen-keys.h deleted file mode 100644 index ca48a1c89..000000000 --- a/plugins/tweak/tweaks/kitchen-keys.h +++ /dev/null @@ -1,68 +0,0 @@ -using namespace DFHack; -using namespace df::enums; - -using df::global::ui_sidebar_menus; -using df::global::ui_workshop_in_add; - -static df::interface_key kitchen_bindings[] = { - df::interface_key::HOTKEY_KITCHEN_COOK_2, - df::interface_key::HOTKEY_KITCHEN_COOK_3, - df::interface_key::HOTKEY_KITCHEN_COOK_4, - // DF uses CUSTOM_R for this reaction in the raws, so this key is recognized - // by this tweak but not displayed - df::interface_key::HOTKEY_KITCHEN_RENDER_FAT -}; - -struct kitchen_keys_hook : df::viewscreen_dwarfmodest { - typedef df::viewscreen_dwarfmodest interpose_base; - - void draw_binding (int row, df::interface_key key) - { - std::string label = Screen::getKeyDisplay(key); - int x = Gui::getDwarfmodeViewDims().menu_x2 - 2 - label.size(); - int y = row + 4; - OutputString(COLOR_GREY, x, y, "("); - OutputString(COLOR_LIGHTRED, x, y, label); - OutputString(COLOR_GREY, x, y, ")"); - } - - bool kitchen_in_add() - { - if (!*ui_workshop_in_add) - return false; - df::building_workshopst *ws = virtual_cast(world->selected_building); - if (!ws) - return false; - if (ws->type != workshop_type::Kitchen) - return false; - return true; - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) - { - if (kitchen_in_add()) - { - for (int i = 0; i < 4; i++) - { - if (input->count(kitchen_bindings[i])) - { - ui_sidebar_menus->workshop_job.cursor = i; - input->clear(); - input->insert(df::interface_key::SELECT); - } - } - } - INTERPOSE_NEXT(feed)(input); - } - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - if (kitchen_in_add()) - for (int i = 0; i < 3; i++) - draw_binding(i, kitchen_bindings[i]); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(kitchen_keys_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(kitchen_keys_hook, render); diff --git a/plugins/tweak/tweaks/kitchen-prefs-all.h b/plugins/tweak/tweaks/kitchen-prefs-all.h new file mode 100644 index 000000000..c5fa7baa9 --- /dev/null +++ b/plugins/tweak/tweaks/kitchen-prefs-all.h @@ -0,0 +1,69 @@ +#include "modules/Kitchen.h" + +#include "df/interface_key.h" +#include "df/layer_object_listst.h" +#include "df/viewscreen_kitchenprefst.h" + +using namespace DFHack; + +struct kitchen_prefs_all_hook : df::viewscreen_kitchenprefst { + typedef df::viewscreen_kitchenprefst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + df::kitchen_pref_flag flag; + df::kitchen_exc_type exc_type; + if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + flag.bits.Cook = true; + exc_type = df::kitchen_exc_type::Cook; + } + else if (input->count(interface_key::CUSTOM_SHIFT_B)) + { + flag.bits.Brew = true; + exc_type = df::kitchen_exc_type::Brew; + } + + if (flag.whole && cursor < forbidden[page].size()) + { + bool was_forbidden = forbidden[page][cursor].whole & flag.whole; + for (size_t i = 0; i < forbidden[page].size(); i++) + { + if (possible[page][i].whole & flag.whole) + { + if (was_forbidden) + { + // unset flag + forbidden[page][i].whole &= ~flag.whole; + Kitchen::removeExclusion(exc_type, + item_type[page][i], item_subtype[page][i], + mat_type[page][i], mat_index[page][i]); + } + else + { + // set flag + forbidden[page][i].whole |= flag.whole; + Kitchen::addExclusion(exc_type, + item_type[page][i], item_subtype[page][i], + mat_type[page][i], mat_index[page][i]); + } + } + } + } + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + int x = 2, y = gps->dimy - 2; + OutputHotkeyString(x, y, "Cook all", interface_key::CUSTOM_SHIFT_C, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); + x = 20; + OutputHotkeyString(x, y, "Brew all", interface_key::CUSTOM_SHIFT_B, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(kitchen_prefs_all_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(kitchen_prefs_all_hook, feed); diff --git a/plugins/tweak/tweaks/max-wheelbarrow.h b/plugins/tweak/tweaks/max-wheelbarrow.h index 37f292d6b..a7af186ef 100644 --- a/plugins/tweak/tweaks/max-wheelbarrow.h +++ b/plugins/tweak/tweaks/max-wheelbarrow.h @@ -43,9 +43,8 @@ struct max_wheelbarrow_hook : df::viewscreen_dwarfmodest { { df::building_stockpilest* stockpile = getStockpile(); bool handled = false; - if (stockpile) + if (stockpile && !Gui::inRenameBuilding()) { - auto dims = Gui::getDwarfmodeViewDims(); handled = true; if (!in_wheelbarrow_entry && input->count(df::interface_key::BUILDJOB_STOCKPILE_WHEELBARROW)) @@ -73,9 +72,8 @@ struct max_wheelbarrow_hook : df::viewscreen_dwarfmodest { } else { - for (auto iter = input->begin(); iter != input->end(); ++iter) + for (df::interface_key key : *input) { - df::interface_key key = *iter; if (key >= Screen::charToKey('0') && key <= Screen::charToKey('9') && wheelbarrow_entry.size() < 3) { diff --git a/plugins/tweak/tweaks/pausing-fps-counter.h b/plugins/tweak/tweaks/pausing-fps-counter.h new file mode 100644 index 000000000..d80fb2974 --- /dev/null +++ b/plugins/tweak/tweaks/pausing-fps-counter.h @@ -0,0 +1,137 @@ +#include "df/global_objects.h" +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "df/enabler.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_titlest.h" + +struct dwarfmode_pausing_fps_counter_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + static const uint32_t history_length = 3; + + // whether init.txt have [FPS:YES] + static bool init_have_fps_yes() + { + static bool first = true; + static bool init_have_fps_yes; + + if (first && df::global::gps) + { + // if first time called, then display_frames is set iff init.txt have [FPS:YES] + first = false; + init_have_fps_yes = (df::global::gps->display_frames == 1); + } + return init_have_fps_yes; + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (!df::global::pause_state || !df::global::enabler || !df::global::world + || !df::global::gps || !df::global::pause_state) + return; + + // if init.txt does not have [FPS:YES] then dont show this FPS counter + if (!dwarfmode_pausing_fps_counter_hook::init_have_fps_yes()) + return; + + static bool prev_paused = true; + static uint32_t prev_clock = 0; + static int32_t prev_frames = 0; + static uint32_t elapsed_clock = 0; + static uint32_t elapsed_frames = 0; + static double history[history_length]; + + if (prev_clock == 0) + { + // init + for (uint32_t i = 0; i < history_length; i++) + history[i] = 0.0; + } + + // disable default FPS counter because it is rendered on top of this FPS counter. + if (df::global::gps->display_frames == 1) + df::global::gps->display_frames = 0; + + if (*df::global::pause_state) + prev_paused = true; + else + { + uint32_t clock = df::global::enabler->clock; + int32_t frames = df::global::world->frame_counter; + + if (!prev_paused && prev_clock != 0 + && clock >= prev_clock && frames >= prev_frames) + { + // if we were previously paused, then dont add clock/frames, + // but wait for the next time render is called. + elapsed_clock += clock - prev_clock; + elapsed_frames += frames - prev_frames; + } + + prev_paused = false; + prev_clock = clock; + prev_frames = frames; + + // add FPS to history every second or after at least one frame. + if (elapsed_clock >= 1000 && elapsed_frames >= 1) + { + double fps = elapsed_frames / (elapsed_clock / 1000.0); + for (int i = history_length - 1; i >= 1; i--) + history[i] = history[i - 1]; + history[0] = fps; + + elapsed_clock = 0; + elapsed_frames = 0; + } + } + + // average fps over a few seconds to stabilize the counter. + double fps_sum = 0.0; + int fps_count = 0; + for (uint32_t i = 0; i < history_length; i++) + { + if (history[i] > 0.0) + { + fps_sum += history[i]; + fps_count++; + } + } + + double fps = fps_count == 0 ? 1.0 : fps_sum / fps_count; + double gfps = df::global::enabler->calculated_gfps; + + std::stringstream fps_counter; + fps_counter << "FPS:" + << setw(4) << fixed << setprecision(fps >= 1.0 ? 0 : 2) << fps + << " (" << gfps << ")"; + + // show this FPS counter same as the default counter. + int x = 10; + int y = 0; + OutputString(COLOR_WHITE, x, y, fps_counter.str(), + false, 0, COLOR_CYAN, false); + } +}; + +struct title_pausing_fps_counter_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + // if init.txt have FPS:YES then enable default FPS counter when exiting dwarf mode. + // So it is enabled if starting adventure mode without exiting dwarf fortress. + if (dwarfmode_pausing_fps_counter_hook::init_have_fps_yes() + && df::global::gps && df::global::gps->display_frames == 0) + df::global::gps->display_frames = 1; + } +}; + + +IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_pausing_fps_counter_hook, render); + +IMPLEMENT_VMETHOD_INTERPOSE(title_pausing_fps_counter_hook, render); diff --git a/plugins/tweak/tweaks/stable-cursor.h b/plugins/tweak/tweaks/stable-cursor.h index 105510c8b..7b6482d7b 100644 --- a/plugins/tweak/tweaks/stable-cursor.h +++ b/plugins/tweak/tweaks/stable-cursor.h @@ -52,20 +52,7 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest last_cursor.isValid() && cur_cursor.isValid()) { Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z); - - // Force update of ui state - set tmp; - if (last_cursor.z < 2) - tmp.insert(interface_key::CURSOR_UP_Z); - else - tmp.insert(interface_key::CURSOR_DOWN_Z); - INTERPOSE_NEXT(feed)(&tmp); - tmp.clear(); - if (last_cursor.z < 2) - tmp.insert(interface_key::CURSOR_DOWN_Z); - else - tmp.insert(interface_key::CURSOR_UP_Z); - INTERPOSE_NEXT(feed)(&tmp); + Gui::refreshSidebar(); } else if (!is_default && cur_cursor.isValid()) { diff --git a/plugins/tweak/tweaks/stone-status-all.h b/plugins/tweak/tweaks/stone-status-all.h new file mode 100644 index 000000000..575f7c279 --- /dev/null +++ b/plugins/tweak/tweaks/stone-status-all.h @@ -0,0 +1,34 @@ +#include "df/interface_key.h" +#include "df/layer_object_listst.h" +#include "df/viewscreen_layer_stone_restrictionst.h" + +using namespace DFHack; + +struct stone_status_all_hook : df::viewscreen_layer_stone_restrictionst { + typedef df::viewscreen_layer_stone_restrictionst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + if (input->count(interface_key::SELECT_ALL)) + { + if (VIRTUAL_CAST_VAR(list, df::layer_object_listst, layer_objects[0])) + { + bool new_state = !*stone_economic[type_tab][list->cursor]; + for (bool *economic : stone_economic[type_tab]) + *economic = new_state; + } + } + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + int x = 2, y = 23; + OutputHotkeyString(x, y, "All", interface_key::SELECT_ALL, + false, 0, COLOR_WHITE, COLOR_LIGHTRED); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(stone_status_all_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(stone_status_all_hook, feed); diff --git a/plugins/tweak/tweaks/tradereq-pet-gender.h b/plugins/tweak/tweaks/tradereq-pet-gender.h index df03008b8..ca786bef2 100644 --- a/plugins/tweak/tweaks/tradereq-pet-gender.h +++ b/plugins/tweak/tweaks/tradereq-pet-gender.h @@ -18,7 +18,7 @@ struct pet_gender_hook : df::viewscreen_topicmeeting_takerequestsst { df::historical_entity* entity = df::historical_entity::find(meeting->civ_id); vector& races = entity->resources.animals.pet_races; vector& castes = entity->resources.animals.pet_castes; - for (int i = (good_idx / 17) * 17, y = 4; i < (good_idx / 17) * 17 + 17 && i < races.size(); i++, y++) { + for (int i = (good_idx / 17) * 17, y = 4; i < (good_idx / 17) * 17 + 17 && size_t(i) < races.size(); i++, y++) { int x = 30 + 1 + world->raws.creatures.all[races[i]]->caste[castes[i]]->caste_name[0].size(); bool male = (bool)world->raws.creatures.all[races[i]]->caste[castes[i]]->gender; OutputString((i == good_idx) ? COLOR_WHITE : COLOR_GREY, diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 347ef5dbf..17fcd18bd 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -37,10 +37,6 @@ using std::set; using namespace DFHack; using namespace df::enums; -#ifndef HAVE_NULLPTR -#define nullptr 0L -#endif - #define COLOR_TITLE COLOR_BROWN #define COLOR_UNSELECTED COLOR_GREY #define COLOR_SELECTED COLOR_WHITE @@ -90,7 +86,7 @@ static void transform_(vector &src, vector &dst, Fn func) typedef int8_t UIColor; -static void OutputString(UIColor color, int &x, int &y, const std::string &text, +static inline void OutputString(UIColor color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0, const UIColor bg_color = 0, bool map = false) { Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text, map); @@ -103,7 +99,7 @@ static void OutputString(UIColor color, int &x, int &y, const std::string &text, x += text.length(); } -static void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, +static inline void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); @@ -112,14 +108,14 @@ static void OutputHotkeyString(int &x, int &y, const char *text, const char *hot OutputString(text_color, x, y, display, newline, left_margin, 0, map); } -static void OutputHotkeyString(int &x, int &y, const char *text, df::interface_key hotkey, +static inline void OutputHotkeyString(int &x, int &y, const char *text, df::interface_key hotkey, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputHotkeyString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), newline, left_margin, text_color, hotkey_color, map); } -static void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, +static inline void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); @@ -130,14 +126,14 @@ static void OutputLabelString(int &x, int &y, const char *text, const char *hotk OutputString(hotkey_color, x, y, label, newline, left_margin, 0, map); } -static void OutputLabelString(int &x, int &y, const char *text, df::interface_key hotkey, const string &label, bool newline = false, +static inline void OutputLabelString(int &x, int &y, const char *text, df::interface_key hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputLabelString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), label, newline, left_margin, text_color, hotkey_color, map); } -static void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, +static inline void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); @@ -145,7 +141,7 @@ static void OutputFilterString(int &x, int &y, const char *text, const char *hot OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin, 0, map); } -static void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, +static inline void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputHotkeyString(x, y, text, hotkey, false, 0, color, hotkey_color, map); @@ -156,7 +152,7 @@ static void OutputToggleString(int &x, int &y, const char *text, const char *hot OutputString(COLOR_GREY, x, y, "Off", newline, left_margin, 0, map); } -static void OutputToggleString(int &x, int &y, const char *text, df::interface_key hotkey, bool state, bool newline = true, +static inline void OutputToggleString(int &x, int &y, const char *text, df::interface_key hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { OutputToggleString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), state, newline, left_margin, color, hotkey_color, map); @@ -167,7 +163,7 @@ inline string int_to_string(const int n) return static_cast( &(ostringstream() << n) )->str(); } -static void set_to_limit(int &value, const int maximum, const int min = 0) +static inline void set_to_limit(int &value, const int maximum, const int min = 0) { if (value < min) value = min; @@ -197,9 +193,9 @@ inline void paint_text(const UIColor color, const int &x, const int &y, const st Screen::paintString(Screen::Pen(' ', color, background), x, y, text); } -static string pad_string(string text, const int size, const bool front = true, const bool trim = false) +static inline string pad_string(string text, const int size, const bool front = true, const bool trim = false) { - if (text.length() > size) + if (text.length() > size_t(size)) { if (trim && size > 10) { @@ -222,7 +218,7 @@ static string pad_string(string text, const int size, const bool front = true, c } } -static df::interface_key get_string_key(const std::set *input) +static inline df::interface_key get_string_key(const std::set *input) { for (auto it = input->begin(); it != input->end(); ++it) { @@ -232,7 +228,7 @@ static df::interface_key get_string_key(const std::set *input return df::interface_key::NONE; } -static char get_string_input(const std::set *input) +static inline char get_string_input(const std::set *input) { return DFHack::Screen::keyToChar(get_string_key(input)); } @@ -241,7 +237,7 @@ static char get_string_input(const std::set *input) * Utility Functions */ -static df::building_stockpilest *get_selected_stockpile() +static inline df::building_stockpilest *get_selected_stockpile() { if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || df::global::ui->main.mode != ui_sidebar_mode::QueryBuilding) @@ -252,7 +248,7 @@ static df::building_stockpilest *get_selected_stockpile() return virtual_cast(df::global::world->selected_building); } -static bool can_trade() +static inline bool can_trade() { if (df::global::ui->caravans.size() == 0) return false; @@ -270,19 +266,19 @@ static bool can_trade() return false; } -static bool is_metal_item(df::item *item) +static inline bool is_metal_item(df::item *item) { MaterialInfo mat(item); return (mat.getCraftClass() == craft_material_class::Metal); } -static bool is_set_to_melt(df::item* item) +static inline bool is_set_to_melt(df::item* item) { return item->flags.bits.melt; } // Copied from Kelly Martin's code -static bool can_melt(df::item* item) +static inline bool can_melt(df::item* item) { df::item_flags bad_flags; @@ -322,6 +318,8 @@ static bool can_melt(df::item* item) } } break; + default: + break; } } @@ -338,12 +336,13 @@ static bool can_melt(df::item* item) class StockpileInfo { public: - StockpileInfo() : id(0), sp(nullptr) + StockpileInfo() : id(0), sp(nullptr), x1(-30000), x2(-30000), y1(-30000), y2(-30000), z(-30000) { } - StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) + StockpileInfo(df::building_stockpilest *sp_) : StockpileInfo() { + sp = sp_; readBuilding(); } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 18090afc6..8396a974e 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -40,7 +40,6 @@ #include "df/plant_raw.h" #include "df/inorganic_raw.h" #include "df/builtin_mats.h" -#include "df/vehicle.h" using std::vector; using std::string; @@ -357,7 +356,9 @@ public: : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), is_active(false), cant_resume_reported(false), low_stock_reported(-1) - {} + { + mat_mask.whole = 0; // see https://github.com/DFHack/dfhack/issues/1047 + } int goalCount() { return config.ival(0); } void setGoalCount(int v) { config.ival(0) = v; } @@ -427,16 +428,16 @@ public: static int fix_job_postings (color_ostream *out, bool dry_run) { int count = 0; - df::job_list_link *link = &world->job_list; + df::job_list_link *link = &world->jobs.list; while (link) { df::job *job = link->item; if (job) { - for (size_t i = 0; i < world->job_postings.size(); ++i) + for (size_t i = 0; i < world->jobs.postings.size(); ++i) { - df::world::T_job_postings *posting = world->job_postings[i]; - if (posting->job == job && i != job->posting_index && !posting->flags.bits.dead) + df::job_handler::T_postings *posting = world->jobs.postings[i]; + if (posting->job == job && i != size_t(job->posting_index) && !posting->flags.bits.dead) { ++count; if (out) @@ -671,7 +672,7 @@ static void check_lost_jobs(color_ostream &out, int ticks) ProtectedJob::cur_tick_idx++; if (ticks < 0) ticks = 0; - df::job_list_link *p = world->job_list.next; + df::job_list_link *p = world->jobs.list.next; for (; p; p = p->next) { df::job *job = p->item; @@ -705,7 +706,7 @@ static void check_lost_jobs(color_ostream &out, int ticks) static void update_job_data(color_ostream &out) { - df::job_list_link *p = world->job_list.next; + df::job_list_link *p = world->jobs.list.next; for (; p; p = p->next) { ProtectedJob *pj = get_known(p->item->id); @@ -793,7 +794,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (item.subtype >= 0) weight += 10000; - df::dfhack_material_category mat_mask(0); + df::dfhack_material_category mat_mask; std::string maskstr = vector_get(tokens,1); if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { out.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); @@ -1031,7 +1032,7 @@ static int cbEnumJobOutputs(lua_State *L) lua_settop(L, 6); - df::dfhack_material_category mat_mask(0); + df::dfhack_material_category mat_mask; if (!lua_isnil(L, 3)) Lua::CheckDFAssign(L, &mat_mask, 3); @@ -1157,21 +1158,6 @@ static bool itemInRealJob(df::item *item) != job_type_class::Hauling; } -static bool isRouteVehicle(df::item *item) -{ - int id = item->getVehicleID(); - if (id < 0) return false; - - auto vehicle = df::vehicle::find(id); - return vehicle && vehicle->route_id >= 0; -} - -static bool isAssignedSquad(df::item *item) -{ - auto &vec = ui->equipment.items_assigned[item->getType()]; - return binsearch_index(vec, &df::item::id, item->id) >= 0; -} - static void map_job_items(color_ostream &out) { for (size_t i = 0; i < constraints.size(); i++) @@ -1286,10 +1272,10 @@ static void map_job_items(color_ostream &out) item->flags.bits.owned || item->flags.bits.in_chest || item->isAssignedToStockpile() || - isRouteVehicle(item) || + Items::isRouteVehicle(item) || itemInRealJob(item) || itemBusy(item) || - isAssignedSquad(item)) + Items::isSquadEquipment(item)) { is_invalid = true; cv->item_inuse_count++; diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 7772968a3..1dfc10dc2 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -72,6 +72,7 @@ #include "df/general_ref_building_civzone_assignedst.h" #include #include +#include "df/unit_relationship_type.h" #include "df/unit_soul.h" #include "df/unit_wound.h" #include "df/viewscreen_dwarfmodest.h" @@ -107,7 +108,6 @@ REQUIRE_GLOBAL(ui_building_assign_items); REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_menu_width); -REQUIRE_GLOBAL(ui_area_map_width); using namespace DFHack::Gui; @@ -449,7 +449,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; - out << "index in units vector: " << FindIndexById(unit->id) << endl; + out << "index in units vector: " << findIndexById(unit->id) << endl; } out << endl; @@ -982,7 +982,7 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building } // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug) - if(unit->relations.pet_owner_id != -1) + if(unit->relationship_ids[df::unit_relationship_type::Pet] != -1) return CR_OK; // check if unit is already pastured or caged, remove refs where necessary @@ -1874,7 +1874,7 @@ command_result df_zone (color_ostream &out, vector & parameters) if (p == "race") { race_filter_set = true; } - } catch (const exception& err) { + } catch (const exception&) { return CR_FAILURE; } } @@ -2073,7 +2073,7 @@ command_result df_zone (color_ostream &out, vector & parameters) if(target_count > 0) { vector units_for_cagezone; - size_t count = 0; + int count = 0; for(auto unit_it = world->units.all.begin(); unit_it != world->units.all.end(); ++unit_it) { df::unit *unit = *unit_it; @@ -2362,8 +2362,8 @@ bool compareUnitAgesYounger(df::unit* i, df::unit* j) int32_t age_j = (int32_t) getAge(j, true); if(age_i == 0 && age_j == 0) { - age_i = i->relations.birth_time; - age_j = j->relations.birth_time; + age_i = i->birth_time; + age_j = j->birth_time; } return (age_i < age_j); } @@ -2373,8 +2373,8 @@ bool compareUnitAgesOlder(df::unit* i, df::unit* j) int32_t age_j = (int32_t) getAge(j, true); if(age_i == 0 && age_j == 0) { - age_i = i->relations.birth_time; - age_j = j->relations.birth_time; + age_i = i->birth_time; + age_j = j->birth_time; } return (age_i > age_j); } @@ -3925,8 +3925,8 @@ public: return; int left_margin = gps->dimx - 30; - int8_t a = *ui_menu_width; - int8_t b = *ui_area_map_width; + int8_t a = (*ui_menu_width)[0]; + int8_t b = (*ui_menu_width)[1]; if ((a == 1 && b > 1) || (a == 2 && b == 2)) left_margin -= 24; diff --git a/reversing/ms_rtti64.idc b/reversing/ms_rtti64.idc new file mode 100644 index 000000000..da26ae916 --- /dev/null +++ b/reversing/ms_rtti64.idc @@ -0,0 +1,1086 @@ +#include + +#define isRef(F) ((F & FF_REF) != 0) +#define hasName(F) ((F & FF_NAME) != 0) + +static GetVtableSize(a) +{ + auto b,c,f; + b = BADADDR; + f = GetFlags(a); + do { + f = GetFlags(a); + if (b == BADADDR) //first entry + { + b=a; + if (!(isRef(f) && (hasName(f) || (f&FF_LABL)))) + { + return 0; + } + } + else if (isRef(f)) //might mean start of next vtable + break; + + if (!hasValue(f) || !isData(f)) + break; + c = Qword(a); + if (c) + { + f = GetFlags(c); + if (!hasValue(f) || !isCode(f) || Dword(c)==0) + break; + } + a = a+8; + } + while (1); + if (b!=BADADDR) + { + c = (a-b)/8; + return c; + } + else + { + return 0; + } +} + +static Unknown( ea, length ) +{ + auto i; + if (ea==BADADDR) + return; + for(i=0; i < length; i++) + { + MakeUnkn(ea+i,0); + } +} + +static DwordRel(x) +{ + x = Dword(x) + 0x140000000; + return x; +} + +static ForceQword( x ) { //Make dword, undefine as needed + if (x==BADADDR || x==0) + return; + if (!MakeQword( x )) + { + Unknown(x,8); + MakeQword(x); + } +} + +static ForceDword( x ) { //Make dword, undefine as needed + if (x==BADADDR || x==0) + return; + if (!MakeDword( x )) + { + Unknown(x,4); + MakeDword(x); + } +} + +static SoftOff64 ( x ) { //Make 64-bit offset if !=0 + if (x==BADADDR || x==0) + return; + ForceQword(x); + if (Qword(x)>0 && Qword(x)<=MaxEA()) OpOff(x, 0, 0); +} + +static SoftOff ( x ) { //Make 32-bit base-relative offset if !=0 + if (x==BADADDR || x==0) + return; + ForceDword(x); + if (Dword(x)>0 && Dword(x)<=MaxEA()) OpOffEx(x,0,REF_OFF32|REFINFO_RVA|REFINFO_SIGNEDOP, -1, 0, 0); +} + +//check if pointer is to typeinfo record and extract the type name from it +static GetTypeName(vtbl) +{ + auto x; + if (vtbl==BADADDR) + return; + x = DwordRel(vtbl+12); + if ((!x) || (x==BADADDR)) return ""; + return GetAsciizStr(x+16); +} + +static DwordCmt(x, cmt) +{ + if (x==BADADDR || x==0) + return; + ForceDword(x); + MakeComm(x, cmt); +} +static OffCmt(x, cmt) +{ + if (x==BADADDR || x==0) + return; + SoftOff(x); + if (cmt != "") + MakeComm(x, cmt); +} +static OffCmt64(x, cmt) +{ + if (x==BADADDR || x==0) + return; + SoftOff64(x); + MakeComm(x, cmt); +} +static StrCmt(x, cmt) +{ + auto save_str; + if (x==BADADDR || x==0) + return; + MakeUnkn(x, 0); + save_str = GetLongPrm(INF_STRTYPE); + SetLongPrm(INF_STRTYPE,0); + MakeStr(x, BADADDR); + MakeName(x, ""); + MakeComm(x, cmt); + SetLongPrm(INF_STRTYPE,save_str); +} +static DwordArrayCmt(x, n, cmt) +{ + if (x==BADADDR || x==0) + return; + Unknown(x,4*n); + ForceDword(x); + MakeArray(x,n); + MakeComm(x, cmt); +} + +//check if values match a pattern +static matchBytes(addr,match) +{ + auto i,len,s; + len = strlen(match); + if (len%2) + { + Warning("Bad match string in matchBytes: %s",match); + return 0; + } + i=0; + while (i 2147483647) x = x - 4294967296; + if (x<0) + { + sign = 1; + x = -x; + } + if (x==0) + return "A@"; + else if (x<=10) + return form("%s%d",sign?"?":"",x-1); + else + { + while (x>0) + { + s = form("%c%s",'A'+x%16,s); + x = x / 16; + } + return sign?"?":""+s+"@"; + } +} +static Parse_BCD(x, indent) +{ + auto indent_str,i,a,s; + if (x==BADADDR || x==0) + return; + indent_str="";i=0; + while(i0) //check numContainedBases + DumpNestedClass(a+4, indent+1, n); //nested classes following + a=a+4*(n+1); + i=i+n+1; + } +} + +static Parse_CHD(x, indent) +{ + auto indent_str,i,a,n,p,s; + if (x==BADADDR || x==0) + return; + indent_str="";i=0; + while(i ea ) // InSort + { + for ( ; idx != -1; idx = GetNextIndex(AR_LONG, id, idx) ) + { + val = GetArrayElement(AR_LONG, id, idx); + SetArrayLong(id, idx, ea); + ea = val; + } + } + } + SetArrayLong(id, GetLastIndex(AR_LONG, id) + 1, ea); +} +static getArraySize(id) +{ + auto idx, count; + count = 0; + for ( idx = GetFirstIndex(AR_LONG, id); idx != -1; idx = GetNextIndex(AR_LONG, id, idx) ) + { + count++; + } + return count; +} + +static doAddrList(name) +{ + auto idx, id, val, ctr, dtr; + id = GetArrayId("AddrList"); + ctr = 0; dtr = 0; + if ( name!=0 && id != -1 ) + { + if (getArraySize(id)!=2) + return; + for ( idx = GetFirstIndex(AR_LONG, id); idx != -1; idx = GetNextIndex(AR_LONG, id, idx) ) + { + val = GetArrayElement(AR_LONG, id, idx); + if (Byte(val)==0xE9) + val = getRelJmpTarget(val); + if ((substr(Name(val),0,3)=="??1")) + dtr = val; + else + ctr = val; + } + } + if (ctr!=0 && dtr!=0) + { + MakeName(ctr, MakeSpecialName(name,SN_constructor,0)); + } + DeleteArray(GetArrayId("AddrList")); +} + +//check if there's a vtable at a and dump into to f +//returns position after the end of vtable +static DoVtable(a) +{ + auto x,y,s,p,q,i,name; + + //check if it looks like a vtable + y = GetVtableSize(a); + if (y == 0) + return a+8; + + //check if it's named as a vtable + name = Name(a); + if (substr(name,0,4)!="??_7") name=0; + + x = Qword(a-8); + //otherwise try to get it from RTTI + if (IsValidCOL(x)) + { + Parse_Vtable(a); + if (name==0) + name = GetVtblName(x); + MakeName(a, name); + } + if (name!=0) + { + name = ".?AV"+substr(name, 4, strstr(name,"@@6B")+2); + } + { + DeleteArray(GetArrayId("AddrList")); + q = 0; i = 1; + for ( x=DfirstB(a); x != BADADDR; x=DnextB(a,x) ) + { + p = funcStart(x); + if (p!=-1) + { + if (q==p) + i++; + else + { + if (q) { + AddAddr(q); + } + i = 1; + q = p; + } + } + } + if (q) + { + AddAddr(q); + } + + x = a; + while (y>0) + { + p = Dword(x); + if (GetFunctionFlags(p) == -1) + { + MakeCode(p); + MakeFunction(p, BADADDR); + } + checkSDD(p,name,a,0); + y--; + x = x+8; + } + doAddrList(name); + } + return x; +} + +static scan_for_vtables(void) +{ + auto rmin, rmax, cmin, cmax, s, a, x, y; + s = FirstSeg(); + rmin = 0; rmax = 0; + while (s!=BADADDR) + { + if (SegName(s)==".rdata") + { + rmin = s; + rmax = NextSeg(s); + } + else if (SegName(s)==".text") + { + cmin = s; + cmax = NextSeg(s); + } + s = NextSeg(s); + } + if (rmin==0) {rmin=cmin; rmax=cmax;} + a = rmin; + while (a=cmin && x + //74 07 jz short @@no_free + //56 push esi + //E8 CA 2D 0D 00 call operator delete(void *) + //59 pop ecx + // @@no_free: + //8B C6 mov eax, esi + //5E pop esi + //C2 04 00 retn 4 + t = SN_scalardtr; + } + else if (matchBytes(x,"538A5C2408568BF1F6C302742B8B46FC578D7EFC68????????506A??56E8") || + matchBytes(x,"538A5C2408F6C302568BF1742E8B46FC5768????????8D7EFC5068????????56E8")) + { + //53 push ebx + //8A 5C 24 08 mov bl, [esp+arg_0] + //56 push esi + //8B F1 mov esi, ecx + //F6 C3 02 test bl, 2 + //74 2B jz short loc_100037F8 + //8B 46 FC mov eax, [esi-4] + //57 push edi + //8D 7E FC lea edi, [esi-4] + //68 xx xx xx xx push offset class::~class(void) + //50 push eax + //6A xx push xxh + //56 push esi + //E8 xx xx xx xx call `eh vector destructor iterator'(void *,uint,int,void (*)(void *)) + t = SN_vectordtr; + if (name!=0) + a = Dword(x+21); + if (gate && Byte(a)==0xE9) + { + a = getRelJmpTarget(a); + } + } + + if (t>0) + { + if (t==SN_vectordtr) + s = "vector"; + else + s = "scalar"; + if (name!=0) + MakeName(x, MakeSpecialName(name,t,0)); + if (a!=BADADDR) + { + if (name!=0) + MakeName(a, MakeSpecialName(name,SN_vdestructor,0)); + } + CommentStack(x, 4, "__flags$",-1); + } + return t; +} + +static main(void) +{ + scan_for_vtables(); +} \ No newline at end of file diff --git a/scripts b/scripts new file mode 160000 index 000000000..0b6e4a563 --- /dev/null +++ b/scripts @@ -0,0 +1 @@ +Subproject commit 0b6e4a56392e125c01e24694ddd880bbc652451d diff --git a/scripts/3rdparty/dscorbett b/scripts/3rdparty/dscorbett deleted file mode 160000 index 4353c1040..000000000 --- a/scripts/3rdparty/dscorbett +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4353c10401ced7aec89f002947d1252f30237789 diff --git a/scripts/3rdparty/kane-t b/scripts/3rdparty/kane-t deleted file mode 160000 index 0a75d5ff6..000000000 --- a/scripts/3rdparty/kane-t +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0a75d5ff69916cf9b3739f4b20d36ab4cfdcf824 diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor deleted file mode 160000 index 26c600132..000000000 --- a/scripts/3rdparty/lethosor +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 26c60013223e02b5558e31bed8e0625495430995 diff --git a/scripts/3rdparty/maienm b/scripts/3rdparty/maienm deleted file mode 160000 index 45c78449e..000000000 --- a/scripts/3rdparty/maienm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 45c78449e71d1ba263044fb00108509088ad0026 diff --git a/scripts/3rdparty/maxthyme b/scripts/3rdparty/maxthyme deleted file mode 160000 index b337e931b..000000000 --- a/scripts/3rdparty/maxthyme +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b337e931b8b7a167ee5ce1ac6b5c3155c291f260 diff --git a/scripts/3rdparty/roses b/scripts/3rdparty/roses deleted file mode 160000 index 4b6e77265..000000000 --- a/scripts/3rdparty/roses +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b6e772654df6805b66f77900a4618bbf9b54dab diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt deleted file mode 100644 index e9d899970..000000000 --- a/scripts/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -include(Scripts.cmake) -DFHACK_3RDPARTY_SCRIPT_REPO(dscorbett) -DFHACK_3RDPARTY_SCRIPT_REPO(kane-t) -DFHACK_3RDPARTY_SCRIPT_REPO(lethosor) -DFHACK_3RDPARTY_SCRIPT_REPO(maienm) -DFHACK_3RDPARTY_SCRIPT_REPO(maxthyme) -# DFHACK_3RDPARTY_SCRIPT_REPO(roses) - -install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts - DESTINATION ${DFHACK_DATA_DESTINATION} - FILES_MATCHING PATTERN "*.lua" - PATTERN "*.rb" - PATTERN "3rdparty" EXCLUDE - ) diff --git a/scripts/Scripts.cmake b/scripts/Scripts.cmake deleted file mode 100644 index 3e7de5424..000000000 --- a/scripts/Scripts.cmake +++ /dev/null @@ -1,19 +0,0 @@ -include(../plugins/Plugins.cmake) - -MACRO(DFHACK_SCRIPTS) - PARSE_ARGUMENTS(SCRIPT - "SUBDIRECTORY" - "SOME_OPT" - ${ARGN} - ) - CAR(SCRIPT_SUBDIRECTORY ${SCRIPT_SUBDIRECTORY}) - install(FILES ${SCRIPT_DEFAULT_ARGS} - DESTINATION ${DFHACK_DATA_DESTINATION}/scripts/${SCRIPT_SUBDIRECTORY}) -ENDMACRO() - -MACRO(DFHACK_3RDPARTY_SCRIPT_REPO repo_path) - if(NOT EXISTS ${dfhack_SOURCE_DIR}/scripts/3rdparty/${repo_path}/CMakeLists.txt) - MESSAGE(SEND_ERROR "Script submodule scripts/3rdparty/${repo_path} does not exist - run `git submodule update --init`.") - endif() - add_subdirectory(3rdparty/${repo_path}) -ENDMACRO() diff --git a/scripts/about.txt b/scripts/about.txt deleted file mode 100644 index 818891af5..000000000 --- a/scripts/about.txt +++ /dev/null @@ -1,2 +0,0 @@ -Basic scripts are not stored in any subdirectory, and can be invoked directly. -They are generally useful tools for any player. diff --git a/scripts/adaptation.rb b/scripts/adaptation.rb deleted file mode 100644 index 50cf80a86..000000000 --- a/scripts/adaptation.rb +++ /dev/null @@ -1,104 +0,0 @@ -# View or set cavern adaptation levels -# based on removebadthoughts.rb -=begin - -adaptation -========== -View or set level of cavern adaptation for the selected unit or the whole fort. -Usage: ``adaptation [value]``. The ``value`` must be -between 0 and 800,000 inclusive. - -=end - -# Color constants, values mapped to color_value enum in include/ColorText.h -COLOR_GREEN = 2 -COLOR_RED = 4 -COLOR_YELLOW = 14 -COLOR_WHITE = 15 - -def usage(s) - if nil != s - puts(s) - end - puts "Usage: adaptation [value]" - throw :script_finished -end - -mode = $script_args[0] || 'help' -who = $script_args[1] -value = $script_args[2] - -if 'help' == mode - usage(nil) -elsif 'show' != mode && 'set' != mode - usage("Invalid mode '#{mode}': must be either 'show' or 'set'") -end - -if nil == who - usage("Target not specified") -elsif 'him' != who && 'all' != who - usage("Invalid target '#{who}'") -end - -if 'set' == mode - if nil == value - usage("Value not specified") - elsif !/[[:digit:]]/.match(value) - usage("Invalid value '#{value}'") - end - - if 0 > value.to_i || 800000 < value.to_i - usage("Value must be between 0 and 800000") - end - value = value.to_i -end - -num_set = 0 - -set_adaptation_value = lambda { |u,v| - next if !df.unit_iscitizen(u) - next if u.flags1.dead - u.status.misc_traits.each { |t| - if t.id == :CaveAdapt - # TBD: expose the color_ostream console and color values of - # t.value based on adaptation level - if mode == 'show' - if df.respond_to?(:print_color) - print "Unit #{u.id} (#{u.name}) has an adaptation of " - case t.value - when 0..399999 - #df.print_color(COLOR_GREEN, "#{t.value}\n") - print "#{t.value}\n" - when 400000..599999 - df.print_color(COLOR_YELLOW, "#{t.value}\n") - else - df.print_color(COLOR_RED, "#{t.value}\n") - end - else - puts "Unit #{u.id} (#{u.name}) has an adaptation of #{t.value}" - end - elsif mode == 'set' - puts "Unit #{u.id} (#{u.name}) changed from #{t.value} to #{v}" - t.value = v - num_set += 1 - end - end - } -} - -case who -when 'him' - if u = df.unit_find - set_adaptation_value[u,value] - else - puts 'Please select a dwarf ingame' - end -when 'all' - df.unit_citizens.each { |uu| - set_adaptation_value[uu,value] - } -end - -if 'set' == mode - puts "#{num_set} unit#{'s' if num_set != 1} updated." -end diff --git a/scripts/add-thought.lua b/scripts/add-thought.lua deleted file mode 100644 index 0f82b555c..000000000 --- a/scripts/add-thought.lua +++ /dev/null @@ -1,93 +0,0 @@ --- Adds emotions to creatures. ---@ module = true - ---[[=begin - -add-thought -=========== -Adds a thought or emotion to the selected unit. Can be used by other scripts, -or the gui invoked by running ``add-thought gui`` with a unit selected. - -=end]] - -local utils=require('utils') - -function addEmotionToUnit(unit,thought,emotion,severity,strength,subthought) - local emotions=unit.status.current_soul.personality.emotions - if not (type(emotion)=='number') then emotion=df.emotion_type[emotion] end - if not (type(thought)=='number') then thought=df.unit_thought_type[thought] end - emotions:insert('#',{new=df.unit_personality.T_emotions, - type=emotion, - unk2=1, - strength=strength, - thought=thought, - subthought=subthought, - severity=severity, - flags=0, - unk7=0, - year=df.global.cur_year, - year_tick=df.global.cur_year_tick - }) - local divider=df.emotion_type.attrs[emotion].divider - if divider~=0 then - unit.status.current_soul.personality.stress_level=unit.status.current_soul.personality.stress_level+math.ceil(severity/df.emotion_type.attrs[emotion].divider) - end -end - -validArgs = validArgs or utils.invert({ - 'unit', - 'thought', - 'emotion', - 'severity', - 'strength', - 'subthought', - 'gui' -}) - -function tablify(iterableObject) - t={} - for k,v in ipairs(iterableObject) do - t[k] = v~=nil and v or 'nil' - end - return t -end - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) - -local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) - -if not unit then qerror('A unit must be specified or selected.') end -if args.gui then - local script=require('gui.script') - script.start(function() - local tok,thought=script.showListPrompt('emotions','Which thought?',COLOR_WHITE,tablify(df.unit_thought_type),10,true) - if tok then - local eok,emotion=script.showListPrompt('emotions','Which emotion?',COLOR_WHITE,tablify(df.emotion_type),10,true) - if eok then - local sok,severity=script.showInputPrompt('emotions','At what severity?',COLOR_WHITE,'0') - if sok then - local stok,strength=script.showInputPrompt('emotions','At what strength?',COLOR_WHITE,'0') - if stok then - addEmotionToUnit(unit,thought,emotion,severity,strength,0) - end - end - end - end - end) -else - local thought = args.thought or 180 - - local emotion = args.emotion or -1 - - local severity = args.severity or 0 - - local subthought = args.subthought or 0 - - local strength = args.strength or 0 - - addEmotionToUnit(unit,thought,emotion,severity,strength,subthought) -end diff --git a/scripts/armoks-blessing.lua b/scripts/armoks-blessing.lua deleted file mode 100644 index dfa99daad..000000000 --- a/scripts/armoks-blessing.lua +++ /dev/null @@ -1,202 +0,0 @@ --- Adjust all attributes of all dwarves to an ideal --- by vjek ---[[=begin - -armoks-blessing -=============== -Runs the equivalent of `rejuvenate`, `elevate-physical`, `elevate-mental`, and -`brainwash` on all dwarves currently on the map. This is an extreme change, -which sets every stat to an ideal - legendary skills, great traits, and -easy-to-satisfy preferences. - -Without arguments, all attributes, age & personalities are adjusted. -Arguments allow for skills to be adjusted as well. - -=end]] -function rejuvenate(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local current_year=df.global.cur_year - local newbirthyear=current_year - 20 - if unit.relations.birth_year < newbirthyear then - unit.relations.birth_year=newbirthyear - end - if unit.relations.old_year < current_year+100 then - unit.relations.old_year=current_year+100 - end - -end --- --------------------------------------------------------------------------- -function brainwash_unit(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local profile ={75,25,25,75,25,25,25,99,25,25,25,50,75,50,25,75,75,50,75,75,25,75,75,50,75,25,50,25,75,75,75,25,75,75,25,75,25,25,75,75,25,75,75,75,25,75,75,25,25,50} - local i - - for i=1, #profile do - unit.status.current_soul.personality.traits[i-1]=profile[i] - end - -end --- --------------------------------------------------------------------------- -function elevate_attributes(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local ok,f,t,k = pcall(pairs,unit.status.current_soul.mental_attrs) - if ok then - for k,v in f,t,k do - v.value=v.max_value - end - end - - local ok,f,t,k = pcall(pairs,unit.body.physical_attrs) - if ok then - for k,v in f,t,k do - v.value=v.max_value - end - end -end --- --------------------------------------------------------------------------- --- this function will return the number of elements, starting at zero. --- useful for counting things where #foo doesn't work -function count_this(to_be_counted) - local count = -1 - local var1 = "" - while var1 ~= nil do - count = count + 1 - var1 = (to_be_counted[count]) - end - count=count-1 - return count -end --- --------------------------------------------------------------------------- -function make_legendary(skillname,unit) - local skillnamenoun,skillnum - - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - if (df.job_skill[skillname]) then - skillnamenoun = df.job_skill.attrs[df.job_skill[skillname]].caption_noun - else - print ("The skill name provided is not in the list.") - return - end - - if skillnamenoun ~= nil then - utils = require 'utils' - skillnum = df.job_skill[skillname] - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = skillnum, rating = 20 }, 'id') - print (unit.name.first_name.." is now a Legendary "..skillnamenoun) - else - print ("Empty skill name noun, bailing out!") - return - end -end --- --------------------------------------------------------------------------- -function BreathOfArmok(unit) - - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - local i - - local count_max = count_this(df.job_skill) - utils = require 'utils' - for i=0, count_max do - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - print ("The breath of Armok has engulfed "..unit.name.first_name) -end --- --------------------------------------------------------------------------- -function LegendaryByClass(skilltype,v) - unit=v - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - utils = require 'utils' - local i - local skillclass - local count_max = count_this(df.job_skill) - for i=0, count_max do - skillclass = df.job_skill_class[df.job_skill.attrs[i].type] - if skilltype == skillclass then - print ("Skill "..df.job_skill.attrs[i].caption.." is type: "..skillclass.." and is now Legendary for "..unit.name.first_name) - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - end -end --- --------------------------------------------------------------------------- -function PrintSkillList() - local count_max = count_this(df.job_skill) - local i - for i=0, count_max do - print("'"..df.job_skill.attrs[i].caption.."' "..df.job_skill[i].." Type: "..df.job_skill_class[df.job_skill.attrs[i].type]) - end - print ("Provide the UPPER CASE argument, for example: PROCESSPLANTS rather than Threshing") -end --- --------------------------------------------------------------------------- -function PrintSkillClassList() - local i - local count_max = count_this(df.job_skill_class) - for i=0, count_max do - print(df.job_skill_class[i]) - end - print ("Provide one of these arguments, and all skills of that type will be made Legendary") - print ("For example: Medical will make all medical skills legendary") -end --- --------------------------------------------------------------------------- -function adjust_all_dwarves(skillname) - for _,v in ipairs(df.global.world.units.all) do - if v.race == df.global.ui.race_id then - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(v))) - brainwash_unit(v) - elevate_attributes(v) - rejuvenate(v) - if skillname then - if skillname=="Normal" or skillname=="Medical" or skillname=="Personal" or skillname=="Social" or skillname=="Cultural" or skillname=="MilitaryWeapon" or skillname=="MilitaryAttack" or skillname=="MilitaryDefense" or skillname=="MilitaryMisc" then - LegendaryByClass(skillname,v) - elseif skillname=="all" then - BreathOfArmok(v) - else - make_legendary(skillname,v) - end - end - end - end -end --- --------------------------------------------------------------------------- --- main script operation starts here --- --------------------------------------------------------------------------- -local opt = ... -local skillname - -if opt then - if opt=="list" then - PrintSkillList() - return - end - if opt=="classes" then - PrintSkillClassList() - return - end - skillname = opt -else - print ("No skillname supplied, no skills will be adjusted. Pass argument 'list' to see a skill list, 'classes' to show skill classes, or use 'all' if you want all skills legendary.") -end - -adjust_all_dwarves(skillname) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb deleted file mode 100644 index 1bf07c599..000000000 --- a/scripts/autofarm.rb +++ /dev/null @@ -1,192 +0,0 @@ -# Select crops to plant based on current stocks -=begin - -autofarm -======== -Automatically handle crop selection in farm plots based on current plant stocks. -Selects a crop for planting if current stock is below a threshold. -Selected crops are dispatched on all farmplots. - -Usage:: - - autofarm start - autofarm default 30 - autofarm threshold 150 helmet_plump tail_pig - -=end -class AutoFarm - - def initialize - @thresholds = Hash.new(50) - @lastcounts = Hash.new(0) - end - - def setthreshold(id, v) - list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id } - if tok = df.match_rawname(id, list) - @thresholds[tok] = v.to_i - else - puts "No plant with id #{id}, try one of " + - list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ') - end - end - - def setdefault(v) - @thresholds.default = v.to_i - end - - def is_plantable(plant) - has_seed = plant.flags[:SEED] - season = df.cur_season - harvest = df.cur_season_tick + plant.growdur * 10 - will_finish = harvest < 10080 - can_plant = has_seed && plant.flags[season] - can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) - can_plant - end - - def find_plantable_plants - plantable = {} - counts = Hash.new(0) - - df.world.items.other[:SEEDS].each { |i| - if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && - !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && - !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact) - counts[i.mat_index] += i.stack_size - end - } - - counts.keys.each { |i| - if df.ui.tasks.discovered_plants[i] - plant = df.world.raws.plants.all[i] - if is_plantable(plant) - plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0) - plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0) - end - end - } - - return plantable - end - - def set_farms(plants, farms) - return if farms.length == 0 - if plants.length == 0 - plants = [-1] - end - - season = df.cur_season - - farms.each_with_index { |f, idx| - f.plant_id[season] = plants[idx % plants.length] - } - end - - def process - plantable = find_plantable_plants - @lastcounts = Hash.new(0) - - df.world.items.other[:PLANT].each { |i| - if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && - !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && - !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact && plantable.has_key?(i.mat_index)) - id = df.world.raws.plants.all[i.mat_index].id - @lastcounts[id] += i.stack_size - end - } - - return unless @running - - plants_s = [] - plants_u = [] - - plantable.each_key { |k| - plant = df.world.raws.plants.all[k] - if (@lastcounts[plant.id] < @thresholds[plant.id]) - plants_s.push(k) if plantable[k] == :Surface - plants_u.push(k) if plantable[k] == :Underground - end - } - - farms_s = [] - farms_u = [] - df.world.buildings.other[:FARM_PLOT].each { |f| - if (f.flags.exists) - underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean - farms_s.push(f) unless underground - farms_u.push(f) if underground - end - } - - set_farms(plants_s, farms_s) - set_farms(plants_u, farms_u) - end - - def start - return if @running - @onupdate = df.onupdate_register('autofarm', 1200) { process } - @running = true - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - stat = @running ? "Running." : "Stopped." - @lastcounts.each { |k,v| - stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}" - } - @thresholds.each { |k,v| - stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k) - } - stat << "\nDefault: #{@thresholds.default}" - stat - end - -end - -$AutoFarm ||= AutoFarm.new - -case $script_args[0] -when 'start', 'enable' - $AutoFarm.start - puts $AutoFarm.status - -when 'end', 'stop', 'disable' - $AutoFarm.stop - puts 'Stopped.' - -when 'default' - $AutoFarm.setdefault($script_args[1]) - -when 'threshold' - t = $script_args[1] - $script_args[2..-1].each {|i| - $AutoFarm.setthreshold(i, t) - } - -when 'delete' - $AutoFarm.stop - $AutoFarm = nil - -when 'help', '?' - puts < 0 - puts "Unsuspended #{count} job(s)." - df.process_jobs = true - end - end - - def start - @running = true - @onupdate = df.onupdate_register('autounsuspend', 5) { process if @running } - end - - def stop - @running = false - df.onupdate_unregister(@onupdate) - end -end - -case $script_args[0] -when 'start' - $AutoUnsuspend ||= AutoUnsuspend.new - $AutoUnsuspend.start - -when 'end', 'stop' - $AutoUnsuspend.stop - -else - puts $AutoUnsuspend && $AutoUnsuspend.running ? 'Running.' : 'Stopped.' -end diff --git a/scripts/ban-cooking.rb b/scripts/ban-cooking.rb deleted file mode 100644 index 535f39126..000000000 --- a/scripts/ban-cooking.rb +++ /dev/null @@ -1,132 +0,0 @@ -# convenient way to ban cooking categories of food -=begin - -ban-cooking -=========== -A more convenient way to ban cooking various categories of foods than the -kitchen interface. Usage: ``ban-cooking ``. Valid types are ``booze``, -``honey``, ``tallow``, ``oil``, ``seeds`` (non-tree plants with seeds), -``brew``, ``mill``, ``thread``, and ``milk``. - -=end - -already_banned = {} -kitchen = df.ui.kitchen -kitchen.item_types.length.times { |i| - already_banned[[kitchen.mat_types[i], kitchen.mat_indices[i], kitchen.item_types[i], kitchen.item_subtypes[i]]] = kitchen.exc_types[i] & 1 -} -ban_cooking = lambda { |mat_type, mat_index, type| - subtype = -1 - key = [mat_type, mat_index, type, subtype] - if already_banned[key] - next if already_banned[key] == 1 - - index = kitchen.mat_types.zip(kitchen.mat_indices, kitchen.item_types, kitchen.item_subtypes) - kitchen.exc_types[index] |= 1 - already_banned[key] = 1 - next - end - df.ui.kitchen.mat_types << mat_type - df.ui.kitchen.mat_indices << mat_index - df.ui.kitchen.item_types << type - df.ui.kitchen.item_subtypes << subtype - df.ui.kitchen.exc_types << 1 - already_banned[key] = 1 -} - -$script_args.each do |arg| - case arg - when 'booze' - df.world.raws.plants.all.each_with_index do |p, i| - p.material.each_with_index do |m, j| - if m.flags[:ALCOHOL] - ban_cooking[j + DFHack::MaterialInfo::PLANT_BASE, i, :DRINK] - end - end - end - df.world.raws.creatures.all.each_with_index do |c, i| - c.material.each_with_index do |m, j| - if m.flags[:ALCOHOL] - ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :DRINK] - end - end - end - - when 'honey' - # hard-coded in the raws of the mead reaction - honey = df.decode_mat('CREATURE:HONEY_BEE:HONEY') - ban_cooking[honey.mat_type, honey.mat_index, :LIQUID_MISC] - - when 'tallow' - df.world.raws.creatures.all.each_with_index do |c, i| - c.material.each_with_index do |m, j| - if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SOAP_MAT') - ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :GLOB] - end - end - end - - when 'oil' - df.world.raws.plants.all.each_with_index do |p, i| - p.material.each_with_index do |m, j| - if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SOAP_MAT') - ban_cooking[j + DFHack::MaterialInfo::PLANT_BASE, i, :LIQUID_MISC] - end - end - end - - when 'seeds' - df.world.raws.plants.all.each do |p| - m = df.decode_mat(p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat).material - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SEED_MAT') - - if not p.flags[:TREE] - p.growths.each do |g| - m = df.decode_mat(g).material - ban_cooking[g.mat_type, g.mat_index, :PLANT_GROWTH] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SEED_MAT') - end - end - end - - when 'brew' - df.world.raws.plants.all.each do |p| - m = df.decode_mat(p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat).material - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('DRINK_MAT') - - p.growths.each do |g| - m = df.decode_mat(g).material - ban_cooking[g.mat_type, g.mat_index, :PLANT_GROWTH] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('DRINK_MAT') - end - end - - when 'mill' - df.world.raws.plants.all.each do |p| - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.flags[:MILL] - end - - when 'thread' - df.world.raws.plants.all.each do |p| - ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.flags[:THREAD] - end - - when 'milk' - df.world.raws.creatures.all.each_with_index do |c, i| - c.material.each_with_index do |m, j| - if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('CHEESE_MAT') - ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :LIQUID_MISC] - end - end - end - - else - puts "ban-cooking booze - bans cooking of drinks" - puts "ban-cooking honey - bans cooking of honey bee honey" - puts "ban-cooking tallow - bans cooking of tallow" - puts "ban-cooking oil - bans cooking of oil" - puts "ban-cooking seeds - bans cooking of plants that have seeds (tree seeds don't count)" - puts "ban-cooking brew - bans cooking of plants that can be brewed into alcohol" - puts "ban-cooking mill - bans cooking of plants that can be milled into powder" - puts "ban-cooking thread - bans cooking of plants that can be turned into thread" - puts "ban-cooking milk - bans cooking of creature liquids that can be turned into cheese" - end -end diff --git a/scripts/binpatch.lua b/scripts/binpatch.lua deleted file mode 100644 index ac7705250..000000000 --- a/scripts/binpatch.lua +++ /dev/null @@ -1,51 +0,0 @@ --- Apply or remove binary patches at runtime. ---[[=begin - -binpatch -======== -Implements functions for in-memory binpatches. See `binpatches`. - -=end]] - -local bp = require('binpatch') - -function run_command(cmd,name) - local pfix = name..': ' - - local patch, err = bp.load_dif_file(name) - if not patch then - dfhack.printerr(pfix..err) - return - end - - if cmd == 'check' then - local status, addr = patch:status() - if status == 'conflict' then - dfhack.printerr(string.format('%sconflict at address %x', pfix, addr)) - else - print(pfix..'patch is '..status) - end - elseif cmd == 'apply' then - local ok, msg = patch:apply() - if ok then - print(pfix..msg) - else - dfhack.printerr(pfix..msg) - end - elseif cmd == 'remove' then - local ok, msg = patch:remove() - if ok then - print(pfix..msg) - else - dfhack.printerr(pfix..msg) - end - else - qerror('Invalid command: '..cmd) - end -end - -local cmd,name = ... -if not cmd or not name then - qerror('Usage: binpatch check/apply/remove ') -end -run_command(cmd, name) diff --git a/scripts/brainwash.lua b/scripts/brainwash.lua deleted file mode 100644 index b28f648f7..000000000 --- a/scripts/brainwash.lua +++ /dev/null @@ -1,70 +0,0 @@ --- Brainwash a dwarf, modifying their personality --- usage is: target a unit in DF, and execute this script in dfhack --- by vjek ---[[=begin - -brainwash -========= -Modify the personality traits of the selected dwarf to match an 'ideal' -personality - as stable and reliable as possible. This makes dwarves very -stable, preventing tantrums even after months of misery. - -=end]] - -function brainwash_unit(profile) - local i,unit_name - - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - - unit_name=dfhack.TranslateName(dfhack.units.getVisibleName(unit)) - - print("Previous personality values for "..unit_name) - printall(unit.status.current_soul.personality.traits) - - --now set new personality - for i=1, #profile do - unit.status.current_soul.personality.traits[i-1]=profile[i] - end - - print("New personality values for "..unit_name) - printall(unit.status.current_soul.personality.traits) - print(unit_name.." has been brainwashed, praise Armok!") -end - --- main script starts here --- profiles are listed here and passed to the brainwash function --- -local baseline={50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50} -local ideal={75,25,25,75,25,25,25,99,25,25,25,50,75,50,25,75,75,50,75,75,25,75,75,50,75,25,50,25,75,75,75,25,75,75,25,75,25,25,75,75,25,75,75,75,25,75,75,25,25,50} -local stepford={99,1,1,99,1,1,1,99,1,1,1,1,50,50,1,99,99,50,50,50,1,1,99,50,50,50,50,50,50,99,50,1,1,99,1,99,1,1,99,99,1,99,99,99,1,50,50,1,1,1} -local wrecked={1,99,99,1,99,99,99,1,99,99,99,1,1,99,99,1,1,1,1,1,99,1,1,99,1,99,99,99,1,1,1,99,1,1,99,1,99,99,1,1,99,1,1,1,99,1,1,99,99,99} -local opt = ... - -if opt then - if opt=="ideal" then - brainwash_unit(ideal) - return - end - if opt=="baseline" then - brainwash_unit(baseline) - return - end - if opt=="stepford" then - brainwash_unit(stepford) - return - end - if opt=="wrecked" then - brainwash_unit(wrecked) - return - end -else - print ("Invalid or missing personality argument.\nValid choices are ideal , baseline , stepford, and wrecked.") - print ("ideal will create a reliable dwarf with generally positive personality traits.") - print ("baseline will reset all personality traits to a default / the average.") - print ("stepford amplifies all good qualities to an excessive degree.") - print ("wrecked amplifies all bad qualities to an excessive degree.") -end diff --git a/scripts/burial.lua b/scripts/burial.lua deleted file mode 100644 index 3023f9128..000000000 --- a/scripts/burial.lua +++ /dev/null @@ -1,27 +0,0 @@ --- allows burial in unowned coffins --- by Putnam https://gist.github.com/Putnam3145/e7031588f4d9b24b9dda ---[[=begin - -burial -====== -Sets all unowned coffins to allow burial. ``burial -pets`` also allows burial -of pets. - -=end]] - -local utils=require('utils') - -validArgs = validArgs or utils.invert({ - 'pets' -}) - -local args = utils.processArgs({...}, validArgs) - -for k,v in ipairs(df.global.world.buildings.other.COFFIN) do - if v.owner_id==-1 then - v.burial_mode.allow_burial=true - if not args.pets then - v.burial_mode.no_pets=true - end - end -end \ No newline at end of file diff --git a/scripts/colonies.lua b/scripts/colonies.lua deleted file mode 100644 index 2e755ea18..000000000 --- a/scripts/colonies.lua +++ /dev/null @@ -1,82 +0,0 @@ --- List, create, or change wild colonies (eg honey bees) --- By PeridexisErrant and Warmist - -local help = [[=begin - -colonies -======== -List vermin colonies, place honey bees, or convert all vermin -to honey bees. Usage: - -:colonies: List all vermin colonies on the map. -:colonies place: Place a honey bee colony under the cursor. -:colonies convert: Convert all existing colonies to honey bees. - -The ``place`` and ``convert`` subcommands by default create or -convert to honey bees, as this is the most commonly useful. -However both accept an optional flag to use a different vermin -type, for example ``colonies place ANT`` creates an ant colony -and ``colonies convert TERMITE`` ends your beekeeping industry. - -=end]] - -function findVermin(target_verm) - for k,v in pairs(df.global.world.raws.creatures.all) do - if v.creature_id == target_verm then - return k - end - end - qerror("No vermin found with name: "..target_verm) -end - -function list_colonies() - for idx, col in pairs(df.global.world.vermin.colonies) do - race = df.global.world.raws.creatures.all[col.race].creature_id - print(race..' at '..col.pos.x..', '..col.pos.y..', '..col.pos.z) - end -end - -function convert_vermin_to(target_verm) - local vermin_id = findVermin(target_verm) - local changed = 0 - for _, verm in pairs(df.global.world.vermin.colonies) do - verm.race = vermin_id - verm.caste = -1 -- check for queen bee? - verm.amount = 18826 - verm.visible = true - changed = changed + 1 - end - print('Converted '..changed..' colonies to '..target_verm) -end - -function place_vermin(target_verm) - local pos = copyall(df.global.cursor) - if pos.x == -30000 then - qerror("Cursor must be pointing somewhere") - end - local verm = df.vermin:new() - verm.race = findVermin(target_verm) - verm.flags.is_colony = true - verm.caste = -1 -- check for queen bee? - verm.amount = 18826 - verm.visible = true - verm.pos:assign(pos) - df.global.world.vermin.colonies:insert("#", verm) - df.global.world.vermin.all:insert("#", verm) -end - -local args = {...} -local target_verm = args[2] or "HONEY_BEE" - -if args[1] == 'help' or args[1] == '?' then - print(help) -elseif args[1] == 'convert' then - convert_vermin_to(target_verm) -elseif args[1] == 'place' then - place_vermin(target_verm) -else - if #df.global.world.vermin.colonies < 1 then - dfhack.printerr('There are no colonies on the map.') - end - list_colonies() -end diff --git a/scripts/create-items.rb b/scripts/create-items.rb deleted file mode 100644 index 544bd8ce0..000000000 --- a/scripts/create-items.rb +++ /dev/null @@ -1,207 +0,0 @@ -# create first necessity items under cursor -=begin - -create-items -============ -Spawn items under the cursor, to get your fortress started. - -The first argument gives the item category, the second gives the material, -and the optionnal third gives the number of items to create (defaults to 20). - -Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``, -``web``. - -Instead of material, using ``list`` makes the script list eligible materials. - -The ``web`` item category will create an uncollected cobweb on the floor. - -Note that the script does not enforce anything, and will let you create -boulders of toad blood and stuff like that. -However the ``list`` mode will only show 'normal' materials. - -Examples:: - - create-items boulders COAL_BITUMINOUS 12 - create-items plant tail_pig - create-items log list - create-items web CREATURE:SPIDER_CAVE_GIANT:SILK - create-items bar CREATURE:CAT:SOAP - create-items bar adamantine - -=end - -category = $script_args[0] || 'help' -mat_raw = $script_args[1] || 'list' -count = $script_args[2] - - -category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help' - -if category == 'help' - puts < 5 - df.curview.feed_keys(:CURSOR_DOWN_Z) - df.curview.feed_keys(:CURSOR_UP_Z) -else - df.curview.feed_keys(:CURSOR_UP_Z) - df.curview.feed_keys(:CURSOR_DOWN_Z) -end diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb deleted file mode 100644 index 6b2f252c0..000000000 --- a/scripts/deathcause.rb +++ /dev/null @@ -1,75 +0,0 @@ -# show death cause of a creature -=begin - -deathcause -========== -Select a body part ingame, or a unit from the :kbd:`u` unit list, and this -script will display the cause of death of the creature. - -=end - -def display_death_event(e) - str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}" - str << " (cause: #{e.death_cause.to_s.downcase})," - str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 - str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON - str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.shooter_item_subtype].name}" if e.weapon.shooter_item_type == :WEAPON - - puts str.chomp(',') + '.' -end - -def display_death_unit(u) - death_info = u.counters.death_tg - killer = death_info.killer_tg if death_info - - str = "The #{u.race_tg.name[0]}" - str << " #{u.name}" if u.name.has_name - str << " died" - str << " in year #{death_info.event_year}" if death_info - str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1 - str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer - - puts str.chomp(',') + '.' -end - -item = df.item_find(:selected) -unit = df.unit_find(:selected) - -if !item or !item.kind_of?(DFHack::ItemBodyComponent) - item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } -end - -if item and item.kind_of?(DFHack::ItemBodyComponent) - hf = item.hist_figure_id -elsif unit - hf = unit.hist_figure_id -end - -if not hf - puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" - -elsif hf == -1 - if unit ||= item.unit_tg - display_death_unit(unit) - else - puts "Not a historical figure, cannot death find info" - end - -else - histfig = df.world.history.figures.binsearch(hf) - unit = histfig ? df.unit_find(histfig.unit_id) : nil - if unit and not unit.flags1.dead and not unit.flags3.ghostly - puts "#{unit.name} is not dead yet !" - - else - events = df.world.history.events - (0...events.length).reverse_each { |i| - e = events[i] - if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf - display_death_event(e) - break - end - } - end -end - diff --git a/scripts/deteriorateclothes.rb b/scripts/deteriorateclothes.rb deleted file mode 100644 index 33084185e..000000000 --- a/scripts/deteriorateclothes.rb +++ /dev/null @@ -1,81 +0,0 @@ -# Increase the rate at which clothes wear out -=begin - -deteriorateclothes -================== -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. All of those slightly worn wool shoes that dwarves -scatter all over the place will deteriorate at a greatly increased rate, -and eventually just crumble into nothing. As warm and fuzzy as a dining -room full of used socks makes your dwarves feel, your FPS does not like it. - -Usage: ``deteriorateclothes (start|stop)`` - -=end - -class DeteriorateClothes - - def initialize - end - - def process - return false unless @running - - items = [df.world.items.other[:GLOVES], - df.world.items.other[:ARMOR], - df.world.items.other[:SHOES], - df.world.items.other[:PANTS], - df.world.items.other[:HELM]] - - items.each { |type| - type.each { |i| - if (i.subtype.armorlevel == 0 and i.flags.on_ground == true and i.wear > 0) - i.wear_timer *= i.wear + 0.5 - if (i.wear > 2) - i.flags.garbage_collect = true - end - end - } - } - - - end - - def start - @onupdate = df.onupdate_register('deteriorateclothes', 1200, 1200) { process } - @running = true - - puts "Deterioration of old clothes commencing..." - - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - @running ? 'Running.' : 'Stopped.' - end - -end - -case $script_args[0] -when 'start' - if ($DeteriorateClothes) - $DeteriorateClothes.stop - end - - $DeteriorateClothes = DeteriorateClothes.new - $DeteriorateClothes.start - -when 'end', 'stop' - $DeteriorateClothes.stop - -else - if $DeteriorateClothes - puts $DeteriorateClothes.status - else - puts 'Not loaded.' - end -end \ No newline at end of file diff --git a/scripts/deterioratecorpses.rb b/scripts/deterioratecorpses.rb deleted file mode 100644 index 56003f6d0..000000000 --- a/scripts/deterioratecorpses.rb +++ /dev/null @@ -1,106 +0,0 @@ -# Make corpse parts decay and vanish over time -=begin - -deterioratecorpses -================== -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. - -In long running forts, especially evil biomes, you end up with a lot -of toes, teeth, fingers, and limbs scattered all over the place. -Various corpses from various sieges, stray kitten corpses, probably -some heads. Basically, your map will look like a giant pile of -assorted body parts, all of which individually eat up a small part -of your FPS, which collectively eat up quite a bit. - -In addition, this script also targets various butchery byproducts. -Enjoying your thriving animal industry? Your FPS does not. Those -thousands of skulls, bones, hooves, and wool eat up precious FPS -that could be used to kill goblins and elves. Whose corpses will -also get destroyed by the script to kill more goblins and elves. - -This script causes all of those to rot away into nothing after -several months. - -Usage: ``deterioratecorpses (start|stop)`` - -=end - -class DeteriorateCorpses - - def initialize - end - - def process - return false unless @running - - df.world.items.other[:ANY_CORPSE].each { |i| - if (i.flags.dead_dwarf == false) - i.wear_timer += 1 - if (i.wear_timer > 24 + rand(8)) - i.wear_timer = 0 - i.wear += 1 - end - if (i.wear > 3) - i.flags.garbage_collect = true - end - - end - - } - - df.world.items.other[:REMAINS].each { |i| - if (i.flags.dead_dwarf == false) - i.wear_timer += 1 - if (i.wear_timer > 6) - i.wear_timer = 0 - i.wear += 1 - end - if (i.wear > 3) - i.flags.garbage_collect = true - end - - end - - } - - end - - def start - @onupdate = df.onupdate_register('deterioratecorpses', 1200, 1200) { process } - @running = true - - puts "Deterioration of body parts commencing..." - - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - @running ? 'Running.' : 'Stopped.' - end - -end - -case $script_args[0] -when 'start' - if ($DeteriorateCorpses) - $DeteriorateCorpses.stop - end - - $DeteriorateCorpses = DeteriorateCorpses.new - $DeteriorateCorpses.start - -when 'end', 'stop' - $DeteriorateCorpses.stop - -else - if $DeteriorateCorpses - puts $DeteriorateCorpses.status - else - puts 'Not loaded.' - end -end \ No newline at end of file diff --git a/scripts/deterioratefood.rb b/scripts/deterioratefood.rb deleted file mode 100644 index c59171ea5..000000000 --- a/scripts/deterioratefood.rb +++ /dev/null @@ -1,96 +0,0 @@ -# Food and plants decay, and vanish after a few months -=begin - -deterioratefood -=============== -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. - -With this script running, all food and plants wear out and disappear -after several months. Barrels and stockpiles will keep them from -rotting, but it won't keep them from decaying. No more sitting on a -hundred years worth of food. No more keeping barrels of pig tails -sitting around until you decide to use them. Either use it, eat it, -or lose it. Seeds, are excluded from this, if you aren't planning on -using your pig tails, hold onto the seeds for a rainy day. - -This script is...pretty far reaching. However, almost all long -running forts I've had end up sitting on thousands and thousands of -food items. Several thousand cooked meals, three thousand plump -helmets, just as many fish and meat. It gets pretty absurd. And your -FPS doesn't like it. - -Usage: ``deterioratefood (start|stop)`` - -=end - -class DeteriorateFood - - def initialize - end - - def process - return false unless @running - - items = [df.world.items.other[:FISH], - df.world.items.other[:FISH_RAW], - df.world.items.other[:EGG], - df.world.items.other[:CHEESE], - df.world.items.other[:PLANT], - df.world.items.other[:PLANT_GROWTH], - df.world.items.other[:FOOD]] - - items.each { |type| - type.each { |i| - i.wear_timer += 1 - if (i.wear_timer > 24 + rand(8)) - i.wear_timer = 0 - i.wear += 1 - end - if (i.wear > 3) - i.flags.garbage_collect = true - end - } - } - - - end - - def start - @onupdate = df.onupdate_register('deterioratefood', 1200, 1200) { process } - @running = true - - puts "Deterioration of food commencing..." - - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - @running ? 'Running.' : 'Stopped.' - end - -end - -case $script_args[0] -when 'start' - if ($DeteriorateFood) - $DeteriorateFood.stop - end - - $DeteriorateFood = DeteriorateFood.new - $DeteriorateFood.start - -when 'end', 'stop' - $DeteriorateFood.stop - -else - if $DeteriorateFood - puts $DeteriorateFood.status - else - puts 'Not loaded.' - end -end \ No newline at end of file diff --git a/scripts/devel/about.txt b/scripts/devel/about.txt deleted file mode 100644 index 12b1f1853..000000000 --- a/scripts/devel/about.txt +++ /dev/null @@ -1,6 +0,0 @@ -``devel/*`` scripts are intended for developer use, but many may -be of interest to anyone investigating odd phenomema or just messing -around. They are documented to encourage such inquiry. - -Some can PERMANENTLY DAMAGE YOUR SAVE if misused, so please be careful. -The warnings are real; if in doubt make backups before running the command. diff --git a/scripts/devel/all-bob.lua b/scripts/devel/all-bob.lua deleted file mode 100644 index 163f708dd..000000000 --- a/scripts/devel/all-bob.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Changes the first name of all units to "Bob" ---author expwnent --- ---[[=begin - -devel/all-bob -============= -Changes the first name of all units to "Bob". -Useful for testing `modtools/interaction-trigger` events. - -=end]] - -for _,v in ipairs(df.global.world.units.all) do - v.name.first_name = "Bob" -end diff --git a/scripts/devel/check-release.lua b/scripts/devel/check-release.lua deleted file mode 100644 index fcc50642e..000000000 --- a/scripts/devel/check-release.lua +++ /dev/null @@ -1,59 +0,0 @@ --- basic check for release readiness ---[[=begin -devel/check-release -=================== -Basic checks for release readiness -=end]] - -ok = true -function err(s) - dfhack.printerr(s) - ok = false -end -function warn(s) - dfhack.color(COLOR_YELLOW) - dfhack.print(s .. '\n') - dfhack.color(nil) -end - -dfhack_ver = dfhack.getDFHackVersion() -git_desc = dfhack.getGitDescription() -git_commit = dfhack.getGitCommit() -if not dfhack.isRelease() then - err('This build is not tagged as a release') - print[[ -This is probably due to missing git tags. -Try running `git fetch origin --tags` in the DFHack source tree. -]] -end - -expected_git_desc = ('%s-0-g%s'):format(dfhack_ver, git_commit:sub(1, 7)) -if git_desc:sub(1, #expected_git_desc) ~= expected_git_desc then - err(([[Bad git description: -Expected %s, got %s]]):format(expected_git_desc, git_desc)) - print[[ -Ensure that the DFHack source tree is up-to-date (`git pull`) and CMake is -installing DFHack to this DF folder. -]] -end - -if not dfhack.gitXmlMatch() then - err('library/xml submodule commit does not match tracked commit\n' .. - ('Expected %s, got %s'):format( - dfhack.getGitXmlCommit():sub(1, 7), - dfhack.getGitXmlExpectedCommit():sub(1, 7) - )) - print('Try running `git submodule update` in the DFHack source tree.') -end - -if dfhack.isPrerelease() then - warn('This build is marked as a prerelease.') - print('If this is not intentional, be sure your DFHack tree is up-to-date\n' .. - '(`git pull`) and try again.') -end - -if not ok then - err('This build is not release-ready!') -else - print('Release checks succeeded') -end diff --git a/scripts/devel/clear-script-env.lua b/scripts/devel/clear-script-env.lua deleted file mode 100644 index 90c8db0ed..000000000 --- a/scripts/devel/clear-script-env.lua +++ /dev/null @@ -1,26 +0,0 @@ --- Clear script environment ---[[=begin - -devel/clear-script-env -====================== -Clears the environment of the specified lua script(s). - -=end]] -args = {...} -if #args < 1 then qerror("script name(s) required") end -for _, name in pairs(args) do - local file = dfhack.findScript(name) - if file then - local script = dfhack.internal.scripts[file] - if script then - local env = script.env - while next(env) do - env[next(env)] = nil - end - else - dfhack.printerr("Script not loaded: " .. name) - end - else - dfhack.printerr("Can't find script: " .. name) - end -end diff --git a/scripts/devel/cmptiles.lua b/scripts/devel/cmptiles.lua deleted file mode 100644 index bb0871051..000000000 --- a/scripts/devel/cmptiles.lua +++ /dev/null @@ -1,50 +0,0 @@ --- Lists and/or compares two tiletype material groups. ---[[=begin - -devel/cmptiles -============== -Lists and/or compares two tiletype material groups. - -Usage: ``devel/cmptiles material1 [material2]`` - -=end]] - -local nmat1,nmat2=... -local mat1 = df.tiletype_material[nmat1] -local mat2 = df.tiletype_material[nmat2] - -local tmat1 = {} -local tmat2 = {} - -local attrs = df.tiletype.attrs - -for i=df.tiletype._first_item,df.tiletype._last_item do - local shape = df.tiletype_shape[attrs[i].shape] or '' - local variant = df.tiletype_variant[attrs[i].variant] or '' - local special = df.tiletype_special[attrs[i].special] or '' - local direction = attrs[i].direction or '' - local code = shape..':'..variant..':'..special..':'..direction - - if attrs[i].material == mat1 then - tmat1[code] = true - end - if attrs[i].material == mat2 then - tmat2[code] = true - end -end - -local function list_diff(n, t1, t2) - local lst = {} - for k,v in pairs(t1) do - if not t2[k] then - lst[#lst+1] = k - end - end - table.sort(lst) - for k,v in ipairs(lst) do - print(n, v) - end -end - -list_diff(nmat1,tmat1,tmat2) -list_diff(nmat2,tmat2,tmat1) diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua deleted file mode 100644 index b6a2989eb..000000000 --- a/scripts/devel/export-dt-ini.lua +++ /dev/null @@ -1,515 +0,0 @@ --- Exports an ini file for Dwarf Therapist. ---[[=begin -devel/export-dt-ini -=================== -Exports an ini file containing memory addresses for Dwarf Therapist. -=end]] - -local utils = require 'utils' -local ms = require 'memscan' - --- Utility functions - -local globals = df.global -local global_addr = dfhack.internal.getAddress -local os_type = dfhack.getOSType() -local rdelta = dfhack.internal.getRebaseDelta() -local lines = {} -local complete = true - -local function header(name) - table.insert(lines, '') - table.insert(lines, '['..name..']') -end - -local function value(name,addr) - local line - - if not addr then - complete = false - line = name..'=0x0' - elseif addr < 0x10000 then - line = string.format('%s=0x%04x',name,addr) - else - line = string.format('%s=0x%08x',name,addr) - end - - table.insert(lines, line) -end -local function address(name,base,field,...) - local addr - - if base == globals then - addr = global_addr(field) - if addr and select('#',...) > 0 then - _,addr = df.sizeof(ms.field_ref(base,field,...)) - end - elseif base._kind == 'class-type' then - -- field_offset crashes with classes due to vtable problems, - -- so we have to create a real temporary object here. - local obj = df.new(base) - if obj then - local _,a1 = df.sizeof(obj) - local _,a2 = df.sizeof(ms.field_ref(obj,field,...)) - addr = a2-a1 - obj:delete() - end - else - addr = ms.field_offset(base,field,...) - end - - value(name, addr) -end - - --- List of actual values -header('addresses') -address('cur_year_tick',globals,'cur_year_tick') -address('current_year',globals,'cur_year') -address('dwarf_civ_index',globals,'ui','civ_id') -address('dwarf_race_index',globals,'ui','race_id') -address('fortress_entity',globals,'ui','main','fortress_entity') -address('historical_entities_vector',globals,'world','entities','all') -address('creature_vector',globals,'world','units','all') -address('active_creature_vector',globals,'world','units','active') -address('weapons_vector',globals,'world','items','other','WEAPON') -address('shields_vector',globals,'world','items','other', 'SHIELD') -address('quivers_vector',globals,'world','items','other', 'QUIVER') -address('crutches_vector',globals,'world','items','other', 'CRUTCH') -address('backpacks_vector',globals,'world','items','other', 'BACKPACK') -address('ammo_vector',globals,'world','items','other', 'AMMO') -address('flasks_vector',globals,'world','items','other', 'FLASK') -address('pants_vector',globals,'world','items','other', 'PANTS') -address('armor_vector',globals,'world','items','other', 'ARMOR') -address('shoes_vector',globals,'world','items','other', 'SHOES') -address('helms_vector',globals,'world','items','other', 'HELM') -address('gloves_vector',globals,'world','items','other', 'GLOVES') -address('artifacts_vector',globals,'world','artifacts','all') -address('squad_vector',globals,'world','squads','all') -address('activities_vector',globals,'world','activities','all') -address('fake_identities_vector',globals,'world','identities','all') -address('poetic_forms_vector',globals,'world','poetic_forms','all') -address('musical_forms_vector',globals,'world','musical_forms','all') -address('dance_forms_vector',globals,'world','dance_forms','all') -address('occupations_vector',globals,'world','occupations','all') -address('world_data',globals,'world','world_data') -address('material_templates_vector',globals,'world','raws','material_templates') -address('inorganics_vector',globals,'world','raws','inorganics') -address('plants_vector',globals,'world','raws','plants','all') -address('races_vector',globals,'world','raws','creatures','all') -address('itemdef_weapons_vector',globals,'world','raws','itemdefs','weapons') -address('itemdef_trap_vector',globals,'world','raws','itemdefs','trapcomps') -address('itemdef_toy_vector',globals,'world','raws','itemdefs','toys') -address('itemdef_tool_vector',globals,'world','raws','itemdefs','tools') -address('itemdef_instrument_vector',globals,'world','raws','itemdefs','instruments') -address('itemdef_armor_vector',globals,'world','raws','itemdefs','armor') -address('itemdef_ammo_vector',globals,'world','raws','itemdefs','ammo') -address('itemdef_siegeammo_vector',globals,'world','raws','itemdefs','siege_ammo') -address('itemdef_glove_vector',globals,'world','raws','itemdefs','gloves') -address('itemdef_shoe_vector',globals,'world','raws','itemdefs','shoes') -address('itemdef_shield_vector',globals,'world','raws','itemdefs','shields') -address('itemdef_helm_vector',globals,'world','raws','itemdefs','helms') -address('itemdef_pant_vector',globals,'world','raws','itemdefs','pants') -address('itemdef_food_vector',globals,'world','raws','itemdefs','food') -address('language_vector',globals,'world','raws','language','words') -address('translation_vector',globals,'world','raws','language','translations') -address('colors_vector',globals,'world','raws','language','colors') -address('shapes_vector',globals,'world','raws','language','shapes') -address('reactions_vector',globals,'world','raws','reactions') -address('base_materials',globals,'world','raws','mat_table','builtin') -address('all_syndromes_vector',globals,'world','raws','syndromes','all') -address('events_vector',globals,'world','history','events') -address('historical_figures_vector',globals,'world','history','figures') -address('world_site_type',df.world_site,'type') -address('active_sites_vector',df.world_data,'active_site') - -header('offsets') -address('word_table',df.language_translation,'words') -value('string_buffer_offset', 0x0000) - -header('word_offsets') -address('base',df.language_word,'word') -address('noun_singular',df.language_word,'forms','Noun') -address('noun_plural',df.language_word,'forms','NounPlural') -address('adjective',df.language_word,'forms','Adjective') -address('verb',df.language_word,'forms','Verb') -address('present_simple_verb',df.language_word,'forms','Verb3rdPerson') -address('past_simple_verb',df.language_word,'forms','VerbPast') -address('past_participle_verb',df.language_word,'forms','VerbPassive') -address('present_participle_verb',df.language_word,'forms','VerbGerund') -address('words',df.language_name,'words') -address('word_type',df.language_name,'parts_of_speech') -address('language_id',df.language_name,'language') - -header('general_ref_offsets') ---WARNING below value should be: "general_ref::vtable","1","0x8","0x4","vmethod","getType","general_ref_type","" -value('ref_type',0x8) -address('artifact_id',df.general_ref_artifact,'artifact_id') -address('item_id',df.general_ref_item,'item_id') - -header('race_offsets') -address('name_singular',df.creature_raw,'name',0) -address('name_plural',df.creature_raw,'name',1) -address('adjective',df.creature_raw,'name',2) -address('baby_name_singular',df.creature_raw,'general_baby_name',0) -address('baby_name_plural',df.creature_raw,'general_baby_name',1) -address('child_name_singular',df.creature_raw,'general_child_name',0) -address('child_name_plural',df.creature_raw,'general_child_name',1) -address('pref_string_vector',df.creature_raw,'prefstring') -address('castes_vector',df.creature_raw,'caste') -address('pop_ratio_vector',df.creature_raw,'pop_ratio') -address('materials_vector',df.creature_raw,'material') -address('flags',df.creature_raw,'flags') -address('tissues_vector',df.creature_raw,'tissue') - -header('caste_offsets') -address('caste_name',df.caste_raw,'caste_name') -address('caste_descr',df.caste_raw,'description') -address('caste_trait_ranges',df.caste_raw,'personality','a') -address('caste_phys_att_ranges',df.caste_raw,'attributes','phys_att_range') -address('baby_age',df.caste_raw,'misc','baby_age') -address('child_age',df.caste_raw,'misc','child_age') -address('adult_size',df.caste_raw,'misc','adult_size') -address('flags',df.caste_raw,'flags') -address('body_info',df.caste_raw,'body_info') -address('skill_rates',df.caste_raw,'skill_rates') -address('caste_att_rates',df.caste_raw,'attributes','phys_att_rates') -address('caste_att_caps',df.caste_raw,'attributes','phys_att_cap_perc') -address('shearable_tissues_vector',df.caste_raw,'shearable_tissue_layer') -address('extracts',df.caste_raw,'extracts','extract_matidx') - -header('hist_entity_offsets') -address('histfigs',df.historical_entity,'histfig_ids') -address('beliefs',df.historical_entity,'resources','values') -address('squads',df.historical_entity,'squads') -address('positions',df.historical_entity,'positions','own') -address('assignments',df.historical_entity,'positions','assignments') -address('assign_hist_id',df.entity_position_assignment,'histfig') -address('assign_position_id',df.entity_position_assignment,'position_id') -address('position_id',df.entity_position,'id') -address('position_name',df.entity_position,'name') -address('position_female_name',df.entity_position,'name_female') -address('position_male_name',df.entity_position,'name_male') - -header('hist_figure_offsets') -address('hist_race',df.historical_figure,'race') -address('hist_name',df.historical_figure,'name') -address('id',df.historical_figure,'id') -address('hist_fig_info',df.historical_figure,'info') -address('reputation',df.historical_figure_info,'reputation') -address('current_ident',df.historical_figure_info.T_reputation,'cur_identity') -address('fake_name',df.identity,'name') -address('fake_birth_year',df.identity,'birth_year') -address('fake_birth_time',df.identity,'birth_second') -address('kills',df.historical_figure_info,'kills') -address('killed_race_vector',df.historical_kills,'killed_race') -address('killed_undead_vector',df.historical_kills,'killed_undead') -address('killed_counts_vector',df.historical_kills,'killed_count') - -header('hist_event_offsets') -address('event_year',df.history_event,'year') -address('id',df.history_event,'id') -address('killed_hist_id',df.history_event_hist_figure_diedst,'victim_hf') - -header('item_offsets') -if os_type == 'darwin' then - value('item_type',0x4) -else - value('item_type',0x1) -end -address('item_def',df.item_ammost,'subtype') --currently same for all -address('id',df.item,'id') -address('general_refs',df.item,'general_refs') -address('stack_size',df.item_actual,'stack_size') -address('wear',df.item_actual,'wear') -address('mat_type',df.item_crafted,'mat_type') -address('mat_index',df.item_crafted,'mat_index') -address('maker_race',df.item_crafted,'maker_race') -address('quality',df.item_crafted,'quality') - -header('item_subtype_offsets') -address('sub_type',df.itemdef,'subtype') -address('name',df.itemdef_armorst,'name') -address('name_plural',df.itemdef_armorst,'name_plural') -address('adjective',df.itemdef_armorst,'name_preplural') - -header('item_filter_offsets') -address('item_subtype',df.item_filter_spec,'item_subtype') -address('mat_class',df.item_filter_spec,'material_class') -address('mat_type',df.item_filter_spec,'mattype') -address('mat_index',df.item_filter_spec,'matindex') - -header('weapon_subtype_offsets') -address('single_size',df.itemdef_weaponst,'two_handed') -address('multi_size',df.itemdef_weaponst,'minimum_size') -address('ammo',df.itemdef_weaponst,'ranged_ammo') -address('melee_skill',df.itemdef_weaponst,'skill_melee') -address('ranged_skill',df.itemdef_weaponst,'skill_ranged') - -header('armor_subtype_offsets') -address('layer',df.armor_properties,'layer') -address('mat_name',df.itemdef_armorst,'material_placeholder') -address('other_armor_level',df.itemdef_helmst,'armorlevel') -address('armor_adjective',df.itemdef_armorst,'adjective') -address('armor_level',df.itemdef_armorst,'armorlevel') -address('chest_armor_properties',df.itemdef_armorst,'props') -address('pants_armor_properties',df.itemdef_pantsst,'props') -address('other_armor_properties',df.itemdef_helmst,'props') - -header('material_offsets') -address('solid_name',df.material_common,'state_name','Solid') -address('liquid_name',df.material_common,'state_name','Liquid') -address('gas_name',df.material_common,'state_name','Gas') -address('powder_name',df.material_common,'state_name','Powder') -address('paste_name',df.material_common,'state_name','Paste') -address('pressed_name',df.material_common,'state_name','Pressed') -address('flags',df.material_common,'flags') -address('inorganic_materials_vector',df.inorganic_raw,'material') -address('inorganic_flags',df.inorganic_raw,'flags') - -header('plant_offsets') -address('name',df.plant_raw,'name') -address('name_plural',df.plant_raw,'name_plural') -address('name_leaf_plural',df.plant_raw,'leaves_plural') -address('name_seed_plural',df.plant_raw,'seed_plural') -address('materials_vector',df.plant_raw,'material') -address('flags',df.plant_raw,'flags') - -header('descriptor_offsets') -address('color_name',df.descriptor_color,'name') -address('shape_name_plural',df.descriptor_shape,'name_plural') - -header('health_offsets') -address('parent_id',df.body_part_raw,'con_part_id') -address('body_part_flags',df.body_part_raw,'flags') -address('layers_vector',df.body_part_raw,'layers') -address('number',df.body_part_raw,'number') -address('names_vector',df.body_part_raw,'name_singular') -address('names_plural_vector',df.body_part_raw,'name_plural') -address('layer_tissue',df.body_part_layer_raw,'tissue_id') -address('layer_global_id',df.body_part_layer_raw,'layer_id') -address('tissue_name',df.tissue_template,'tissue_name_singular') -address('tissue_flags',df.tissue_template,'flags') - -header('dwarf_offsets') -address('first_name',df.unit,'name','first_name') -address('nick_name',df.unit,'name','nickname') -address('last_name',df.unit,'name','words') -address('custom_profession',df.unit,'custom_profession') -address('profession',df.unit,'profession') -address('race',df.unit,'race') -address('flags1',df.unit,'flags1') -address('flags2',df.unit,'flags2') -address('flags3',df.unit,'flags3') -address('meeting',df.unit,'meeting') -address('caste',df.unit,'caste') -address('sex',df.unit,'sex') -address('id',df.unit,'id') -address('animal_type',df.unit,'training_level') -address('civ',df.unit,'civ_id') -address('specific_refs',df.unit,'specific_refs') -address('squad_id',df.unit,'military','squad_id') -address('squad_position',df.unit,'military','squad_position') -address('recheck_equipment',df.unit,'military','pickup_flags') -address('mood',df.unit,'mood') -address('birth_year',df.unit,'relations','birth_year') -address('birth_time',df.unit,'relations','birth_time') -address('pet_owner_id',df.unit,'relations','pet_owner_id') -address('current_job',df.unit,'job','current_job') -address('physical_attrs',df.unit,'body','physical_attrs') -address('body_size',df.unit,'appearance','body_modifiers') -address('size_info',df.unit,'body','size_info') -address('curse',df.unit,'curse','name') -address('curse_add_flags1',df.unit,'curse','add_tags1') -address('turn_count',df.unit,'curse','time_on_site') -address('souls',df.unit,'status','souls') -address('states',df.unit,'status','misc_traits') -address('labors',df.unit,'status','labors') -address('hist_id',df.unit,'hist_figure_id') -address('artifact_name',df.unit,'status','artifact_name') -address('active_syndrome_vector',df.unit,'syndromes','active') -address('syn_sick_flag',df.unit_syndrome,'flags') -address('unit_health_info',df.unit,'health') -address('temp_mood',df.unit,'counters','soldier_mood') -address('counters1',df.unit,'counters','winded') -address('counters2',df.unit, 'counters','pain') -address('counters3',df.unit, 'counters2','paralysis') -address('limb_counters',df.unit,'status2','limbs_stand_max') -address('blood',df.unit,'body','blood_max') -address('body_component_info',df.unit,'body','components') -address('layer_status_vector',df.body_component_info,'layer_status') -address('wounds_vector',df.unit,'body','wounds') -address('mood_skill',df.unit,'job','mood_skill') -address('used_items_vector',df.unit,'used_items') -address('affection_level',df.unit_item_use,'affection_level') -address('inventory',df.unit,'inventory') -address('inventory_item_mode',df.unit_inventory_item,'mode') -address('inventory_item_bodypart',df.unit_inventory_item,'body_part_id') - -header('syndrome_offsets') -address('cie_effects',df.syndrome,'ce') -address('cie_end',df.creature_interaction_effect,'end') -address('cie_first_perc',df.creature_interaction_effect_phys_att_changest,'phys_att_perc') --same for mental -address('cie_phys',df.creature_interaction_effect_phys_att_changest,'phys_att_add') -address('cie_ment',df.creature_interaction_effect_ment_att_changest,'ment_att_add') -address('syn_classes_vector',df.syndrome,'syn_class') -address('trans_race_id',df.creature_interaction_effect_body_transformationst,'race') - -header('unit_wound_offsets') -address('parts',df.unit_wound,'parts') -address('id',df.unit_wound.T_parts,'body_part_id') -address('layer',df.unit_wound.T_parts,'layer_idx') -address('general_flags',df.unit_wound,'flags') -address('flags1',df.unit_wound.T_parts,'flags1') -address('flags2',df.unit_wound.T_parts,'flags2') -address('effects_vector',df.unit_wound.T_parts,'effect_type') -address('bleeding',df.unit_wound.T_parts,'bleeding') -address('pain',df.unit_wound.T_parts,'pain') -address('cur_pen',df.unit_wound.T_parts,'cur_penetration_perc') -address('max_pen',df.unit_wound.T_parts,'max_penetration_perc') - -header('soul_details') -address('name',df.unit_soul,'name') -address('orientation',df.unit_soul,'orientation_flags') -address('mental_attrs',df.unit_soul,'mental_attrs') -address('skills',df.unit_soul,'skills') -address('preferences',df.unit_soul,'preferences') -address('personality',df.unit_soul,'personality') -address('beliefs',df.unit_personality,'values') -address('emotions',df.unit_personality,'emotions') -address('goals',df.unit_personality,'dreams') -address('goal_realized',df.unit_personality.T_dreams,'unk8') -address('traits',df.unit_personality,'traits') -address('stress_level',df.unit_personality,'stress_level') - -header('emotion_offsets') -address('emotion_type',df.unit_personality.T_emotions,'type') -address('strength',df.unit_personality.T_emotions,'strength') -address('thought_id',df.unit_personality.T_emotions,'thought') -address('sub_id',df.unit_personality.T_emotions,'subthought') -address('level',df.unit_personality.T_emotions,'severity') -address('year',df.unit_personality.T_emotions,'year') -address('year_tick',df.unit_personality.T_emotions,'year_tick') - -header('job_details') -address('id',df.job,'job_type') -address('mat_type',df.job,'mat_type') -address('mat_index',df.job,'mat_index') -address('mat_category',df.job,'material_category') -value('on_break_flag',df.misc_trait_type.OnBreak) -address('sub_job_id',df.job,'reaction_name') -address('reaction',df.reaction,'name') -address('reaction_skill',df.reaction,'skill') - -header('squad_offsets') -address('id',df.squad,'id') -address('name',df.squad,'name') -address('alias',df.squad,'alias') -address('members',df.squad,'positions') -address('orders',df.squad,'orders') -address('schedules',df.squad,'schedule') -if os_type ~= 'windows' then --squad_schedule_entry size - value('sched_size',0x20) -else - value('sched_size',0x40) -end -address('sched_orders',df.squad_schedule_entry,'orders') -address('sched_assign',df.squad_schedule_entry,'order_assignments') -address('alert',df.squad,'cur_alert_idx') -address('carry_food',df.squad,'carry_food') -address('carry_water',df.squad,'carry_water') -address('ammunition',df.squad,'ammunition') -address('ammunition_qty',df.squad_ammo_spec,'amount') -address('quiver',df.squad_position,'quiver') -address('backpack',df.squad_position,'backpack') -address('flask',df.squad_position,'flask') -address('armor_vector',df.squad_position,'uniform','body') -address('helm_vector',df.squad_position,'uniform','head') -address('pants_vector',df.squad_position,'uniform','pants') -address('gloves_vector',df.squad_position,'uniform','gloves') -address('shoes_vector',df.squad_position,'uniform','shoes') -address('shield_vector',df.squad_position,'uniform','shield') -address('weapon_vector',df.squad_position,'uniform','weapon') -address('uniform_item_filter',df.squad_uniform_spec,'item_filter') -address('uniform_indiv_choice',df.squad_uniform_spec,'indiv_choice') - -header('activity_offsets') -address('activity_type',df.activity_entry,'type') -address('events',df.activity_entry,'events') -address('participants',df.activity_event_combat_trainingst,'participants') -address('sq_lead',df.activity_event_skill_demonstrationst,'hist_figure_id') -address('sq_skill',df.activity_event_skill_demonstrationst,'skill') -address('sq_train_rounds',df.activity_event_skill_demonstrationst,'train_rounds') -address('pray_deity',df.activity_event_prayerst,'histfig_id') -address('pray_sphere',df.activity_event_prayerst,'topic') -address('knowledge_category',df.activity_event_ponder_topicst,'knowledge_category') -address('knowledge_flag',df.activity_event_ponder_topicst,'knowledge_flag') -address('perf_type',df.activity_event_performancest,'type') -address('perf_participants',df.activity_event_performancest,'participant_actions') -address('perf_histfig',df.activity_event_performancest.T_participant_actions,'histfig_id') - --- Final creation of the file - -local out = io.open('therapist.ini', 'w') - -out:write('[info]\n') -if dfhack.getOSType() == 'windows' and dfhack.internal.getPE then - out:write(('checksum=0x%x\n'):format(dfhack.internal.getPE())) -elseif dfhack.getOSType() ~= 'windows' and dfhack.internal.getMD5 then - out:write(('checksum=0x%s\n'):format(dfhack.internal.getMD5():sub(1, 8))) -else - out:write('checksum=<>\n') -end -out:write('version_name='..dfhack.getDFVersion()..'\n') -out:write('complete='..(complete and 'true' or 'false')..'\n') - -for i,v in ipairs(lines) do - out:write(v..'\n') -end - -out:write[[ - -[valid_flags_2] -size=0 - -[invalid_flags_1] -size=9 -1\name=a skeleton -1\value=0x00002000 -2\name=a merchant -2\value=0x00000040 -3\name=outpost liason or diplomat -3\value=0x00000800 -4\name=an invader or hostile -4\value=0x00020000 -5\name=an invader or hostile -5\value=0x00080000 -6\name=resident, invader or ambusher -6\value=0x00600000 -7\name=part of a merchant caravan -7\value=0x00000080 -8\name="Dead, Jim." -8\value=0x00000002 -9\name=marauder -9\value=0x00000010 - -[invalid_flags_2] -size=5 -1\name="killed, Jim." -1\value=0x00000080 -2\name=from the Underworld. SPOOKY! -2\value=0x00040000 -3\name=resident -3\value=0x00080000 -4\name=uninvited visitor -4\value=0x00400000 -5\name=visitor -5\value=0x00800000 - -[invalid_flags_3] -size=1 -1\name=a ghost -1\value=0x00001000 -]] - -out:close() diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua deleted file mode 100644 index 9cfacece0..000000000 --- a/scripts/devel/find-offsets.lua +++ /dev/null @@ -1,1936 +0,0 @@ --- Find some offsets for linux. ---[[=begin - -devel/find-offsets -================== -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -Running this script on a new DF version will NOT -MAKE IT RUN CORRECTLY if any data structures -changed, thus possibly leading to CRASHES AND/OR -PERMANENT SAVE CORRUPTION. - -Finding the first few globals requires this script to be -started immediately after loading the game, WITHOUT -first loading a world. The rest expect a loaded save, -not a fresh embark. Finding current_weather requires -a special save previously processed with `devel/prepare-save` -on a DF version with working dfhack. - -The script expects vanilla game configuration, without -any custom tilesets or init file changes. Never unpause -the game unless instructed. When done, quit the game -without saving using 'die'. - -Arguments: - -* global names to force finding them -* ``all`` to force all globals -* ``nofeed`` to block automated fake input searches -* ``nozoom`` to disable neighboring object heuristics - -=end]] - -local utils = require 'utils' -local ms = require 'memscan' -local gui = require 'gui' - -local is_known = dfhack.internal.getAddress - -local os_type = dfhack.getOSType() - -local force_scan = {} -for _,v in ipairs({...}) do - force_scan[v] = true -end - -collectgarbage() - -function prompt_proceed(indent) - if not indent then indent = 0 end - return utils.prompt_yes_no(string.rep(' ', indent) .. 'Proceed?', true) -end - -print[[ -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -Running this script on a new DF version will NOT -MAKE IT RUN CORRECTLY if any data structures -changed, thus possibly leading to CRASHES AND/OR -PERMANENT SAVE CORRUPTION. - -Finding the first few globals requires this script to be -started immediately after loading the game, WITHOUT -first loading a world. The rest expect a loaded save, -not a fresh embark. Finding current_weather requires -a special save previously processed with devel/prepare-save -on a DF version with working dfhack. - -The script expects vanilla game configuration, without -any custom tilesets or init file changes. Never unpause -the game unless instructed. When done, quit the game -without saving using 'die'. -]] - -if not utils.prompt_yes_no('Proceed?') then - return -end - --- Data segment location - -local data = ms.get_data_segment() -if not data then - qerror('Could not find data segment') -end - -print('\nData section: '..tostring(data)) -if data.size < 5000000 then - qerror('Data segment too short.') -end - -local searcher = ms.DiffSearcher.new(data) - -local function get_screen(class, prompt) - if not is_known('gview') then - print('Please navigate to '..prompt) - if not prompt_proceed() then - return nil - end - return true - end - - while true do - local cs = dfhack.gui.getCurViewscreen(true) - if not df.is_instance(class, cs) then - print('Please navigate to '..prompt) - if not prompt_proceed() then - return nil - end - else - return cs - end - end -end - -local function screen_title() - return get_screen(df.viewscreen_titlest, 'the title screen') -end -local function screen_dwarfmode() - return get_screen(df.viewscreen_dwarfmodest, 'the main dwarf mode screen') -end - -local function validate_offset(name,validator,addr,tname,...) - local obj = data:object_by_field(addr,tname,...) - if obj and not validator(obj) then - obj = nil - end - ms.found_offset(name,obj) -end - -local function zoomed_searcher(startn, end_or_sz) - if force_scan.nozoom then - return nil - end - local sv = is_known(startn) - if not sv then - return nil - end - local ev - if type(end_or_sz) == 'number' then - ev = sv + end_or_sz - if end_or_sz < 0 then - sv, ev = ev, sv - end - else - ev = is_known(end_or_sz) - if not ev then - return nil - end - end - sv = sv - (sv % 4) - ev = ev + 3 - ev = ev - (ev % 4) - if data:contains_range(sv, ev-sv) then - return ms.DiffSearcher.new(ms.MemoryArea.new(sv,ev)) - end -end - -local finder_searches = {} -local function exec_finder(finder, names, validators) - if type(names) ~= 'table' then - names = { names } - end - if type(validators) ~= 'table' then - validators = { validators } - end - local search = force_scan['all'] - for k,v in ipairs(names) do - if force_scan[v] or not is_known(v) then - table.insert(finder_searches, v) - search = true - elseif validators[k] then - if not validators[k](df.global[v]) then - dfhack.printerr('Validation failed for '..v..', will try to find again') - table.insert(finder_searches, v) - search = true - end - end - end - if search then - local ok, err = dfhack.safecall(finder) - if not ok then - if tostring(err):find('abort') or not utils.prompt_yes_no('Proceed with the rest of the script?') then - searcher:reset() - qerror('Quit') - end - end - else - print('Already known: '..table.concat(names,', ')) - end -end - -local ordinal_names = { - [0] = '1st entry', - [1] = '2nd entry', - [2] = '3rd entry' -} -setmetatable(ordinal_names, { - __index = function(self,idx) return (idx+1)..'th entry' end -}) - -local function list_index_choices(length_func) - return function(id) - if id > 0 then - local ok, len = pcall(length_func) - if not ok then - len = 5 - elseif len > 10 then - len = 10 - end - return id % len - else - return 0 - end - end -end - -local function can_feed() - return not force_scan['nofeed'] and is_known 'gview' -end - -local function dwarfmode_feed_input(...) - local screen = screen_dwarfmode() - if not df.isvalid(screen) then - qerror('could not retrieve dwarfmode screen') - end - try_save_cursor() - for _,v in ipairs({...}) do - gui.simulateInput(screen, v) - end -end - -local function dwarfmode_step_frames(count) - local screen = screen_dwarfmode() - if not df.isvalid(screen) then - qerror('could not retrieve dwarfmode screen') - end - - for i = 1,(count or 1) do - gui.simulateInput(screen, 'D_ONESTEP') - if screen.keyRepeat ~= 1 then - qerror('Could not step one frame: . did not work') - end - screen:logic() - end -end - -local function dwarfmode_to_top() - if not can_feed() then - return false - end - - local screen = screen_dwarfmode() - if not df.isvalid(screen) then - return false - end - - for i=0,10 do - if is_known 'ui' and df.global.ui.main.mode == df.ui_sidebar_mode.Default then - break - end - gui.simulateInput(screen, 'LEAVESCREEN') - end - - -- force pause just in case - screen.keyRepeat = 1 - return true -end - -local prev_cursor = df.global.T_cursor:new() -prev_cursor.x = -30000 -function try_save_cursor() - if not dfhack.internal.getAddress('cursor') then return end - for _, v in pairs(df.global.cursor) do - if v < 0 then - return - end - end - prev_cursor:assign(df.global.cursor) -end - -function try_restore_cursor() - if not dfhack.internal.getAddress('cursor') then return end - if prev_cursor.x >= 0 then - df.global.cursor:assign(prev_cursor) - dwarfmode_feed_input('CURSOR_DOWN_Z', 'CURSOR_UP_Z') - end -end - -local function feed_menu_choice(catnames,catkeys,enum,enter_seq,exit_seq,prompt) - return function (idx) - if idx == 0 and prompt and not prompt_proceed(2) then - return false - end - if idx > 0 then - dwarfmode_feed_input(table.unpack(exit_seq or {})) - end - idx = idx % #catnames + 1 - dwarfmode_feed_input(table.unpack(enter_seq or {})) - dwarfmode_feed_input(catkeys[idx]) - if enum then - return true, enum[catnames[idx]] - else - return true, catnames[idx] - end - end -end - -local function feed_list_choice(count,upkey,downkey) - return function(idx) - if idx > 0 then - local ok, len - if type(count) == 'number' then - ok, len = true, count - else - ok, len = pcall(count) - end - if not ok then - len = 5 - elseif len > 10 then - len = 10 - end - - local hcnt = len-1 - local rix = 1 + (idx-1) % (hcnt*2) - - if rix >= hcnt then - dwarfmode_feed_input(upkey or 'SECONDSCROLL_UP') - return true, hcnt*2 - rix - else - dwarfmode_feed_input(donwkey or 'SECONDSCROLL_DOWN') - return true, rix - end - else - print(' Please select the first list item.') - if not prompt_proceed(2) then - return false - end - return true, 0 - end - end -end - -local function feed_menu_bool(enter_seq, exit_seq) - return function(idx) - if idx == 0 then - if not prompt_proceed(2) then - return false - end - return true, 0 - end - if idx == 5 then - print(' Please resize the game window.') - if not prompt_proceed(2) then - return false - end - end - if idx%2 == 1 then - dwarfmode_feed_input(table.unpack(enter_seq)) - return true, 1 - else - dwarfmode_feed_input(table.unpack(exit_seq)) - return true, 0 - end - end -end - --- --- Cursor group --- - -local function find_cursor() - if not screen_title() then - return false - end - - -- Unpadded version - local idx, addr = data.int32_t:find_one{ - -30000, -30000, -30000, - -30000, -30000, -30000, -30000, -30000, -30000, - df.game_mode.NONE, df.game_type.NONE - } - if idx then - ms.found_offset('cursor', addr) - ms.found_offset('selection_rect', addr + 12) - ms.found_offset('gamemode', addr + 12 + 24) - ms.found_offset('gametype', addr + 12 + 24 + 4) - return true - end - - -- Padded version - idx, addr = data.int32_t:find_one{ - -30000, -30000, -30000, 0, - -30000, -30000, -30000, -30000, -30000, -30000, 0, 0, - df.game_mode.NONE, 0, 0, 0, df.game_type.NONE - } - if idx then - ms.found_offset('cursor', addr) - ms.found_offset('selection_rect', addr + 0x10) - ms.found_offset('gamemode', addr + 0x30) - ms.found_offset('gametype', addr + 0x40) - return true - end - - dfhack.printerr('Could not find cursor.') - return false -end - --- --- Announcements --- - -local function find_announcements() - local idx, addr = data.int32_t:find_one{ - 25, 25, 31, 31, 24, 24, 40, 40, 40, 40, 40, 40, 40 - } - if idx then - ms.found_offset('announcements', addr) - return - end - - dfhack.printerr('Could not find announcements.') -end - --- --- d_init --- - -local function is_valid_d_init(di) - if di.sky_tile ~= 178 then - print('Sky tile expected 178, found: '..di.sky_tile) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - local ann = is_known 'announcements' - local size,ptr = di:sizeof() - if ann and ptr+size ~= ann then - print('Announcements not immediately after d_init.') - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_d_init() - local idx, addr = data.int16_t:find_one{ - 1,0, 2,0, 5,0, 25,0, -- path_cost - 4,4, -- embark_rect - 20,1000,1000,1000,1000 -- store_dist - } - if idx then - validate_offset('d_init', is_valid_d_init, addr, df.d_init, 'path_cost') - return - end - - dfhack.printerr('Could not find d_init') -end - --- --- gview --- - -local function find_gview() - local vs_vtable = dfhack.internal.getVTable('viewscreenst') - if not vs_vtable then - dfhack.printerr('Cannot search for gview - no viewscreenst vtable.') - return - end - - local idx, addr = data.uint32_t:find_one{0, vs_vtable} - if idx then - ms.found_offset('gview', addr) - return - end - - idx, addr = data.uint32_t:find_one{100, vs_vtable} - if idx then - ms.found_offset('gview', addr) - return - end - - dfhack.printerr('Could not find gview') -end - --- --- enabler --- - -local function lookup_colors() - local f = io.open('data/init/colors.txt', 'r') or error('failed to open file') - local text = f:read('*all') - f:close() - local colors = {} - for _, color in pairs({'BLACK', 'BLUE', 'GREEN', 'CYAN', 'RED', 'MAGENTA', - 'BROWN', 'LGRAY', 'DGRAY', 'LBLUE', 'LGREEN', 'LCYAN', 'LRED', - 'LMAGENTA', 'YELLOW', 'WHITE'}) do - for _, part in pairs({'R', 'G', 'B'}) do - local opt = color .. '_' .. part - table.insert(colors, tonumber(text:match(opt .. ':(%d+)') or error('missing from colors.txt: ' .. opt))) - end - end - return colors -end - -local function is_valid_enabler(e) - if not ms.is_valid_vector(e.textures.raws, 4) - or not ms.is_valid_vector(e.text_system, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - return true -end - -local function find_enabler() - -- Data from data/init/colors.txt - local default_colors = { - 0, 0, 0, 0, 0, 128, 0, 128, 0, - 0, 128, 128, 128, 0, 0, 128, 0, 128, - 128, 128, 0, 192, 192, 192, 128, 128, 128, - 0, 0, 255, 0, 255, 0, 0, 255, 255, - 255, 0, 0, 255, 0, 255, 255, 255, 0, - 255, 255, 255 - } - local colors - local ok, ret = pcall(lookup_colors) - if not ok then - dfhack.printerr('Failed to look up colors, using defaults: \n' .. ret) - colors = default_colors - else - colors = ret - end - - for i = 1,#colors do colors[i] = colors[i]/255 end - - local idx, addr = data.float:find_one(colors) - if not idx then - idx, addr = data.float:find_one(default_colors) - end - if idx then - validate_offset('enabler', is_valid_enabler, addr, df.enabler, 'ccolor') - return - end - - dfhack.printerr('Could not find enabler') -end - --- --- gps --- - -local function is_valid_gps(g) - if g.clipx[0] < 0 or g.clipx[0] > g.clipx[1] or g.clipx[1] >= g.dimx then - dfhack.printerr('Invalid clipx: ', g.clipx[0], g.clipx[1], g.dimx) - end - if g.clipy[0] < 0 or g.clipy[0] > g.clipy[1] or g.clipy[1] >= g.dimy then - dfhack.printerr('Invalid clipy: ', g.clipy[0], g.clipy[1], g.dimy) - end - - return true -end - -local function find_gps() - print('\nPlease ensure the mouse cursor is not over the game window.') - if not prompt_proceed() then - return - end - - local zone - if os_type == 'windows' or os_type == 'linux' then - zone = zoomed_searcher('cursor', 0x1000) - elseif os_type == 'darwin' then - zone = zoomed_searcher('enabler', 0x1000) - end - zone = zone or searcher - - local w,h = ms.get_screen_size() - - local idx, addr = zone.area.int32_t:find_one{w, h, -1, -1} - if not idx then - idx, addr = data.int32_t:find_one{w, h, -1, -1} - end - if idx then - validate_offset('gps', is_valid_gps, addr, df.graphic, 'dimx') - return - end - - dfhack.printerr('Could not find gps') -end - --- --- World --- - -local function is_valid_world(world) - if not ms.is_valid_vector(world.units.all, 4) - or not ms.is_valid_vector(world.units.active, 4) - or not ms.is_valid_vector(world.units.bad, 4) - or not ms.is_valid_vector(world.history.figures, 4) - or not ms.is_valid_vector(world.features.map_features, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if #world.units.all == 0 or #world.units.all ~= #world.units.bad then - print('Different or zero size of units.all and units.bad:'..#world.units.all..' vs '..#world.units.bad) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_world() - local catnames = { - 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' - } - local catkeys = { - 'STOCKPILE_GRAVEYARD', 'STOCKPILE_REFUSE', 'STOCKPILE_STONE', 'STOCKPILE_WOOD', - 'STOCKPILE_GEM', 'STOCKPILE_BARBLOCK', 'STOCKPILE_CLOTH', 'STOCKPILE_LEATHER', - 'STOCKPILE_AMMO', 'STOCKPILE_COINS' - } - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_STOCKPILES') - - addr = searcher:find_interactive( - 'Auto-searching for world.', - 'int32_t', - feed_menu_choice(catnames, catkeys, df.stockpile_category), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for world. Please open the stockpile creation -menu, and select different types as instructed below:]], - 'int32_t', catnames, df.stockpile_category - ) - end - - validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type') -end - --- --- UI --- - -local function is_valid_ui(ui) - if not ms.is_valid_vector(ui.economic_stone, 1) - or not ms.is_valid_vector(ui.dipscripts, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if ui.follow_item ~= -1 or ui.follow_unit ~= -1 then - print('Invalid follow state: '..ui.follow_item..', '..ui.follow_unit) - return false - end - - return true -end - -local function find_ui() - local catnames = { - 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', - 'DesignateUpStair', 'DesignateDownStair', 'DesignateUpDownStair', - 'DesignateUpRamp', 'DesignateChopTrees' - } - local catkeys = { - 'DESIGNATE_DIG', 'DESIGNATE_CHANNEL', 'DESIGNATE_DIG_REMOVE_STAIRS_RAMPS', - 'DESIGNATE_STAIR_UP', 'DESIGNATE_STAIR_DOWN', 'DESIGNATE_STAIR_UPDOWN', - 'DESIGNATE_RAMP', 'DESIGNATE_CHOP' - } - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_DESIGNATE') - - addr = searcher:find_interactive( - 'Auto-searching for ui.', - 'int16_t', - feed_menu_choice(catnames, catkeys, df.ui_sidebar_mode), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui. Please open the designation -menu, and switch modes as instructed below:]], - 'int16_t', catnames, df.ui_sidebar_mode - ) - end - - validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode') -end - --- --- ui_sidebar_menus --- - -local function is_valid_ui_sidebar_menus(usm) - if not ms.is_valid_vector(usm.workshop_job.choices_all, 4) - or not ms.is_valid_vector(usm.workshop_job.choices_visible, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if #usm.workshop_job.choices_all == 0 - or #usm.workshop_job.choices_all ~= #usm.workshop_job.choices_visible then - print('Different or zero size of visible and all choices:'.. - #usm.workshop_job.choices_all..' vs '..#usm.workshop_job.choices_visible) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_ui_sidebar_menus() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - - addr = searcher:find_interactive([[ -Auto-searching for ui_sidebar_menus. Please select a Mason's, -Craftsdwarf's, or Carpenter's workshop:]], - 'int32_t', - function(idx) - if idx == 0 then - prompt_proceed(2) - -- ensure that the job list isn't full - dwarfmode_feed_input('BUILDJOB_CANCEL', 'BUILDJOB_ADD') - return true, 0 - end - return feed_list_choice(7)(idx) - end, - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_sidebar_menus. Please switch to 'q' mode, -select a Mason, Craftsdwarfs, or Carpenters workshop, open -the Add Job menu, and move the cursor within:]], - 'int32_t', - { 0, 1, 2, 3, 4, 5, 6 }, - ordinal_names - ) - end - - validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus, - addr, df.ui_sidebar_menus, 'workshop_job', 'cursor') -end - --- --- ui_build_selector --- - -local function is_valid_ui_build_selector(ubs) - if not ms.is_valid_vector(ubs.requirements, 4) - or not ms.is_valid_vector(ubs.choices, 4) - then - dfhack.printerr('Vector layout check failed.') - return false - end - - if ubs.building_type ~= df.building_type.Trap - or ubs.building_subtype ~= df.trap_type.PressurePlate then - print('Invalid building type and subtype:'..ubs.building_type..','..ubs.building_subtype) - return false - end - - return true -end - -local function find_ui_build_selector() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive([[ -Auto-searching for ui_build_selector. This requires mechanisms.]], - 'int32_t', - function(idx) - if idx == 0 then - dwarfmode_to_top() - dwarfmode_feed_input( - 'D_BUILDING', - 'HOTKEY_BUILDING_TRAP', - 'HOTKEY_BUILDING_TRAP_TRIGGER', - 'BUILDING_TRIGGER_ENABLE_CREATURE' - ) - else - dwarfmode_feed_input('BUILDING_TRIGGER_MIN_SIZE_UP') - end - return true, 5000 + 1000*idx - end, - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_build_selector. Please start constructing -a pressure plate, and enable creatures. Then change the min -weight as requested, knowing that the ui shows 5000 as 5K:]], - 'int32_t', - { 5000, 6000, 7000, 8000, 9000, 10000, 11000 } - ) - end - - validate_offset('ui_build_selector', is_valid_ui_build_selector, - addr, df.ui_build_selector, 'plate_info', 'unit_min') -end - --- --- init --- - -local function is_valid_init(i) - -- derived from curses_*.png image sizes presumably - if i.font.small_font_dispx ~= 8 or i.font.small_font_dispy ~= 12 or - i.font.large_font_dispx ~= 10 or i.font.large_font_dispy ~= 12 then - print('Unexpected font sizes: ', - i.font.small_font_dispx, i.font.small_font_dispy, - i.font.large_font_dispx, i.font.large_font_dispy) - if not utils.prompt_yes_no('Ignore?') then - return false - end - end - - return true -end - -local function find_init() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('ui_build_selector', 0x3000) - elseif os_type == 'linux' or os_type == 'darwin' then - zone = zoomed_searcher('d_init', -0x2000) - end - zone = zone or searcher - - local idx, addr = zone.area.int32_t:find_one{250, 150, 15, 0} - if idx then - validate_offset('init', is_valid_init, addr, df.init, 'input', 'hold_time') - return - end - - local w,h = ms.get_screen_size() - - local idx, addr = zone.area.int32_t:find_one{w, h} - if idx then - validate_offset('init', is_valid_init, addr, df.init, 'display', 'grid_x') - return - end - - dfhack.printerr('Could not find init') -end - --- --- current_weather --- - -local function find_current_weather() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('crime_next_id', 512) - elseif os_type == 'darwin' then - zone = zoomed_searcher('cursor', -64) - elseif os_type == 'linux' then - zone = zoomed_searcher('ui_building_assign_type', -512) - end - zone = zone or searcher - - local wbytes = { - 2, 1, 0, 2, 0, - 1, 2, 1, 0, 0, - 2, 0, 2, 1, 2, - 1, 2, 0, 1, 1, - 2, 0, 1, 0, 2 - } - - local idx, addr = zone.area.int8_t:find_one(wbytes) - if idx then - ms.found_offset('current_weather', addr) - return - end - - dfhack.printerr('Could not find current_weather - must be a wrong save.') -end - --- --- ui_menu_width --- - -local function find_ui_menu_width() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive('Auto-searching for ui_menu_width', 'int8_t', function(idx) - local val = (idx % 3) + 1 - if idx == 0 then - print('Switch to the default [map][menu][map] layout (with Tab)') - if not prompt_proceed(2) then return false end - else - dwarfmode_feed_input('CHANGETAB', val ~= 3 and 'CHANGETAB') - end - return true, val - end) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_menu_width. Please exit to the main -dwarfmode menu, then use Tab to do as instructed below:]], - 'int8_t', - { 2, 3, 1 }, - { [2] = 'switch to the most usual [mapmap][menu] layout', - [3] = 'hide the menu completely', - [1] = 'switch to the default [map][menu][map] layout' } - ) - end - - ms.found_offset('ui_menu_width', addr) - - -- NOTE: Assume that the vars are adjacent, as always - ms.found_offset('ui_area_map_width', addr+1) - - -- reset to make sure view is small enough for window_x/y scan on small maps - df.global.ui_menu_width = 2 - df.global.ui_area_map_width = 3 -end - --- --- ui_selected_unit --- - -local function find_ui_selected_unit() - if not is_known 'world' then - dfhack.printerr('Cannot search for ui_selected_unit: no world') - return - end - - for i,unit in ipairs(df.global.world.units.active) do - -- This function does a lot of things and accesses histfigs, souls and so on: - --dfhack.units.setNickname(unit, i) - - -- Instead use just a simple bit of code that only requires the start of the - -- unit to be valid. It may not work properly with vampires or reset later - -- if unpaused, but is sufficient for this script and won't crash: - unit.name.nickname = tostring(i) - unit.name.has_name = true - end - - local addr = searcher:find_menu_cursor([[ -Searching for ui_selected_unit. Please activate the 'v' -mode, point it at units, and enter their numeric nickname -into the prompts below:]], - 'int32_t', - function() - return utils.prompt_input(' Enter index: ', utils.check_number) - end, - 'noprompt' - ) - ms.found_offset('ui_selected_unit', addr) -end - --- --- ui_unit_view_mode --- - -local function find_ui_unit_view_mode() - local catnames = { 'General', 'Inventory', 'Preferences', 'Wounds' } - local catkeys = { 'UNITVIEW_GEN', 'UNITVIEW_INV', 'UNITVIEW_PRF', 'UNITVIEW_WND' } - local addr - - if dwarfmode_to_top() and is_known('ui_selected_unit') then - dwarfmode_feed_input('D_VIEWUNIT') - - if df.global.ui_selected_unit < 0 then - df.global.ui_selected_unit = 0 - end - - addr = searcher:find_interactive( - 'Auto-searching for ui_unit_view_mode.', - 'int32_t', - feed_menu_choice(catnames, catkeys, df.ui_unit_view_mode.T_value), - 10 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_unit_view_mode. Having selected a unit -with 'v', switch the pages as requested:]], - 'int32_t', catnames, df.ui_unit_view_mode.T_value - ) - end - - ms.found_offset('ui_unit_view_mode', addr) -end - --- --- ui_look_cursor --- - -local function look_item_list_count() - return #df.global.ui_look_list.items -end - -local function find_ui_look_cursor() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_LOOK') - - addr = searcher:find_interactive([[ -Auto-searching for ui_look_cursor. Please select a tile -with at least 5 items or units on the ground, and move -the cursor as instructed:]], - 'int32_t', - feed_list_choice(look_item_list_count), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_look_cursor. Please activate the 'k' -mode, find a tile with many items or units on the ground, -and select list entries as instructed:]], - 'int32_t', - list_index_choices(look_item_list_count), - ordinal_names - ) - end - - ms.found_offset('ui_look_cursor', addr) -end - --- --- ui_building_item_cursor --- - -local function building_item_list_count() - return #df.global.world.selected_building.contained_items -end - -local function find_ui_building_item_cursor() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDITEM') - - addr = searcher:find_interactive([[ -Auto-searching for ui_building_item_cursor. Please highlight a -workshop, trade depot or other building with at least 5 contained -items, and select as instructed:]], - 'int32_t', - feed_list_choice(building_item_list_count), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_building_item_cursor. Please activate the 't' -mode, find a cluttered workshop, trade depot, or other building -with many contained items, and select as instructed:]], - 'int32_t', - list_index_choices(building_item_list_count), - ordinal_names - ) - end - - ms.found_offset('ui_building_item_cursor', addr) -end - --- --- ui_workshop_in_add --- - -local function find_ui_workshop_in_add() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - - addr = searcher:find_interactive([[ -Auto-searching for ui_workshop_in_add. Please select a -workshop, e.g. Carpenters or Masons.]], - 'int8_t', - feed_menu_bool( - { 'BUILDJOB_CANCEL', 'BUILDJOB_ADD' }, - { 'SELECT', 'SELECT', 'SELECT', 'SELECT', 'SELECT' } - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_workshop_in_add. Please activate the 'q' -mode, find a workshop without jobs (or delete jobs), -and do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the add job menu', - [0] = 'add job, thus exiting the menu' } - ) - end - - ms.found_offset('ui_workshop_in_add', addr) -end - --- --- ui_workshop_job_cursor --- - -local function workshop_job_list_count() - return #df.global.world.selected_building.jobs -end - -local function find_ui_workshop_job_cursor() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - addr = searcher:find_interactive([[ -Auto-searching for ui_workshop_job_cursor. Please highlight a -Mason's or Carpenter's workshop, or any building with a job -selection interface navigable with just "Enter":]], - 'int32_t', - function(idx) - if idx == 0 then prompt_proceed(2) end - for i = 1, 10 - workshop_job_list_count() do - dwarfmode_feed_input('BUILDJOB_ADD', 'SELECT', 'SELECT', 'SELECT', 'SELECT', 'SELECT') - end - dwarfmode_feed_input('SECONDSCROLL_DOWN') - -- adding jobs resets the cursor position, so it is difficult to determine here - return true - end, - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_workshop_job_cursor. Please activate the 'q' -mode, find a workshop with many jobs, and select as instructed:]], - 'int32_t', - list_index_choices(workshop_job_list_count), - ordinal_names - ) - end - - ms.found_offset('ui_workshop_job_cursor', addr) -end - --- --- ui_building_in_assign --- - -local function find_ui_building_in_assign() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - try_restore_cursor() - - addr = searcher:find_interactive([[ -Auto-searching for ui_building_in_assign. Please select a room, -i.e. a bedroom, tomb, office, dining room or statue garden.]], - 'int8_t', - feed_menu_bool( - { { 'BUILDJOB_STATUE_ASSIGN', 'BUILDJOB_COFFIN_ASSIGN', - 'BUILDJOB_CHAIR_ASSIGN', 'BUILDJOB_TABLE_ASSIGN', - 'BUILDJOB_BED_ASSIGN' } }, - { 'LEAVESCREEN' } - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_building_in_assign. Please activate -the 'q' mode, select a room building (e.g. a bedroom) -and do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Assign owner menu', - [0] = 'press Esc to exit assign' } - ) - end - - ms.found_offset('ui_building_in_assign', addr) -end - --- --- ui_building_in_resize --- - -local function find_ui_building_in_resize() - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - try_restore_cursor() - - addr = searcher:find_interactive([[ -Auto-searching for ui_building_in_resize. Please select a room, -i.e. a bedroom, tomb, office, dining room or statue garden.]], - 'int8_t', - feed_menu_bool( - { { 'BUILDJOB_STATUE_SIZE', 'BUILDJOB_COFFIN_SIZE', - 'BUILDJOB_CHAIR_SIZE', 'BUILDJOB_TABLE_SIZE', - 'BUILDJOB_BED_SIZE' } }, - { 'LEAVESCREEN' } - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_building_in_resize. Please activate -the 'q' mode, select a room building (e.g. a bedroom) -and do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Resize room mode', - [0] = 'press Esc to exit resize' } - ) - end - - ms.found_offset('ui_building_in_resize', addr) -end - --- --- ui_lever_target_type --- -local function find_ui_lever_target_type() - local catnames = { - 'Bridge', 'Door', 'Floodgate', - 'Cage', 'Chain', 'TrackStop', - 'GearAssembly', - } - local catkeys = { - 'HOTKEY_TRAP_BRIDGE', 'HOTKEY_TRAP_DOOR', 'HOTKEY_TRAP_FLOODGATE', - 'HOTKEY_TRAP_CAGE', 'HOTKEY_TRAP_CHAIN', 'HOTKEY_TRAP_TRACK_STOP', - 'HOTKEY_TRAP_GEAR_ASSEMBLY', - } - local addr - - if dwarfmode_to_top() then - dwarfmode_feed_input('D_BUILDJOB') - - addr = searcher:find_interactive( - 'Auto-searching for ui_lever_target_type. Please select a lever:', - 'int8_t', - feed_menu_choice(catnames, catkeys, df.lever_target_type, - {'BUILDJOB_ADD'}, - {'LEAVESCREEN', 'LEAVESCREEN'}, - true -- prompt - ), - 20 - ) - end - - if not addr then - addr = searcher:find_menu_cursor([[ -Searching for ui_lever_target_type. Please select a lever with -'q' and enter the "add task" menu with 'a':]], - 'int8_t', catnames, df.lever_target_type - ) - end - - ms.found_offset('ui_lever_target_type', addr) -end - --- --- window_x --- - -local function feed_window_xyz(dec,inc,step) - return function(idx) - if idx == 0 then - for i = 1,30 do dwarfmode_feed_input(dec) end - else - dwarfmode_feed_input(inc) - end - return true, nil, step - end -end - -local function find_window_x() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for window_x.', - 'int32_t', - feed_window_xyz('CURSOR_LEFT_FAST', 'CURSOR_RIGHT', 10), - 20 - ) - - dwarfmode_feed_input('D_HOTKEY1') - end - - if not addr then - addr = searcher:find_counter([[ -Searching for window_x. Please exit to main dwarfmode menu, -scroll to the LEFT edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Right to scroll right one step.' - ) - end - - ms.found_offset('window_x', addr) -end - --- --- window_y --- - -local function find_window_y() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for window_y.', - 'int32_t', - feed_window_xyz('CURSOR_UP_FAST', 'CURSOR_DOWN', 10), - 20 - ) - - dwarfmode_feed_input('D_HOTKEY1') - end - - if not addr then - addr = searcher:find_counter([[ -Searching for window_y. Please exit to main dwarfmode menu, -scroll to the TOP edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Down to scroll down one step.' - ) - end - - ms.found_offset('window_y', addr) -end - --- --- window_z --- - -local function find_window_z() - local addr - - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for window_z.', - 'int32_t', - feed_window_xyz('CURSOR_UP_Z', 'CURSOR_DOWN_Z', -1), - 30 - ) - - dwarfmode_feed_input('D_HOTKEY1') - end - - if not addr then - addr = searcher:find_counter([[ -Searching for window_z. Please exit to main dwarfmode menu, -scroll to a Z level near surface, then do as instructed below. - -NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int32_t', -1, - "Please press '>' to scroll one Z level down." - ) - end - - ms.found_offset('window_z', addr) -end - --- --- cur_year --- - -local function find_cur_year() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('formation_next_id', 32) - elseif os_type == 'darwin' then - zone = zoomed_searcher('cursor', -32) - elseif os_type == 'linux' then - zone = zoomed_searcher('ui_building_assign_type', -512) - end - if not zone then - dfhack.printerr('Cannot search for cur_year - prerequisites missing.') - return - end - - local yvalue = utils.prompt_input('Please enter current in-game year: ', utils.check_number) - local idx, addr = zone.area.int32_t:find_one{yvalue} - if idx then - ms.found_offset('cur_year', addr) - return - end - - dfhack.printerr('Could not find cur_year') -end - --- --- cur_year_tick --- - -function stop_autosave() - if is_known 'd_init' then - local f = df.global.d_init.flags4 - if f.AUTOSAVE_SEASONAL or f.AUTOSAVE_YEARLY then - f.AUTOSAVE_SEASONAL = false - f.AUTOSAVE_YEARLY = false - print('Disabled seasonal and yearly autosave.') - end - else - dfhack.printerr('Could not disable autosave!') - end -end - -function step_n_frames(cnt, feed) - local world = df.global.world - local ctick = world.frame_counter - - if feed then - print(" Auto-stepping "..cnt.." frames.") - dwarfmode_step_frames(cnt) - return world.frame_counter-ctick - end - - local more = '' - while world.frame_counter-ctick < cnt do - print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") - more = ' more' - if not prompt_proceed(2) then - return nil - end - end - return world.frame_counter-ctick -end - -local function find_cur_year_tick() - local zone - if os_type == 'windows' then - zone = zoomed_searcher('ui_unit_view_mode', 0x200) - else - zone = zoomed_searcher('cur_year', 128) - end - if not zone then - dfhack.printerr('Cannot search for cur_year_tick - prerequisites missing.') - return - end - - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = zone:find_interactive( - 'Searching for cur_year_tick.', - 'int32_t', - function(idx) - if idx > 0 then - if not step_n_frames(1, feed) then - return false - end - end - return true, nil, 1 - end, - 20 - ) - - ms.found_offset('cur_year_tick', addr) -end - -local function find_cur_year_tick_advmode() - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = searcher:find_interactive( - 'Searching for cur_year_tick_advmode.', - 'int32_t', - function(idx) - if idx > 0 then - if not step_n_frames(1, feed) then - return false - end - end - return true, nil, 144 - end, - 20 - ) - - ms.found_offset('cur_year_tick_advmode', addr) -end - --- --- cur_season_tick --- - -local function find_cur_season_tick() - if not (is_known 'cur_year_tick') then - dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.') - return - end - - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = searcher:find_interactive([[ -Searching for cur_season_tick. Please exit to main dwarfmode -menu, then do as instructed below:]], - 'int32_t', - function(ccursor) - if ccursor > 0 then - if not step_n_frames(10, feed) then - return false - end - end - return true, math.floor((df.global.cur_year_tick%100800)/10) - end - ) - ms.found_offset('cur_season_tick', addr) -end - --- --- cur_season --- - -local function find_cur_season() - if not (is_known 'cur_year_tick' and is_known 'cur_season_tick') then - dfhack.printerr('Cannot search for cur_season - prerequisites missing.') - return - end - - stop_autosave() - - local feed = dwarfmode_to_top() - local addr = searcher:find_interactive([[ -Searching for cur_season. Please exit to main dwarfmode -menu, then do as instructed below:]], - 'int8_t', - function(ccursor) - if ccursor > 0 then - local cst = df.global.cur_season_tick - df.global.cur_season_tick = 10079 - df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10 - if not step_n_frames(10, feed) then - return false - end - end - return true, math.floor(df.global.cur_year_tick/100800)%4 - end - ) - ms.found_offset('cur_season', addr) -end - --- --- process_jobs --- - -local function get_process_zone() - if os_type == 'windows' then - return zoomed_searcher('ui_workshop_job_cursor', 'ui_building_in_resize') - elseif os_type == 'linux' or os_type == 'darwin' then - return zoomed_searcher('cur_year', 'cur_year_tick') - end -end - -local function find_process_jobs() - local zone = get_process_zone() or searcher - local addr - - stop_autosave() - - if dwarfmode_to_top() and dfhack.internal.getAddress('cursor') then - local cursor = df.global.T_cursor:new() - addr = zone:find_interactive([[ -Searching for process_jobs. Please position the cursor to the left -of at least 10 vacant natural floor tiles.]], - 'int8_t', - function(idx) - if idx == 0 then - dwarfmode_feed_input('D_LOOK') - if not prompt_proceed(2) then return false end - cursor:assign(df.global.cursor) - elseif idx == 6 then - print(' Please resize the game window.') - if not prompt_proceed(2) then return false end - end - dwarfmode_to_top() - dwarfmode_step_frames(1) - if idx % 2 == 0 then - dwarfmode_feed_input( - 'D_BUILDING', - 'HOTKEY_BUILDING_CONSTRUCTION', - 'HOTKEY_BUILDING_CONSTRUCTION_WALL' - ) - df.global.cursor:assign(cursor) - df.global.cursor.x = df.global.cursor.x + (idx / 2) - dwarfmode_feed_input('CURSOR_RIGHT', 'CURSOR_LEFT', 'SELECT', 'SELECT') - return true, 1 - else - return true, 0 - end - end, - 20) - end - - if not addr then - local addr = zone:find_menu_cursor([[ -Searching for process_jobs. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'designate a building to be constructed, e.g a bed or a wall', - [0] = 'step or unpause the game to reset the flag' } - ) - end - ms.found_offset('process_jobs', addr) -end - --- --- process_dig --- - -local function find_process_dig() - local zone = get_process_zone() or searcher - local addr - - stop_autosave() - - if dwarfmode_to_top() and dfhack.internal.getAddress('cursor') then - local cursor = df.global.T_cursor:new() - addr = zone:find_interactive([[ -Searching for process_dig. Please position the cursor to the left -of at least 10 unmined, unrevealed tiles.]], - 'int8_t', - function(idx) - if idx == 0 then - dwarfmode_feed_input('D_LOOK') - if not prompt_proceed(2) then return false end - cursor:assign(df.global.cursor) - elseif idx == 6 then - print(' Please resize the game window.') - if not prompt_proceed(2) then return false end - end - dwarfmode_to_top() - dwarfmode_step_frames(1) - if idx % 2 == 0 then - dwarfmode_feed_input('D_DESIGNATE', 'DESIGNATE_DIG') - df.global.cursor:assign(cursor) - df.global.cursor.x = df.global.cursor.x + (idx / 2) - dwarfmode_feed_input('SELECT', 'SELECT') - return true, 1 - else - return true, 0 - end - end, - 20) - end - - if not addr then - addr = zone:find_menu_cursor([[ -Searching for process_dig. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'designate a tile to be mined out', - [0] = 'step or unpause the game to reset the flag' } - ) - end - ms.found_offset('process_dig', addr) -end - --- --- pause_state --- - -local function find_pause_state() - local zone, addr - if os_type == 'linux' or os_type == 'darwin' then - zone = zoomed_searcher('ui_look_cursor', 32) - elseif os_type == 'windows' then - zone = zoomed_searcher('ui_workshop_job_cursor', 80) - end - zone = zone or searcher - - stop_autosave() - - if dwarfmode_to_top() then - addr = zone:find_interactive( - 'Auto-searching for pause_state', - 'int8_t', - function(idx) - if idx%2 == 0 then - dwarfmode_feed_input('D_ONESTEP') - return true, 0 - else - screen_dwarfmode():logic() - return true, 1 - end - end, - 20 - ) - end - - if not addr then - addr = zone:find_menu_cursor([[ -Searching for pause_state. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'PAUSE the game', - [0] = 'UNPAUSE the game' } - ) - end - - ms.found_offset('pause_state', addr) -end - --- --- standing orders --- - -local function find_standing_orders(gname, seq, depends) - if type(seq) ~= 'table' then seq = {seq} end - for k, v in pairs(depends) do - if not dfhack.internal.getAddress(k) then - qerror(('Cannot locate %s: %s not found'):format(gname, k)) - end - df.global[k] = v - end - local addr - if dwarfmode_to_top() then - addr = searcher:find_interactive( - 'Auto-searching for ' .. gname, - 'uint8_t', - function(idx) - dwarfmode_feed_input('D_ORDERS') - dwarfmode_feed_input(table.unpack(seq)) - return true - end - ) - else - dfhack.printerr("Won't scan for standing orders global manually: " .. gname) - return - end - - ms.found_offset(gname, addr) -end - -local function exec_finder_so(gname, seq, _depends) - local depends = {} - for k, v in pairs(_depends or {}) do - if k:find('standing_orders_') ~= 1 then - k = 'standing_orders_' .. k - end - depends[k] = v - end - if force_scan['standing_orders'] then - force_scan[gname] = true - end - exec_finder(function() - return find_standing_orders(gname, seq, depends) - end, gname) -end - --- --- MAIN FLOW --- - -print('\nInitial globals (need title screen):\n') - -exec_finder(find_gview, 'gview') -exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' }) -exec_finder(find_announcements, 'announcements') -exec_finder(find_d_init, 'd_init', is_valid_d_init) -exec_finder(find_enabler, 'enabler', is_valid_enabler) -exec_finder(find_gps, 'gps', is_valid_gps) - -print('\nCompound globals (need loaded world):\n') - -print('\nPlease load the save previously processed with prepare-save.') -if not prompt_proceed() then - searcher:reset() - return -end - -exec_finder(find_world, 'world', is_valid_world) -exec_finder(find_ui, 'ui', is_valid_ui) -exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus') -exec_finder(find_ui_build_selector, 'ui_build_selector') -exec_finder(find_init, 'init', is_valid_init) - -print('\nPrimitive globals:\n') - -exec_finder(find_current_weather, 'current_weather') -exec_finder(find_ui_menu_width, { 'ui_menu_width', 'ui_area_map_width' }) -exec_finder(find_ui_selected_unit, 'ui_selected_unit') -exec_finder(find_ui_unit_view_mode, 'ui_unit_view_mode') -exec_finder(find_ui_look_cursor, 'ui_look_cursor') -exec_finder(find_ui_building_item_cursor, 'ui_building_item_cursor') -exec_finder(find_ui_workshop_in_add, 'ui_workshop_in_add') -exec_finder(find_ui_workshop_job_cursor, 'ui_workshop_job_cursor') -exec_finder(find_ui_building_in_assign, 'ui_building_in_assign') -exec_finder(find_ui_building_in_resize, 'ui_building_in_resize') -exec_finder(find_ui_lever_target_type, 'ui_lever_target_type') -exec_finder(find_window_x, 'window_x') -exec_finder(find_window_y, 'window_y') -exec_finder(find_window_z, 'window_z') - -print('\nUnpausing globals:\n') - -exec_finder(find_cur_year, 'cur_year') -exec_finder(find_cur_year_tick, 'cur_year_tick') -exec_finder(find_cur_year_tick_advmode, 'cur_year_tick_advmode') -exec_finder(find_cur_season_tick, 'cur_season_tick') -exec_finder(find_cur_season, 'cur_season') -exec_finder(find_process_jobs, 'process_jobs') -exec_finder(find_process_dig, 'process_dig') -exec_finder(find_pause_state, 'pause_state') - -print('\nStanding orders:\n') - -exec_finder_so('standing_orders_gather_animals', 'ORDERS_GATHER_ANIMALS') -exec_finder_so('standing_orders_gather_bodies', 'ORDERS_GATHER_BODIES') -exec_finder_so('standing_orders_gather_food', 'ORDERS_GATHER_FOOD') -exec_finder_so('standing_orders_gather_furniture', 'ORDERS_GATHER_FURNITURE') -exec_finder_so('standing_orders_gather_minerals', 'ORDERS_GATHER_STONE') -exec_finder_so('standing_orders_gather_wood', 'ORDERS_GATHER_WOOD') - -exec_finder_so('standing_orders_gather_refuse', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_GATHER'}) -exec_finder_so('standing_orders_gather_refuse_outside', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_OUTSIDE'}, {gather_refuse=1}) -exec_finder_so('standing_orders_gather_vermin_remains', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_OUTSIDE_VERMIN'}, {gather_refuse=1, gather_refuse_outside=1}) -exec_finder_so('standing_orders_dump_bones', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_BONE'}, {gather_refuse=1}) -exec_finder_so('standing_orders_dump_corpses', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_CORPSE'}, {gather_refuse=1}) -exec_finder_so('standing_orders_dump_hair', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_STRAND_TISSUE'}, {gather_refuse=1}) -exec_finder_so('standing_orders_dump_other', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_OTHER'}, {gather_refuse=1}) -exec_finder_so('standing_orders_dump_shells', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SHELL'}, {gather_refuse=1}) -exec_finder_so('standing_orders_dump_skins', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SKIN'}, {gather_refuse=1}) -exec_finder_so('standing_orders_dump_skulls', - {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SKULL'}, {gather_refuse=1}) - - -exec_finder_so('standing_orders_auto_butcher', - {'ORDERS_WORKSHOP', 'ORDERS_BUTCHER'}) -exec_finder_so('standing_orders_auto_collect_webs', - {'ORDERS_WORKSHOP', 'ORDERS_COLLECT_WEB'}) -exec_finder_so('standing_orders_auto_fishery', - {'ORDERS_WORKSHOP', 'ORDERS_AUTO_FISHERY'}) -exec_finder_so('standing_orders_auto_kiln', - {'ORDERS_WORKSHOP', 'ORDERS_AUTO_KILN'}) -exec_finder_so('standing_orders_auto_kitchen', - {'ORDERS_WORKSHOP', 'ORDERS_AUTO_KITCHEN'}) -exec_finder_so('standing_orders_auto_loom', - {'ORDERS_WORKSHOP', 'ORDERS_LOOM'}) -exec_finder_so('standing_orders_auto_other', - {'ORDERS_WORKSHOP', 'ORDERS_AUTO_OTHER'}) -exec_finder_so('standing_orders_auto_slaughter', - {'ORDERS_WORKSHOP', 'ORDERS_SLAUGHTER'}) -exec_finder_so('standing_orders_auto_smelter', - {'ORDERS_WORKSHOP', 'ORDERS_AUTO_SMELTER'}) -exec_finder_so('standing_orders_auto_tan', - {'ORDERS_WORKSHOP', 'ORDERS_TAN'}) -exec_finder_so('standing_orders_use_dyed_cloth', - {'ORDERS_WORKSHOP', 'ORDERS_DYED_CLOTH'}) - -exec_finder_so('standing_orders_forbid_other_dead_items', - {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_OTHER_ITEMS'}) -exec_finder_so('standing_orders_forbid_other_nohunt', - {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_OTHER_CORPSE'}) -exec_finder_so('standing_orders_forbid_own_dead', - {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_YOUR_CORPSE'}) -exec_finder_so('standing_orders_forbid_own_dead_items', - {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_YOUR_ITEMS'}) -exec_finder_so('standing_orders_forbid_used_ammo', - {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_PROJECTILE'}) - -exec_finder_so('standing_orders_farmer_harvest', 'ORDERS_ALL_HARVEST') -exec_finder_so('standing_orders_job_cancel_announce', 'ORDERS_EXCEPTIONS') -exec_finder_so('standing_orders_mix_food', 'ORDERS_MIXFOODS') - -exec_finder_so('standing_orders_zoneonly_drink', - {'ORDERS_ZONE', 'ORDERS_ZONE_DRINKING'}) -exec_finder_so('standing_orders_zoneonly_fish', - {'ORDERS_ZONE', 'ORDERS_ZONE_FISHING'}) - -dwarfmode_to_top() -print('\nDone. Now exit the game with the die command and add\n'.. - 'the newly-found globals to symbols.xml. You can find them\n'.. - 'in stdout.log or here:\n') - -for _, global in ipairs(finder_searches) do - local addr = dfhack.internal.getAddress(global) - if addr ~= nil then - local ival = addr - dfhack.internal.getRebaseDelta() - print(string.format("", global, ival)) - end -end - -searcher:reset() diff --git a/scripts/devel/inject-raws.lua b/scripts/devel/inject-raws.lua deleted file mode 100644 index 048d4b560..000000000 --- a/scripts/devel/inject-raws.lua +++ /dev/null @@ -1,197 +0,0 @@ --- Inject new raw definitions into the world ---[[=begin - -devel/inject-raws -================= -WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE. - -This script attempts to inject new raw objects into your -world. If the injected references do not match the actual -edited raws, your save will refuse to load, or load but crash. - -This script can handle reaction, item and building definitions. - -The savegame contains a list of the relevant definition tokens in -the right order, but all details are read from raws every time. -This allows just adding stub definitions, and simply saving and -reloading the game. - -This is useful enough for modders and some users to justify the danger. - -Usage example:: - - devel/inject-raws trapcomp ITEM_TRAPCOMP_STEAM_PISTON workshop STEAM_ENGINE MAGMA_STEAM_ENGINE reaction STOKE_BOILER - -=end]] - -local utils = require 'utils' - -local raws = df.global.world.raws - -print[[ -WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE. - -This script attempts to inject new raw objects into your -world. If the injected references do not match the actual -edited raws, your save will refuse to load, or load but crash. -]] - -if not utils.prompt_yes_no('Did you make a backup?') then - qerror('Not backed up.') -end - -df.global.pause_state = true - -local changed = false - -function inject_reaction(name) - for _,v in ipairs(raws.reactions) do - if v.code == name then - print('Reaction '..name..' already exists.') - return - end - end - - print('Injecting reaction '..name) - changed = true - - raws.reactions:insert('#', { - new = true, - code = name, - name = 'Dummy reaction '..name, - index = #raws.reactions, - }) -end - -local building_types = { - workshop = { df.building_def_workshopst, raws.buildings.workshops }, - furnace = { df.building_def_furnacest, raws.buildings.furnaces }, -} - -function inject_building(btype, name) - for _,v in ipairs(raws.buildings.all) do - if v.code == name then - print('Building '..name..' already exists.') - return - end - end - - print('Injecting building '..name) - changed = true - - local typeinfo = building_types[btype] - - local id = raws.buildings.next_id - raws.buildings.next_id = id+1 - - raws.buildings.all:insert('#', { - new = typeinfo[1], - code = name, - name = 'Dummy '..btype..' '..name, - id = id, - }) - - typeinfo[2]:insert('#', raws.buildings.all[#raws.buildings.all-1]) -end - -local itemdefs = raws.itemdefs -local item_types = { - weapon = { df.itemdef_weaponst, itemdefs.weapons, 'weapon_type' }, - trainweapon = { df.itemdef_weaponst, itemdefs.weapons, 'training_weapon_type' }, - pick = { df.itemdef_weaponst, itemdefs.weapons, 'digger_type' }, - trapcomp = { df.itemdef_trapcompst, itemdefs.trapcomps, 'trapcomp_type' }, - toy = { df.itemdef_toyst, itemdefs.toys, 'toy_type' }, - tool = { df.itemdef_toolst, itemdefs.tools, 'tool_type' }, - instrument = { df.itemdef_instrumentst, itemdefs.instruments, 'instrument_type' }, - armor = { df.itemdef_armorst, itemdefs.armor, 'armor_type' }, - ammo = { df.itemdef_ammost, itemdefs.ammo, 'ammo_type' }, - siegeammo = { df.itemdef_siegeammost, itemdefs.siege_ammo, 'siegeammo_type' }, - gloves = { df.itemdef_glovest, itemdefs.gloves, 'gloves_type' }, - shoes = { df.itemdef_shoest, itemdefs.shoes, 'shoes_type' }, - shield = { df.itemdef_shieldst, itemdefs.shields, 'shield_type' }, - helm = { df.itemdef_helmst, itemdefs.helms, 'helm_type' }, - pants = { df.itemdef_pantsst, itemdefs.pants, 'pants_type' }, - food = { df.itemdef_foodst, itemdefs.food }, -} - -function add_to_civ(entity, bvec, id) - for _,v in ipairs(entity.resources[bvec]) do - if v == id then - return - end - end - - entity.resources[bvec]:insert('#', id) -end - -function add_to_dwarf_civs(btype, id) - local typeinfo = item_types[btype] - if not typeinfo[3] then - print('Not adding to civs.') - end - - for _,entity in ipairs(df.global.world.entities.all) do - if entity.race == df.global.ui.race_id then - add_to_civ(entity, typeinfo[3], id) - end - end -end - -function inject_item(btype, name) - for _,v in ipairs(itemdefs.all) do - if v.id == name then - print('Itemdef '..name..' already exists.') - return - end - end - - print('Injecting item '..name) - changed = true - - local typeinfo = item_types[btype] - local vec = typeinfo[2] - local id = #vec - - vec:insert('#', { - new = typeinfo[1], - id = name, - subtype = id, - name = name, - name_plural = name, - }) - - itemdefs.all:insert('#', vec[id]) - - add_to_dwarf_civs(btype, id) -end - -local args = {...} -local mode = nil -local ops = {} - -for _,kv in ipairs(args) do - if mode and string.match(kv, '^[%u_]+$') then - table.insert(ops, curry(mode, kv)) - elseif kv == 'reaction' then - mode = inject_reaction - elseif building_types[kv] then - mode = curry(inject_building, kv) - elseif item_types[kv] then - mode = curry(inject_item, kv) - else - qerror('Invalid option: '..kv) - end -end - -if #ops > 0 then - print('') - for _,v in ipairs(ops) do - v() - end -end - -if changed then - print('\nNow without unpausing save and reload the game to re-read raws.') -else - print('\nNo changes made.') -end diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua deleted file mode 100644 index 8f622756f..000000000 --- a/scripts/devel/inspect-screen.lua +++ /dev/null @@ -1,110 +0,0 @@ --- Read from the screen and display info about the tiles ---[[=begin - -devel/inspect-screen -==================== -Read the tiles from the screen and display info about them. - -=end]] - -local utils = require 'utils' -local gui = require 'gui' - -InspectScreen = defclass(InspectScreen, gui.Screen) - -function InspectScreen:init(args) - local w,h = dfhack.screen.getWindowSize() - self.cursor_x = math.floor(w/2) - self.cursor_y = math.floor(h/2) -end - -function InspectScreen:computeFrame(parent_rect) - local sw, sh = parent_rect.width, parent_rect.height - self.cursor_x = math.max(0, math.min(self.cursor_x, sw-1)) - self.cursor_y = math.max(0, math.min(self.cursor_y, sh-1)) - - local frame = { w = 14, r = 1, h = 10, t = 1 } - if self.cursor_x > sw/2 then - frame = { w = 14, l = 1, h = 10, t = 1 } - end - - return gui.compute_frame_body(sw, sh, frame, 1, 0, false) -end - -function InspectScreen:onRenderFrame(dc, rect) - self:renderParent() - self.cursor_pen = dfhack.screen.readTile(self.cursor_x, self.cursor_y) - if gui.blink_visible(100) then - dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y) - end - dc:fill(rect, {ch=' ',fg=COLOR_WHITE,bg=COLOR_CYAN}) -end - -local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true} -local BG_PEN = {fg=COLOR_BLACK,bg=COLOR_WHITE,tile_color=true} -local TXT_PEN = {fg=COLOR_WHITE} - -function InspectScreen:onRenderBody(dc) - dc:pen(COLOR_WHITE, COLOR_CYAN) - if self.cursor_pen then - local info = self.cursor_pen - dc:string('CH: '):char(info.ch, FG_PEN):char(info.ch, BG_PEN):string(' '):string(''..info.ch,TXT_PEN):newline() - local fgcolor = info.fg - local fgstr = info.fg - if info.bold then - fgcolor = (fgcolor+8)%16 - fgstr = fgstr..'+8' - end - dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN) - dc:seek(dc.width-1):char(info.ch,{fg=info.fg,bold=info.bold}):newline() - dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN) - dc:seek(dc.width-1):char(info.ch,{fg=COLOR_BLACK,bg=info.bg}):newline() - local bstring = 'false' - if info.bold then bstring = 'true' end - dc:string('Bold: '..bstring):newline():newline() - - if info.tile and gui.USE_GRAPHICS then - dc:string('TL: '):tile(' ', info.tile, FG_PEN):tile(' ', info.tile, BG_PEN):string(' '..info.tile):newline() - if info.tile_color then - dc:string('Color: true') - elseif info.tile_fg then - dc:string('FG: '):string('NN',{fg=info.tile_fg}):string(' '):string(''..info.tile_fg,TXT_PEN):newline() - dc:string('BG: '):string('NN',{fg=info.tile_bg}):string(' '):string(''..info.tile_bg,TXT_PEN):newline() - end - end - else - dc:string('Invalid', COLOR_LIGHTRED) - end -end - -local MOVEMENT_KEYS = { - CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 }, - CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, - CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 }, - CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 }, - CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, - CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, - CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, - CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true }, -} - -function InspectScreen:onInput(keys) - if keys.LEAVESCREEN then - self:dismiss() - else - for k,v in pairs(MOVEMENT_KEYS) do - if keys[k] then - local delta = 1 - if v[4] then - delta = 10 - end - self.cursor_x = self.cursor_x + delta*v[1] - self.cursor_y = self.cursor_y + delta*v[2] - self:updateLayout() - return - end - end - end -end - -InspectScreen{}:show() diff --git a/scripts/devel/light.lua b/scripts/devel/light.lua deleted file mode 100644 index e19591e11..000000000 --- a/scripts/devel/light.lua +++ /dev/null @@ -1,388 +0,0 @@ --- an experimental lighting engine ---[[=begin - -devel/light -=========== -An experimental lighting engine for DF, using the `rendermax` plugin. - -Call ``devel/light static`` to not recalculate lighting when in game. -Press :kbd:`~` to recalculate lighting. Press :kbd:`\`` to exit. - -=end]] - -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local render = require 'plugins.rendermax' - -local levelDim=0.05 -local tile_attrs = df.tiletype.attrs - -local args={...} - -function setCell(x,y,cell) - cell=cell or {} - cell.fm=cell.fm or {r=1,g=1,b=1} - cell.bm=cell.bm or {r=1,g=1,b=1} - cell.fo=cell.fo or {r=0,g=0,b=0} - cell.bo=cell.bo or {r=0,g=0,b=0} - render.setCell(x,y,cell) -end -function getCursorPos() - local g_cursor=df.global.cursor - if g_cursor.x ~= -30000 then - return copyall(g_cursor) - end -end -function falloff(color,sqDist,maxdist) - local v1=1/(sqDist/maxdist+1) - local v2=v1-1/(1+maxdist*maxdist) - local v=v2/(1-1/(1+maxdist*maxdist)) - return {r=v*color.r,g=v*color.g,b=v*color.b} -end -function blend(c1,c2) -return {r=math.max(c1.r,c2.r), - g=math.max(c1.g,c2.g), - b=math.max(c1.b,c2.b)} -end -LightOverlay=defclass(LightOverlay,guidm.DwarfOverlay) -LightOverlay.ATTRS { - lightMap={}, - dynamic=true, - dirty=false, -} -function LightOverlay:init(args) - - self.tick=df.global.cur_year_tick_advmode -end - -function lightPassable(shape) - if shape==df.tiletype_shape.WALL or - shape==df.tiletype_shape.BROOK_BED or - shape==df.tiletype_shape.TREE then - return false - else - return true - end -end -function circle(xm, ym,r,plot) - local x = -r - local y = 0 - local err = 2-2*r -- /* II. Quadrant */ - repeat - plot(xm-x, ym+y);--/* I. Quadrant */ - plot(xm-y, ym-x);--/* II. Quadrant */ - plot(xm+x, ym-y);--/* III. Quadrant */ - plot(xm+y, ym+x);--/* IV. Quadrant */ - r = err; - if (r <= y) then - y=y+1 - err =err+y*2+1; --/* e_xy+e_y < 0 */ - end - if (r > x or err > y) then - x=x+1 - err =err+x*2+1; --/* e_xy+e_x > 0 or no 2nd y-step */ - end - until (x >= 0); -end -function line(x0, y0, x1, y1,plot) - local dx = math.abs(x1-x0) - local dy = math.abs(y1-y0) - local sx,sy - if x0 < x1 then sx = 1 else sx = -1 end - if y0 < y1 then sy = 1 else sy = -1 end - local err = dx-dy - - while true do - if not plot(x0,y0) then - return - end - if x0 == x1 and y0 == y1 then - break - end - local e2 = 2*err - if e2 > -dy then - err = err - dy - x0 = x0 + sx - end - if x0 == x1 and y0 == y1 then - if not plot(x0,y0) then - return - end - break - end - if e2 < dx then - err = err + dx - y0 = y0 + sy - end - end -end -function LightOverlay:calculateFovs() - self.fovs=self.fovs or {} - self.precalc=self.precalc or {} - for k,v in ipairs(self.fovs) do - self:calculateFov(v.pos,v.radius,v.color) - end -end -function LightOverlay:calculateFov(pos,radius,color) - local vp=self:getViewport() - local map = self.df_layout.map - local ray=function(tx,ty) - local power=copyall(color) - local lx=pos.x - local ly=pos.y - local setTile=function(x,y) - if x>0 and y>0 and x<=map.width and y<=map.height then - local dtsq=(lx-x)*(lx-x)+(ly-y)*(ly-y) - local dt=math.sqrt(dtsq) - local tile=x+y*map.width - if self.precalc[tile] then - local tcol=blend(self.precalc[tile],power) - if tcol.r==self.precalc[tile].r and tcol.g==self.precalc[tile].g and self.precalc[tile].b==self.precalc[tile].b - and dtsq>0 then - return false - end - end - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - local ncol=blend(power,ocol) - - self.lightMap[tile]=ncol - local v=self.ocupancy[tile] - if dtsq>0 then - power.r=power.r*(v.r^dt) - power.g=power.g*(v.g^dt) - power.b=power.b*(v.b^dt) - end - lx=x - ly=y - local pwsq=power.r*power.r+power.g*power.g+power.b*power.b - return pwsq>levelDim*levelDim - end - return false - end - line(pos.x,pos.y,tx,ty,setTile) - end - circle(pos.x,pos.y,radius,ray) -end -function LightOverlay:placeLightFov(pos,radius,color) - local map = self.df_layout.map - local tile=pos.x+pos.y*map.width - local ocol=self.precalc[tile] or {r=0,g=0,b=0} - local ncol=blend(color,ocol) - self.precalc[tile]=ncol - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - local ncol=blend(color,ocol) - self.lightMap[tile]=ncol - table.insert(self.fovs,{pos=pos,radius=radius,color=color}) -end -function LightOverlay:placeLightFov2(pos,radius,color,f,rays) - f=f or falloff - local raycount=rays or 25 - local vp=self:getViewport() - local map = self.df_layout.map - local off=math.random(0,math.pi) - local done={} - for d=0,math.pi*2,math.pi*2/raycount do - local dx,dy - dx=math.cos(d+off) - dy=math.sin(d+off) - local cx=0 - local cy=0 - - for dt=0,radius,0.01 do - if math.abs(math.floor(dt*dx)-cx)>0 or math.abs(math.floor(dt*dy)-cy)> 0 then - local x=cx+pos.x - local y=cy+pos.y - - if x>0 and y>0 and x<=map.width and y<=map.height and not done[tile] then - local tile=x+y*map.width - done[tile]=true - local ncol=f(color,dt*dt,radius) - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - ncol=blend(ncol,ocol) - self.lightMap[tile]=ncol - - - if --(ncol.r==ocol.r and ncol.g==ocol.g and ncol.b==ocol.b) or - not self.ocupancy[tile] then - break - end - end - cx=math.floor(dt*dx) - cy=math.floor(dt*dy) - end - end - end -end -function LightOverlay:placeLight(pos,radius,color,f) - f=f or falloff - local vp=self:getViewport() - local map = self.df_layout.map - - for i=-radius,radius do - for j=-radius,radius do - local x=pos.x+i+1 - local y=pos.y+j+1 - if x>0 and y>0 and x<=map.width and y<=map.height then - local tile=x+y*map.width - local ncol=f(color,(i*i+j*j),radius) - local ocol=self.lightMap[tile] or {r=0,g=0,b=0} - self.lightMap[tile]=blend(ncol,ocol) - end - end - end -end -function LightOverlay:calculateLightLava() - local vp=self:getViewport() - local map = self.df_layout.map - for i=map.x1,map.x2 do - for j=map.y1,map.y2 do - local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} - local pos2={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z-1} - local t1=dfhack.maps.getTileFlags(pos) - local tt=dfhack.maps.getTileType(pos) - if tt then - local shape=tile_attrs[tt].shape - local t2=dfhack.maps.getTileFlags(pos2) - if (t1 and t1.liquid_type and t1.flow_size>0) or - (shape==df.tiletype_shape.EMPTY and t2 and t2.liquid_type and t2.flow_size>0) then - --self:placeLight({x=i,y=j},5,{r=0.8,g=0.2,b=0.2}) - self:placeLightFov({x=i,y=j},5,{r=0.8,g=0.2,b=0.2},nil) - end - end - end - end -end -function LightOverlay:calculateLightSun() - local vp=self:getViewport() - local map = self.df_layout.map - for i=map.x1,map.x2+1 do - for j=map.y1,map.y2+1 do - local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} - - local t1=dfhack.maps.getTileFlags(pos) - - if (t1 and t1.outside ) then - - self:placeLightFov({x=i,y=j},15,{r=1,g=1,b=1},nil) - end - end - end -end -function LightOverlay:calculateLightCursor() - local c=getCursorPos() - - if c then - - local vp=self:getViewport() - local pos=vp:tileToScreen(c) - --self:placeLight(pos,11,{r=0.96,g=0.84,b=0.03}) - self:placeLightFov({x=pos.x+1,y=pos.y+1},11,{r=0.96,g=0.84,b=0.03}) - - end -end -function LightOverlay:buildOcupancy() - self.ocupancy={} - local vp=self:getViewport() - local map = self.df_layout.map - for i=map.x1,map.x2+1 do - for j=map.y1,map.y2+1 do - local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} - local tile=i+j*map.width - local tt=dfhack.maps.getTileType(pos) - local t1=dfhack.maps.getTileFlags(pos) - if tt then - local shape=tile_attrs[tt].shape - if not lightPassable(shape) then - self.ocupancy[tile]={r=0,g=0,b=0} - else - if t1 and not t1.liquid_type and t1.flow_size>2 then - self.ocupancy[tile]={r=0.5,g=0.5,b=0.7} - else - self.ocupancy[tile]={r=0.8,g=0.8,b=0.8} - end - end - end - end - end -end -function LightOverlay:changed() - if self.dirty or self.tick~=df.global.cur_year_tick_advmode then - self.dirty=false - self.tick=df.global.cur_year_tick_advmode - return true - end - return false -end -function LightOverlay:makeLightMap() - if not self:changed() then - return - end - self.fovs={} - self.precalc={} - self.lightMap={} - - self:buildOcupancy() - self:calculateLightCursor() - self:calculateLightLava() - self:calculateLightSun() - - self:calculateFovs() -end -function LightOverlay:onIdle() - self._native.parent:logic() -end -function LightOverlay:render(dc) - if self.dynamic then - self:makeLightMap() - end - self:renderParent() - local vp=self:getViewport() - local map = self.df_layout.map - - self.lightMap=self.lightMap or {} - render.lockGrids() - render.invalidate({x=map.x1,y=map.y1,w=map.width,h=map.height}) - render.resetGrids() - for i=map.x1,map.x2 do - for j=map.y1,map.y2 do - local v=self.lightMap[i+j*map.width] - if v then - setCell(i,j,{fm=v,bm=v}) - else - local dimRgb={r=levelDim,g=levelDim,b=levelDim} - setCell(i,j,{fm=dimRgb,bm=dimRgb}) - end - end - end - render.unlockGrids() - -end -function LightOverlay:onDismiss() - render.lockGrids() - render.resetGrids() - render.invalidate() - render.unlockGrids() - -end -function LightOverlay:onInput(keys) - if keys.STRING_A096 then - self:dismiss() - else - self:sendInputToParent(keys) - - if keys.CHANGETAB then - self:updateLayout() - end - if keys.STRING_A126 and not self.dynamic then - self:makeLightMap() - end - self.dirty=true - end -end -if not render.isEnabled() then - qerror("Lua rendermode not enabled!") -end -local dyn=true -if #args>0 and args[1]=="static" then dyn=false end -local lview = LightOverlay{ dynamic=dyn} -lview:show() diff --git a/scripts/devel/list-filters.lua b/scripts/devel/list-filters.lua deleted file mode 100644 index 7eb2210a9..000000000 --- a/scripts/devel/list-filters.lua +++ /dev/null @@ -1,78 +0,0 @@ --- List input items for the building being built. ---[[=begin - -devel/list-filters -================== -List input items for the building currently being built. -This is where the filters in lua/dfhack/buildings.lua come from. - -=end]] - -local dumper = require 'dumper' -local utils = require 'utils' -local buildings = require 'dfhack.buildings' - -local function name_enum(tgt,name,ename,enum) - if tgt[name] ~= nil then - tgt[name] = ename..'.'..enum[tgt[name]] - end -end - -local lookup = {} -local items = df.global.world.items - -for i=df.job_item_vector_id._first_item,df.job_item_vector_id._last_item do - local id = df.job_item_vector_id.attrs[i].other - local ptr - if id == df.items_other_id.ANY then - ptr = items.all - elseif id == df.items_other_id.BAD then - ptr = items.bad - else - ptr = items.other[id] - end - if ptr then - local _,addr = df.sizeof(ptr) - lookup[addr] = 'df.job_item_vector_id.'..df.job_item_vector_id[i] - end -end - -local function clone_filter(src,quantity) - local tgt = utils.clone_with_default(src, buildings.input_filter_defaults, true) - if quantity ~= 1 then - tgt.quantity = quantity - end - name_enum(tgt, 'item_type', 'df.item_type', df.item_type) - name_enum(tgt, 'has_tool_use', 'df.tool_uses', df.tool_uses) - local ptr = src.item_vector - if ptr and ptr ~= df.global.world.items.other[0] then - local _,addr = df.sizeof(ptr) - tgt.vector_id = lookup[addr] - end - return tgt -end - -local function dump(name) - local out = {} - for i,v in ipairs(df.global.ui_build_selector.requirements) do - out[#out+1] = clone_filter(v.filter, v.count_required) - end - - local fmt = dumper.DataDumper(out,name,false,1,4) - fmt = string.gsub(fmt, '"(df%.[^"]+)"','%1') - fmt = string.gsub(fmt, '%s+$', '') - print(fmt) -end - -local itype = df.global.ui_build_selector.building_type -local stype = df.global.ui_build_selector.building_subtype - -if itype == df.building_type.Workshop then - dump(' [df.workshop_type.'..df.workshop_type[stype]..'] = ') -elseif itype == df.building_type.Furnace then - dump(' [df.furnace_type.'..df.furnace_type[stype]..'] = ') -elseif itype == df.building_type.Trap then - dump(' [df.trap_type.'..df.trap_type[stype]..'] = ') -else - dump(' [df.building_type.'..df.building_type[itype]..'] = ') -end diff --git a/scripts/devel/lsmem.lua b/scripts/devel/lsmem.lua deleted file mode 100644 index b390adc5c..000000000 --- a/scripts/devel/lsmem.lua +++ /dev/null @@ -1,21 +0,0 @@ --- Prints memory ranges of the process. ---[[=begin - -devel/lsmem -=========== -Prints memory ranges of the process. - -=end]] - -for _,v in ipairs(dfhack.internal.getMemRanges()) do - local access = { '-', '-', '-', 'p' } - if v.read then access[1] = 'r' end - if v.write then access[2] = 'w' end - if v.execute then access[3] = 'x' end - if not v.valid then - access[4] = '?' - elseif v.shared then - access[4] = 's' - end - print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name)) -end diff --git a/scripts/devel/lua-example.lua b/scripts/devel/lua-example.lua deleted file mode 100644 index bbdb04ed6..000000000 --- a/scripts/devel/lua-example.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Example of a lua script. ---[[=begin - -devel/lua-example -================= -An example lua script, which reports the number of times it has -been called. Useful for testing environment persistence. - -=end]] - -run_count = (run_count or 0) + 1 - -print('Arguments: ',...) -print('Command called '..run_count..' times.') diff --git a/scripts/devel/make-dt.pl b/scripts/devel/make-dt.pl deleted file mode 100644 index e3f390802..000000000 --- a/scripts/devel/make-dt.pl +++ /dev/null @@ -1,492 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -my ($version, $timestamp, $hash); - -open FH, 'version.lisp' or die "Cannot open version"; -while () { - if (/df-version-str.*\"(.*)\"/) { - $version = $1; - } elsif (/windows-timestamp.*#x([0-9a-f]+)/) { - $timestamp = $1; - } elsif (/linux-hash.*\"(.*)\"/) { - $hash = $1; - } -} -close FH; - -sub load_csv(\%$) { - my ($rhash, $fname) = @_; - - open FH, $fname or die "Cannot open $fname"; - while () { - next unless /^\"([^\"]*)\",\"(\d+)\",\"(?:0x([0-9a-fA-F]+))?\",\"[^\"]*\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\"/; - my ($top, $level, $addr, $type, $name, $target) = ($1,$2,$3,$4,$5,$6); - next if defined $rhash->{$top}{$name}; - $rhash->{$top}{$name} = ($type eq 'enum-item' ? $target : hex $addr); - } - close FH; -} - -our $complete; - -sub lookup_addr(\%$$;$) { - my ($rhash, $top, $name, $bias) = @_; - - my $val = $rhash->{$top}{$name}; - unless (defined $val) { - $complete = 0; - return 0; - } - return $val + ($bias||0); -} - -our @lines; - -sub emit_header($) { - my ($name) = @_; - push @lines, '' if @lines; - push @lines, "[$name]"; -} - -sub emit_addr($\%$$;$) { - my ($name, $rhash, $top, $var, $bias) = @_; - - my $val = $rhash->{$top}{$var}; - if (defined $val) { - $val += ($bias||0); - if ($val < 0x10000) { - push @lines, sprintf('%s=0x%04x', $name, $val); - } else { - push @lines, sprintf('%s=0x%08x', $name, $val); - } - } else { - $complete = 0; - push @lines, "$name=0x0"; - } -} - -sub generate_dt_ini($$$$) { - my ($subdir, $version, $checksum, $ssize) = @_; - - my %globals; - load_csv %globals, "$subdir/globals.csv"; - my %all; - load_csv %all, "$subdir/all.csv"; - - local $complete = 1; - local @lines; - - emit_header 'addresses'; - emit_addr 'translation_vector',%globals,'world','world.raws.language.translations'; - emit_addr 'language_vector',%globals,'world','world.raws.language.words'; - emit_addr 'creature_vector',%globals,'world','world.units.all'; - emit_addr 'active_creature_vector',%globals,'world','world.units.active'; - emit_addr 'dwarf_race_index',%globals,'ui','ui.race_id'; - emit_addr 'squad_vector',%globals,'world','world.squads.all'; - emit_addr 'current_year',%globals,'cur_year','cur_year'; - emit_addr 'cur_year_tick',%globals,'cur_year_tick','cur_year_tick'; - emit_addr 'dwarf_civ_index',%globals,'ui','ui.civ_id'; - emit_addr 'races_vector',%globals,'world','world.raws.creatures.all'; - emit_addr 'reactions_vector',%globals,'world','world.raws.reactions'; - emit_addr 'events_vector',%globals,'world','world.history.events'; - emit_addr 'historical_figures_vector',%globals,'world','world.history.figures'; - emit_addr 'fake_identities_vector',%globals,'world','world.identities.all'; - emit_addr 'fortress_entity',%globals,'ui','ui.main.fortress_entity'; - emit_addr 'historical_entities_vector',%globals,'world','world.entities.all'; - emit_addr 'itemdef_weapons_vector',%globals,'world','world.raws.itemdefs.weapons'; - emit_addr 'itemdef_trap_vector',%globals,'world','world.raws.itemdefs.trapcomps'; - emit_addr 'itemdef_toy_vector',%globals,'world','world.raws.itemdefs.toys'; - emit_addr 'itemdef_tool_vector',%globals,'world','world.raws.itemdefs.tools'; - emit_addr 'itemdef_instrument_vector',%globals,'world','world.raws.itemdefs.instruments'; - emit_addr 'itemdef_armor_vector',%globals,'world','world.raws.itemdefs.armor'; - emit_addr 'itemdef_ammo_vector',%globals,'world','world.raws.itemdefs.ammo'; - emit_addr 'itemdef_siegeammo_vector',%globals,'world','world.raws.itemdefs.siege_ammo'; - emit_addr 'itemdef_glove_vector',%globals,'world','world.raws.itemdefs.gloves'; - emit_addr 'itemdef_shoe_vector',%globals,'world','world.raws.itemdefs.shoes'; - emit_addr 'itemdef_shield_vector',%globals,'world','world.raws.itemdefs.shields'; - emit_addr 'itemdef_helm_vector',%globals,'world','world.raws.itemdefs.helms'; - emit_addr 'itemdef_pant_vector',%globals,'world','world.raws.itemdefs.pants'; - emit_addr 'itemdef_food_vector',%globals,'world','world.raws.itemdefs.food'; - emit_addr 'colors_vector',%globals,'world','world.raws.language.colors'; - emit_addr 'shapes_vector',%globals,'world','world.raws.language.shapes'; - emit_addr 'base_materials',%globals,'world','world.raws.mat_table.builtin'; - emit_addr 'inorganics_vector',%globals,'world','world.raws.inorganics'; - emit_addr 'plants_vector',%globals,'world','world.raws.plants.all'; - emit_addr 'material_templates_vector',%globals,'world','world.raws.material_templates'; - emit_addr 'all_syndromes_vector',%globals,'world','world.raws.syndromes.all'; - emit_addr 'world_data',%globals,'world','world.world_data'; - emit_addr 'active_sites_vector',%all,'world_data','active_site'; - emit_addr 'world_site_type',%all,'world_site','type'; - emit_addr 'weapons_vector',%globals,'world','world.items.other[WEAPON]'; - emit_addr 'shields_vector',%globals,'world','world.items.other[SHIELD]'; - emit_addr 'quivers_vector',%globals,'world','world.items.other[QUIVER]'; - emit_addr 'crutches_vector',%globals,'world','world.items.other[CRUTCH]'; - emit_addr 'backpacks_vector',%globals,'world','world.items.other[BACKPACK]'; - emit_addr 'ammo_vector',%globals,'world','world.items.other[AMMO]'; - emit_addr 'flasks_vector',%globals,'world','world.items.other[FLASK]'; - emit_addr 'pants_vector',%globals,'world','world.items.other[PANTS]'; - emit_addr 'armor_vector',%globals,'world','world.items.other[ARMOR]'; - emit_addr 'shoes_vector',%globals,'world','world.items.other[SHOES]'; - emit_addr 'helms_vector',%globals,'world','world.items.other[HELM]'; - emit_addr 'gloves_vector',%globals,'world','world.items.other[GLOVES]'; - emit_addr 'artifacts_vector',%globals,'world','world.artifacts.all'; - - emit_header 'offsets'; - emit_addr 'word_table',%all,'language_translation','words'; - push @lines, 'string_buffer_offset=0x0000'; - - emit_header 'word_offsets'; - emit_addr 'base',%all,'language_word','word'; - emit_addr 'noun_singular',%all,'language_word','forms[Noun]'; - emit_addr 'noun_plural',%all,'language_word','forms[NounPlural]'; - emit_addr 'adjective',%all,'language_word','forms[Adjective]'; - emit_addr 'verb',%all,'language_word','forms[Verb]'; - emit_addr 'present_simple_verb',%all,'language_word','forms[Verb3rdPerson]'; - emit_addr 'past_simple_verb',%all,'language_word','forms[VerbPast]'; - emit_addr 'past_participle_verb',%all,'language_word','forms[VerbPassive]'; - emit_addr 'present_participle_verb',%all,'language_word','forms[VerbGerund]'; - emit_addr 'words',%all,'language_name','words'; - emit_addr 'word_type',%all,'language_name','parts_of_speech'; - emit_addr 'language_id',%all,'language_name','language'; - - emit_header 'general_ref_offsets'; - emit_addr 'ref_type',%all,'general_ref::vtable','getType'; - emit_addr 'artifact_id',%all,'general_ref_artifact','artifact_id'; - emit_addr 'item_id',%all,'general_ref_item','item_id'; - - emit_header 'race_offsets'; - emit_addr 'name_singular',%all,'creature_raw','name'; - emit_addr 'name_plural',%all,'creature_raw','name',$ssize; - emit_addr 'adjective',%all,'creature_raw','name',$ssize*2; - emit_addr 'baby_name_singular',%all,'creature_raw','general_baby_name'; - emit_addr 'baby_name_plural',%all,'creature_raw','general_baby_name',$ssize; - emit_addr 'child_name_singular',%all,'creature_raw','general_child_name'; - emit_addr 'child_name_plural',%all,'creature_raw','general_child_name',$ssize; - emit_addr 'pref_string_vector',%all,'creature_raw','prefstring'; - emit_addr 'castes_vector',%all,'creature_raw','caste'; - emit_addr 'pop_ratio_vector',%all,'creature_raw','pop_ratio'; - emit_addr 'materials_vector',%all,'creature_raw','material'; - emit_addr 'flags',%all,'creature_raw','flags'; - emit_addr 'tissues_vector',%all,'creature_raw','tissue'; - - emit_header 'caste_offsets'; - emit_addr 'caste_name',%all,'caste_raw','caste_name'; - emit_addr 'caste_descr',%all,'caste_raw','description'; - emit_addr 'caste_trait_ranges',%all,'caste_raw','personality.a'; - emit_addr 'caste_phys_att_ranges',%all,'caste_raw','attributes.phys_att_range'; - emit_addr 'baby_age',%all,'caste_raw','misc.baby_age'; - emit_addr 'child_age',%all,'caste_raw','misc.child_age'; - emit_addr 'adult_size',%all,'caste_raw','misc.adult_size'; - emit_addr 'flags',%all,'caste_raw','flags'; - emit_addr 'body_info',%all,'caste_raw','body_info'; - emit_addr 'skill_rates',%all,'caste_raw','skill_rates'; - emit_addr 'caste_att_rates',%all,'caste_raw','attributes.phys_att_rates'; - emit_addr 'caste_att_caps',%all,'caste_raw','attributes.phys_att_cap_perc'; - emit_addr 'shearable_tissues_vector',%all,'caste_raw','shearable_tissue_layer'; - emit_addr 'extracts',%all,'caste_raw','extracts.extract_matidx'; - - emit_header 'hist_entity_offsets'; - emit_addr 'beliefs',%all,'historical_entity','resources.values'; - emit_addr 'squads',%all,'historical_entity','squads'; - emit_addr 'positions',%all,'historical_entity','positions.own'; - emit_addr 'assignments',%all,'historical_entity','positions.assignments'; - emit_addr 'assign_hist_id',%all,'entity_position_assignment','histfig'; - emit_addr 'assign_position_id',%all,'entity_position_assignment','position_id'; - emit_addr 'position_id',%all,'entity_position','id'; - emit_addr 'position_name',%all,'entity_position','name'; - emit_addr 'position_female_name',%all,'entity_position','name_female'; - emit_addr 'position_male_name',%all,'entity_position','name_male'; - - emit_header 'hist_figure_offsets'; - emit_addr 'hist_race',%all,'historical_figure','race'; - emit_addr 'hist_name',%all,'historical_figure','name'; - emit_addr 'id',%all,'historical_figure','id'; - emit_addr 'hist_fig_info',%all,'historical_figure','info'; - emit_addr 'reputation',%all,'historical_figure_info','reputation'; - emit_addr 'current_ident',%all,'historical_figure_info::anon13','cur_identity'; - emit_addr 'fake_name',%all,'identity','name'; - emit_addr 'fake_birth_year',%all,'identity','birth_year'; - emit_addr 'fake_birth_time',%all,'identity','birth_second'; - emit_addr 'kills',%all,'historical_figure_info','kills'; - emit_addr 'killed_race_vector',%all,'historical_kills','killed_race'; - emit_addr 'killed_undead_vector',%all,'historical_kills','killed_undead'; - emit_addr 'killed_counts_vector',%all,'historical_kills','killed_count'; - - emit_header 'hist_event_offsets'; - emit_addr 'event_year',%all,'history_event','year'; - emit_addr 'id',%all,'history_event','id'; - emit_addr 'killed_hist_id',%all,'history_event_hist_figure_diedst','victim_hf'; - - emit_header 'item_offsets'; - if ($subdir eq 'osx') { - push @lines, 'item_type=0x0004'; - } else { - push @lines, 'item_type=0x0001'; - } - emit_addr 'item_def',%all,'item_ammost','subtype'; #currently same for all - emit_addr 'id',%all,'item','id'; - emit_addr 'general_refs',%all,'item','general_refs'; - emit_addr 'stack_size',%all,'item_actual','stack_size'; - emit_addr 'wear',%all,'item_actual','wear'; - emit_addr 'mat_type',%all,'item_crafted','mat_type'; - emit_addr 'mat_index',%all,'item_crafted','mat_index'; - emit_addr 'quality',%all,'item_crafted','quality'; - - emit_header 'item_subtype_offsets'; - emit_addr 'sub_type',%all,'itemdef','subtype'; - emit_addr 'name',%all,'itemdef_armorst','name'; - emit_addr 'name_plural',%all,'itemdef_armorst','name_plural'; - emit_addr 'adjective',%all,'itemdef_armorst','name_preplural'; - - emit_header 'item_filter_offsets'; - emit_addr 'item_subtype',%all,'item_filter_spec','item_subtype'; - emit_addr 'mat_class',%all,'item_filter_spec','material_class'; - emit_addr 'mat_type',%all,'item_filter_spec','mattype'; - emit_addr 'mat_index',%all,'item_filter_spec','matindex'; - - emit_header 'weapon_subtype_offsets'; - emit_addr 'single_size',%all,'itemdef_weaponst','two_handed'; - emit_addr 'multi_size',%all,'itemdef_weaponst','minimum_size'; - emit_addr 'ammo',%all,'itemdef_weaponst','ranged_ammo'; - emit_addr 'melee_skill',%all,'itemdef_weaponst','skill_melee'; - emit_addr 'ranged_skill',%all,'itemdef_weaponst','skill_ranged'; - - emit_header 'armor_subtype_offsets'; - emit_addr 'layer',%all,'armor_properties','layer'; - emit_addr 'mat_name',%all,'itemdef_armorst','material_placeholder'; - emit_addr 'other_armor_level',%all,'itemdef_helmst','armorlevel'; - emit_addr 'armor_adjective',%all,'itemdef_armorst','adjective'; - emit_addr 'armor_level',%all,'itemdef_armorst','armorlevel'; - emit_addr 'chest_armor_properties',%all,'itemdef_armorst','props'; - emit_addr 'pants_armor_properties',%all,'itemdef_pantsst','props'; - emit_addr 'other_armor_properties',%all,'itemdef_helmst','props'; - - emit_header 'material_offsets'; - emit_addr 'solid_name',%all,'material_common','state_name[Solid]'; - emit_addr 'liquid_name',%all,'material_common','state_name[Liquid]'; - emit_addr 'gas_name',%all,'material_common','state_name[Gas]'; - emit_addr 'powder_name',%all,'material_common','state_name[Powder]'; - emit_addr 'paste_name',%all,'material_common','state_name[Paste]'; - emit_addr 'pressed_name',%all,'material_common','state_name[Pressed]'; - emit_addr 'flags',%all,'material_common','flags'; - emit_addr 'inorganic_materials_vector',%all,'inorganic_raw','material'; - emit_addr 'inorganic_flags',%all,'inorganic_raw','flags'; - - emit_header 'plant_offsets'; - emit_addr 'name',%all,'plant_raw','name'; - emit_addr 'name_plural',%all,'plant_raw','name_plural'; - emit_addr 'name_leaf_plural',%all,'plant_raw','leaves_plural'; - emit_addr 'name_seed_plural',%all,'plant_raw','seed_plural'; - emit_addr 'materials_vector',%all,'plant_raw','material'; - emit_addr 'flags',%all,'plant_raw','flags'; - - emit_header 'descriptor_offsets'; - emit_addr 'color_name',%all,'descriptor_color','name'; - emit_addr 'shape_name_plural',%all,'descriptor_shape','name_plural'; - - emit_header 'health_offsets'; - emit_addr 'parent_id',%all,'body_part_raw','con_part_id'; - emit_addr 'layers_vector',%all,'body_part_raw','layers'; - emit_addr 'number',%all,'body_part_raw','number'; - emit_addr 'names_vector',%all,'body_part_raw','name_singular'; - emit_addr 'names_plural_vector',%all,'body_part_raw','name_plural'; - emit_addr 'layer_tissue',%all,'body_part_layer_raw','tissue_id'; - emit_addr 'layer_global_id',%all,'body_part_layer_raw','layer_id'; - emit_addr 'tissue_name',%all,'tissue_template','tissue_name_singular'; - emit_addr 'tissue_flags',%all,'tissue_template','flags'; - - emit_header 'dwarf_offsets'; - emit_addr 'first_name',%all,'unit','name',lookup_addr(%all,'language_name','first_name'); - emit_addr 'nick_name',%all,'unit','name',lookup_addr(%all,'language_name','nickname'); - emit_addr 'last_name',%all,'unit','name',lookup_addr(%all,'language_name','words'); - emit_addr 'custom_profession',%all,'unit','custom_profession'; - emit_addr 'profession',%all,'unit','profession'; - emit_addr 'race',%all,'unit','race'; - emit_addr 'flags1',%all,'unit','flags1'; - emit_addr 'flags2',%all,'unit','flags2'; - emit_addr 'flags3',%all,'unit','flags3'; - emit_addr 'caste',%all,'unit','caste'; - emit_addr 'sex',%all,'unit','sex'; - emit_addr 'id',%all,'unit','id'; - emit_addr 'animal_type',%all,'unit','training_level'; - emit_addr 'civ',%all,'unit','civ_id'; - emit_addr 'specific_refs',%all,'unit','specific_refs'; - emit_addr 'squad_id',%all,'unit','military.squad_id'; - emit_addr 'squad_position',%all,'unit','military.squad_position'; - emit_addr 'recheck_equipment',%all,'unit','military.pickup_flags'; - emit_addr 'mood',%all,'unit','mood'; - emit_addr 'birth_year',%all,'unit','relations.birth_year'; - emit_addr 'birth_time',%all,'unit','relations.birth_time'; - emit_addr 'pet_owner_id',%all,'unit','relations.pet_owner_id'; - emit_addr 'current_job',%all,'unit','job.current_job'; - emit_addr 'physical_attrs',%all,'unit','body.physical_attrs'; - emit_addr 'body_size',%all,'unit','appearance.body_modifiers'; - emit_addr 'size_info',%all,'unit','body.size_info'; - emit_addr 'curse',%all,'unit','curse.name'; - emit_addr 'curse_add_flags1',%all,'unit','curse.add_tags1'; - emit_addr 'turn_count',%all,'unit','curse.time_on_site'; - emit_addr 'souls',%all,'unit','status.souls'; - emit_addr 'states',%all,'unit','status.misc_traits'; - emit_addr 'labors',%all,'unit','status.labors'; - emit_addr 'hist_id',%all,'unit','hist_figure_id'; - emit_addr 'artifact_name',%all,'unit','status.artifact_name'; - emit_addr 'active_syndrome_vector',%all,'unit','syndromes.active'; - emit_addr 'syn_sick_flag',%all,'unit_syndrome','flags.is_sick'; - emit_addr 'unit_health_info',%all,'unit','health'; - emit_addr 'temp_mood',%all,'unit','counters.soldier_mood'; - emit_addr 'counters1',%all,'unit','counters.winded'; - emit_addr 'counters2',%all,'unit','counters.pain'; - emit_addr 'counters3',%all,'unit','counters2.paralysis'; - emit_addr 'limb_counters',%all,'unit','status2.limbs_stand_max'; - emit_addr 'blood',%all,'unit','body.blood_max'; - emit_addr 'body_component_info',%all,'unit','body.components'; - emit_addr 'layer_status_vector',%all,'body_component_info','layer_status'; - emit_addr 'wounds_vector',%all,'unit','body.wounds'; - emit_addr 'mood_skill',%all,'unit','job.mood_skill'; - emit_addr 'used_items_vector',%all,'unit','used_items'; - emit_addr 'affection_level',%all,'unit_item_use','affection_level'; - emit_addr 'inventory',%all,'unit','inventory'; - emit_addr 'inventory_item_mode',%all,'unit_inventory_item','mode'; - emit_addr 'inventory_item_bodypart',%all,'unit_inventory_item','body_part_id'; - - emit_header 'syndrome_offsets'; - emit_addr 'cie_effects',%all,'syndrome','ce'; - emit_addr 'cie_end',%all,'creature_interaction_effect','end'; - emit_addr 'cie_first_perc',%all,'creature_interaction_effect_phys_att_changest','phys_att_perc'; #same for mental - emit_addr 'cie_phys',%all,'creature_interaction_effect_phys_att_changest','phys_att_add'; - emit_addr 'cie_ment',%all,'creature_interaction_effect_ment_att_changest','ment_att_add'; - emit_addr 'syn_classes_vector',%all,'syndrome','syn_class'; - emit_addr 'trans_race_id',%all,'creature_interaction_effect_body_transformationst','race'; - - emit_header 'unit_wound_offsets'; - emit_addr 'parts',%all,'unit_wound','parts'; - emit_addr 'id',%all,'unit_wound::anon2','body_part_id'; - emit_addr 'layer',%all,'unit_wound::anon2','layer_idx'; - emit_addr 'general_flags',%all,'unit_wound','flags'; - emit_addr 'flags1',%all,'unit_wound::anon2','flags1'; - emit_addr 'flags2',%all,'unit_wound::anon2','flags2'; - emit_addr 'effects_vector',%all,'unit_wound::anon2','effect_type'; - emit_addr 'bleeding',%all,'unit_wound::anon2','bleeding'; - emit_addr 'pain',%all,'unit_wound::anon2','pain'; - emit_addr 'cur_pen',%all,'unit_wound::anon2','cur_penetration_perc'; - emit_addr 'max_pen',%all,'unit_wound::anon2','max_penetration_perc'; - - emit_header 'soul_details'; - emit_addr 'name',%all,'unit_soul','name'; - emit_addr 'orientation',%all,'unit_soul','orientation_flags'; - emit_addr 'mental_attrs',%all,'unit_soul','mental_attrs'; - emit_addr 'skills',%all,'unit_soul','skills'; - emit_addr 'preferences',%all,'unit_soul','preferences'; - emit_addr 'personality',%all,'unit_soul','personality'; - emit_addr 'beliefs',%all,'unit_personality','values'; - emit_addr 'emotions',%all,'unit_personality','emotions'; - emit_addr 'goals',%all,'unit_personality','dreams'; - emit_addr 'goal_realized',%all,'unit_personality::anon5','unk8'; - emit_addr 'traits',%all,'unit_personality','traits'; - emit_addr 'stress_level',%all,'unit_personality','stress_level'; - - emit_header 'emotion_offsets'; - emit_addr 'emotion_type',%all,'unit_personality::anon4','type'; - emit_addr 'strength',%all,'unit_personality::anon4','strength'; - emit_addr 'thought_id',%all,'unit_personality::anon4','thought'; - emit_addr 'sub_id',%all,'unit_personality::anon4','subthought'; - emit_addr 'level',%all,'unit_personality::anon4','severity'; - emit_addr 'year',%all,'unit_personality::anon4','year'; - emit_addr 'year_tick',%all,'unit_personality::anon4','year_tick'; - - emit_header 'job_details'; - emit_addr 'id',%all,'job','job_type'; - emit_addr 'mat_type',%all,'job','mat_type'; - emit_addr 'mat_index',%all,'job','mat_index'; - emit_addr 'mat_category',%all,'job','material_category'; - emit_addr 'on_break_flag',%all,'misc_trait_type','OnBreak'; - emit_addr 'sub_job_id',%all,'job','reaction_name'; - emit_addr 'reaction',%all,'reaction','name'; - emit_addr 'reaction_skill',%all,'reaction','skill'; - - emit_header 'squad_offsets'; - emit_addr 'id',%all,'squad','id'; - emit_addr 'name',%all,'squad','name'; - emit_addr 'alias',%all,'squad','alias'; - emit_addr 'members',%all,'squad','positions'; - emit_addr 'carry_food',%all,'squad','carry_food'; - emit_addr 'carry_water',%all,'squad','carry_water'; - emit_addr 'ammunition',%all,'squad','ammunition'; - emit_addr 'quiver',%all,'squad_position','quiver'; - emit_addr 'backpack',%all,'squad_position','backpack'; - emit_addr 'flask',%all,'squad_position','flask'; - emit_addr 'armor_vector',%all,'squad_position','uniform[body]'; - emit_addr 'helm_vector',%all,'squad_position','uniform[head]'; - emit_addr 'pants_vector',%all,'squad_position','uniform[pants]'; - emit_addr 'gloves_vector',%all,'squad_position','uniform[gloves]'; - emit_addr 'shoes_vector',%all,'squad_position','uniform[shoes]'; - emit_addr 'shield_vector',%all,'squad_position','uniform[shield]'; - emit_addr 'weapon_vector',%all,'squad_position','uniform[weapon]'; - emit_addr 'uniform_item_filter',%all,'squad_uniform_spec','item_filter'; - emit_addr 'uniform_indiv_choice',%all,'squad_uniform_spec','indiv_choice'; - - my $body_str = join("\n",@lines); - my $complete_str = ($complete ? 'true' : 'false'); - - open OUT, ">$subdir/therapist.ini" or die "Cannot open output file"; - print OUT <<__END__; -[info] -checksum=0x$checksum -version_name=$version -complete=$complete_str - -$body_str - -[valid_flags_2] -size=0 - -[invalid_flags_1] -size=10 -1\\name=a zombie -1\\value=0x00001000 -2\\name=a skeleton -2\\value=0x00002000 -3\\name=a merchant -3\\value=0x00000040 -4\\name=outpost liason or diplomat -4\\value=0x00000800 -5\\name=an invader or hostile -5\\value=0x00020000 -6\\name=an invader or hostile -6\\value=0x00080000 -7\\name=resident, invader or ambusher -7\\value=0x00600000 -8\\name=part of a merchant caravan -8\\value=0x00000080 -9\\name="Dead, Jim." -9\\value=0x00000002 -10\\name=marauder -10\\value=0x00000010 - -[invalid_flags_2] -size=5 -1\\name="killed, Jim." -1\\value=0x00000080 -2\\name=from the Underworld. SPOOKY! -2\\value=0x00040000 -3\\name=resident -3\\value=0x00080000 -4\\name=uninvited visitor -4\\value=0x00400000 -5\\name=visitor -5\\value=0x00800000 - -[invalid_flags_3] -size=1 -1\\name=a ghost -1\\value=0x00001000 -__END__ - close OUT; -} - -generate_dt_ini 'linux', $version, substr($hash,0,8), 4; -generate_dt_ini 'windows', $version.' (graphics)', $timestamp, 0x1C; -generate_dt_ini 'osx', $version, substr($hash,0,8), 4; \ No newline at end of file diff --git a/scripts/devel/nuke-items.lua b/scripts/devel/nuke-items.lua deleted file mode 100644 index 6b67f81c8..000000000 --- a/scripts/devel/nuke-items.lua +++ /dev/null @@ -1,22 +0,0 @@ --- Delete ALL items not held by units, buildings or jobs ---[[=begin - -devel/nuke-items -================ -Deletes ALL items not held by units, buildings or jobs. -Intended solely for lag investigation. - -=end]] - -local count = 0 - -for _,v in ipairs(df.global.world.items.all) do - if not (v.flags.in_building or v.flags.construction or v.flags.in_job - or dfhack.items.getGeneralRef(v,df.general_ref_type.UNIT_HOLDER)) then - count = count + 1 - v.flags.forbid = true - v.flags.garbage_collect = true - end -end - -print('Deletion requested: '..count) diff --git a/scripts/devel/pop-screen.lua b/scripts/devel/pop-screen.lua deleted file mode 100644 index 8bc5e45c6..000000000 --- a/scripts/devel/pop-screen.lua +++ /dev/null @@ -1,10 +0,0 @@ --- For killing bugged out gui script screens. ---[[=begin - -devel/pop-screen -================ -For killing bugged out gui script screens. - -=end]] - -dfhack.screen.dismiss(dfhack.gui.getCurViewscreen()) diff --git a/scripts/devel/prepare-save.lua b/scripts/devel/prepare-save.lua deleted file mode 100644 index c1e71ef7f..000000000 --- a/scripts/devel/prepare-save.lua +++ /dev/null @@ -1,98 +0,0 @@ --- Prepare the current save for devel/find-offsets ---[[=begin - -devel/prepare-save -================== -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -This script prepares the current savegame to be used -with `devel/find-offsets`. It CHANGES THE GAME STATE -to predefined values, and initiates an immediate -`quicksave`, thus PERMANENTLY MODIFYING the save. - -=end]] - -local utils = require 'utils' - -df.global.pause_state = true - -print[[ -WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. - -This script prepares the current savegame to be used -with devel/find-offsets. It CHANGES THE GAME STATE -to predefined values, and initiates an immediate -quicksave, thus PERMANENTLY MODIFYING the save. -]] - -if not utils.prompt_yes_no('Proceed?') then - return -end - ---[[print('Placing anchor...') - -do - local wp = df.global.ui.waypoints - - for _,pt in ipairs(wp.points) do - if pt.name == 'dfhack_anchor' then - print('Already placed.') - goto found - end - end - - local x,y,z = pos2xyz(df.global.cursor) - - if not x then - error("Place cursor at your preferred anchor point.") - end - - local id = wp.next_point_id - wp.next_point_id = id + 1 - - wp.points:insert('#',{ - new = true, id = id, name = 'dfhack_anchor', - comment=(x..','..y..','..z), - tile = string.byte('!'), fg_color = COLOR_LIGHTRED, bg_color = COLOR_BLUE, - pos = xyz2pos(x,y,z) - }) - -::found:: -end]] - -print('Nicknaming units...') - -for i,unit in ipairs(df.global.world.units.active) do - dfhack.units.setNickname(unit, i..':'..unit.id) -end - -print('Setting weather...') - -local wbytes = { - 2, 1, 0, 2, 0, - 1, 2, 1, 0, 0, - 2, 0, 2, 1, 2, - 1, 2, 0, 1, 1, - 2, 0, 1, 0, 2 -} - -for i=0,4 do - for j = 0,4 do - df.global.current_weather[i][j] = (wbytes[i*5+j+1] or 2) - end -end - -local yearstr = df.global.cur_year..','..df.global.cur_year_tick - -print('Cur year and tick: '..yearstr) - -dfhack.persistent.save{ - key='prepare-save/cur_year', - value=yearstr, - ints={df.global.cur_year, df.global.cur_year_tick} -} - --- Save - -dfhack.run_script('quicksave') - diff --git a/scripts/devel/print-args.lua b/scripts/devel/print-args.lua deleted file mode 100644 index 46024eaa4..000000000 --- a/scripts/devel/print-args.lua +++ /dev/null @@ -1,15 +0,0 @@ ---print-args.lua ---author expwnent ---[[=begin - -devel/print-args -================ -Prints all the arguments you supply to the script on their own line. -Useful for debugging other scripts. - -=end]] - -local args = {...} -for _,arg in ipairs(args) do - print(arg) -end diff --git a/scripts/devel/print-args2.lua b/scripts/devel/print-args2.lua deleted file mode 100644 index ad870623d..000000000 --- a/scripts/devel/print-args2.lua +++ /dev/null @@ -1,17 +0,0 @@ ---print-args2.lua ---author expwnent ---[[=begin - -devel/print-args2 -================= -Prints all the arguments you supply to the script on their own line -with quotes around them. - -=end]] - -local args = {...} -print("print-args") -for _,arg in ipairs(args) do - print("'"..arg.."'") -end - diff --git a/scripts/devel/save-version.lua b/scripts/devel/save-version.lua deleted file mode 100644 index 450aa04cf..000000000 --- a/scripts/devel/save-version.lua +++ /dev/null @@ -1,154 +0,0 @@ --- Display DF version information about the current save ---@module = true ---[[=begin - -devel/save-version -================== -Display DF version information about the current save - -=end]] - -local function dummy() return nil end - -function has_field(tbl, field) - return (pcall(function() assert(tbl[field] ~= nil) end)) -end - -function class_has_field(cls, field) - local obj = cls:new() - local ret = has_field(obj, field) - obj:delete() - return ret -end - -versions = { --- skipped v0.21-v0.28 - [1287] = "0.31.01", - [1288] = "0.31.02", - [1289] = "0.31.03", - [1292] = "0.31.04", - [1295] = "0.31.05", - [1297] = "0.31.06", - [1300] = "0.31.08", - [1304] = "0.31.09", - [1305] = "0.31.10", - [1310] = "0.31.11", - [1311] = "0.31.12", - [1323] = "0.31.13", - [1325] = "0.31.14", - [1326] = "0.31.15", - [1327] = "0.31.16", - [1340] = "0.31.17", - [1341] = "0.31.18", - [1351] = "0.31.19", - [1353] = "0.31.20", - [1354] = "0.31.21", - [1359] = "0.31.22", - [1360] = "0.31.23", - [1361] = "0.31.24", - [1362] = "0.31.25", - - [1372] = "0.34.01", - [1374] = "0.34.02", - [1376] = "0.34.03", - [1377] = "0.34.04", - [1378] = "0.34.05", - [1382] = "0.34.06", - [1383] = "0.34.07", - [1400] = "0.34.08", - [1402] = "0.34.09", - [1403] = "0.34.10", - [1404] = "0.34.11", - - [1441] = "0.40.01", - [1442] = "0.40.02", - [1443] = "0.40.03", - [1444] = "0.40.04", - [1445] = "0.40.05", - [1446] = "0.40.06", - [1448] = "0.40.07", - [1449] = "0.40.08", - [1451] = "0.40.09", - [1452] = "0.40.10", - [1456] = "0.40.11", - [1459] = "0.40.12", - [1462] = "0.40.13", - [1469] = "0.40.14", - [1470] = "0.40.15", - [1471] = "0.40.16", - [1472] = "0.40.17", - [1473] = "0.40.18", - [1474] = "0.40.19", - [1477] = "0.40.20", - [1478] = "0.40.21", - [1479] = "0.40.22", - [1480] = "0.40.23", - [1481] = "0.40.24", - - [1531] = "0.42.01", - [1532] = "0.42.02", - [1533] = "0.42.03", - [1534] = "0.42.04", - [1537] = "0.42.05", - [1542] = "0.42.06", - - [1551] = "0.43.01", - [1552] = "0.43.02", -} - -min_version = math.huge -max_version = -math.huge - -for k in pairs(versions) do - min_version = math.min(min_version, k) - max_version = math.max(max_version, k) -end - -if class_has_field(df.world.T_cur_savegame, 'save_version') then - function get_save_version() - return df.global.world.cur_savegame.save_version - end -elseif class_has_field(df.world.T_pathfinder, 'anon_2') then - function get_save_version() - return df.global.world.pathfinder.anon_2 - end -else - get_save_version = dummy -end - -if class_has_field(df.world, 'original_save_version') then - function get_original_save_version() - return df.global.world.original_save_version - end -else - get_original_save_version = dummy -end - -function describe(version) - if version == 0 then - return 'no world loaded' - elseif versions[version] then - return versions[version] - elseif version < min_version then - return 'unknown old version before ' .. describe(min_version) .. ': ' .. tostring(version) - elseif version > max_version then - return 'unknown new version after ' .. describe(max_version) .. ': ' .. tostring(version) - else - return 'unknown version: ' .. tostring(version) - end -end - -function dump(desc, func) - local ret = tonumber(func()) - if ret then - print(desc .. ': ' .. describe(ret)) - else - dfhack.printerr('could not find ' .. desc .. ' (DFHack version too old)') - end -end - -if not moduleMode then - if not dfhack.isWorldLoaded() then qerror('no world loaded') end - dump('original DF version', get_original_save_version) - dump('most recent DF version', get_save_version) -end diff --git a/scripts/devel/scanitemother.rb b/scripts/devel/scanitemother.rb deleted file mode 100644 index 1bdf58946..000000000 --- a/scripts/devel/scanitemother.rb +++ /dev/null @@ -1,16 +0,0 @@ -# list selected item's indices in world.item.other[] -=begin - -devel/scanitemother -=================== -List indices in ``world.item.other[]`` where current selected item appears. - -=end -tg = df.item_find -raise 'select an item' if not tg - -o = df.world.items.other -# discard ANY/BAD -o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k| - puts k if o[k].find { |i| i == tg } -} diff --git a/scripts/devel/spawn-unit-helper.rb b/scripts/devel/spawn-unit-helper.rb deleted file mode 100644 index 77dadbcdd..000000000 --- a/scripts/devel/spawn-unit-helper.rb +++ /dev/null @@ -1,35 +0,0 @@ -# Allow arena creature spawn after a mode change - -df.world.arena_spawn.race.clear -df.world.arena_spawn.caste.clear - -df.world.raws.creatures.all.length.times { |r_idx| - df.world.raws.creatures.all[r_idx].caste.length.times { |c_idx| - df.world.arena_spawn.race << r_idx - df.world.arena_spawn.caste << c_idx - } -} - -df.world.arena_spawn.creature_cnt[df.world.arena_spawn.race.length-1] = 0 - -puts < [-xy ] [-z ]') -end - -local fname = table.remove(args,1) -local goal = tonumber(table.remove(args,1)) or qerror('Invalid density') -local expr = table.remove(args,1) or qerror('No expression') -local zscale = 2 -local xyscale = 1 - -for i = 1,#args,2 do - if args[i] == '-xy' then - xyscale = tonumber(args[i+1]) or qerror('Invalid xyscale') - end - if args[i] == '-z' then - zscale = tonumber(args[i+1]) or qerror('Invalid zscale') - end -end - -local fn_env = copyall(math) - -fn_env.rng = rng -fn_env.apow = function(x,y) return math.pow(math.abs(x),y) end -fn_env.spow = function(x,y) return x*math.pow(math.abs(x),y-1) end - --- Noise functions are referenced from expressions --- as variables of form like "x3a", where: --- 1) x is one of x/y/z/w independent functions in each octave --- 2) 3 is the octave number; 0 corresponds to the whole range --- 3) a is the subtype - --- Independent noise functions: support 4 -local ids = { 'x', 'y', 'z', 'w' } --- Subtype: provides an offset to the coordinates -local subs = { - [''] = { 0, 0, 0 }, - a = { 0.5, 0, 0 }, - b = { 0, 0.5, 0 }, - c = { 0.5, 0.5, 0 }, - d = { 0, 0, 0.5 }, - e = { 0.5, 0, 0.5 }, - f = { 0, 0.5, 0.5 }, - g = { 0.5, 0.5, 0.5 }, -} - -function mkdelta(v) - if v == 0 then - return '' - else - return '+'..v - end -end - -function mkexpr(expr) - -- Collect referenced variables - local max_octave = -1 - local vars = {} - - for var,id,octave,subtype in string.gmatch(expr,'%f[%w](([xyzw])(%d+)(%a*))%f[^%w]') do - if not vars[var] then - octave = tonumber(octave) - - if octave > max_octave then - max_octave = octave - end - - local sub = subs[subtype] or qerror('Invalid subtype: '..subtype) - - vars[var] = { id = id, octave = octave, subtype = subtype, sub = sub } - end - end - - if max_octave < 0 then - qerror('No noise function references in expression.') - end - - -- Allocate the noise functions - local code = '' - - for i = 0,max_octave do - for j,id in ipairs(ids) do - code = code .. 'local _fn_'..i..'_'..id..' = rng:perlin(3)\n'; - end - end - - -- Evaluate variables - code = code .. 'return function(x,y,z)\n' - - for var,info in pairs(vars) do - local fn = '_fn_'..info.octave..'_'..info.id - local mul = math.pow(2,info.octave) - mul = math.min(48*4, mul) - code = code .. ' local '..var - .. ' = _fn_'..info.octave..'_'..info.id - .. '(x*'..mul..mkdelta(info.sub[1]) - .. ',y*'..mul..mkdelta(info.sub[2]) - .. ',z*'..mul..mkdelta(info.sub[3]) - .. ')\n' - end - - -- Complete and compile the function - code = code .. ' return ('..expr..')\nend\n' - - local f,err = load(code, '=(expr)', 't', fn_env) - if not f then - qerror(err) - end - return f() -end - -local field_fn = mkexpr(expr) - -function render(thresh,file) - local area = 0 - local line, arr = '', {} - - for zy = 0,1 do - for y = 0,48*4-1 do - line = '' - for zx = 0,1 do - for x = 0,48*4-1 do - local tx = (0.5+x)/(48*4/xyscale) - local ty = (0.5+y)/(48*4/xyscale) - local tz = 0.3+(zx+zy*2)/(48*4/zscale) - local v1 = field_fn(tx, ty, tz) - local v = -1 - if v1 > thresh then - v = v1; - area = area + 1 - end - if file then - local c = math.max(0, math.min(255, v * 127 + 128)) - arr[2*x+1] = c - arr[2*x+2] = c - end - end - if file then - line = line..string.char(table.unpack(arr)) - end - end - if file then - file:write(line,line) - end - end - end - - return area/4/(48*4)/(48*4) -end - -function search(fn,min,max,goal,eps) - local center - for i = 1,32 do - center = (max+min)/2 - local cval = fn(center) - print('At '..center..': '..cval) - if math.abs(cval-goal) < eps then - break - end - if cval > goal then - min = center - else - max = center - end - end - return center -end - -local thresh = search(render, -2, 2, goal, math.min(0.001,goal/20)) - -local file,err = io.open(fname, 'wb') -if not file then - print('error: ',err) - return -end -file:write('P5\n') -file:write('# '..goal..' '..expr..' '..xyscale..' '..zscale..'\n') -file:write('768 768\n255\n') -local area = render(thresh, file) -file:close() - -print('Area fraction: '..area) diff --git a/scripts/devel/unforbidall.rb b/scripts/devel/unforbidall.rb deleted file mode 100644 index 176bff9c0..000000000 --- a/scripts/devel/unforbidall.rb +++ /dev/null @@ -1,10 +0,0 @@ -# unforbid all items -=begin - -devel/unforbidall -================= -Unforbid all items. - -=end - -df.world.items.all.each { |i| i.flags.forbid = false } diff --git a/scripts/devel/unit-path.lua b/scripts/devel/unit-path.lua deleted file mode 100644 index 2eff7ed2d..000000000 --- a/scripts/devel/unit-path.lua +++ /dev/null @@ -1,225 +0,0 @@ --- Show the internal path a unit is currently following. ---[[=begin - -devel/unit-path -=============== -Show the internal path a unit is currently following. - -=end]] - -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local tile_attrs = df.tiletype.attrs - -UnitPathUI = defclass(UnitPathUI, guidm.MenuOverlay) - -UnitPathUI.focus_path = 'unit-path' - -UnitPathUI.ATTRS { - unit = DEFAULT_NIL, - has_path = false, - has_goal = false, -} - -function UnitPathUI:init() - self.saved_mode = df.global.ui.main.mode - if self.unit then - self.has_path = #self.unit.path.path.x > 0 - self.has_goal = self.unit.path.dest.x >= 0 - end -end - -function UnitPathUI:onShow() - -- with cursor, but without those ugly lines from native hauling mode - df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles -end - -function UnitPathUI:onDestroy() - self:moveCursorTo(copyall(self.unit.pos)) - df.global.ui.main.mode = self.saved_mode -end - -local function getTileType(cursor) - local block = dfhack.maps.getTileBlock(cursor) - if block then - return block.tiletype[cursor.x%16][cursor.y%16] - else - return 0 - end -end - -local function getTileWalkable(cursor) - local block = dfhack.maps.getTileBlock(cursor) - if block then - return block.walkable[cursor.x%16][cursor.y%16] - else - return 0 - end -end - -local function paintMapTile(dc, vp, cursor, pos, ...) - if not same_xyz(cursor, pos) then - local stile = vp:tileToScreen(pos) - if stile.z == 0 then - dc:seek(stile.x,stile.y):char(...) - end - end -end - -local function get_path_point(gpath,i) - return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) -end - -function UnitPathUI:renderPath(dc,vp,cursor) - local gpath = self.unit.path.path - local startp = self.unit.pos - local endp = self.unit.path.dest - local visible = gui.blink_visible(500) - - if visible then - paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) - end - - local ok = nil - local pcnt = #gpath.x - if pcnt > 0 then - ok = true - - for i = 0,pcnt-1 do - local pt = get_path_point(gpath, i) - if i == 0 and not same_xyz(startp,pt) then - ok = false - end - if i == pcnt-1 and not same_xyz(endp,pt) then - ok = false - end - --[[local tile = getTileType(pt) - if not isTrackTile(tile) then - ok = false - end]] - if visible then - local char = '+' - if i < pcnt-1 then - local npt = get_path_point(gpath, i+1) - if npt.z == pt.z+1 then - char = 30 - elseif npt.z == pt.z-1 then - char = 31 - elseif npt.x == pt.x+1 then - char = 26 - elseif npt.x == pt.x-1 then - char = 27 - elseif npt.y == pt.y+1 then - char = 25 - elseif npt.y == pt.y-1 then - char = 24 - end - end - local color = COLOR_LIGHTGREEN - if getTileWalkable(pt) == 0 then color = COLOR_LIGHTRED end - paintMapTile(dc, vp, cursor, pt, char, color) - end - end - end - - if gui.blink_visible(120) then - paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) - end - - return ok -end - -function UnitPathUI:onRenderBody(dc) - dc:clear():seek(1,1):pen(COLOR_WHITE):string("Unit Path") - - local prof = dfhack.units.getProfessionName(self.unit) - local name = dfhack.units.getVisibleName(self.unit) - - dc:seek(2,3):pen(COLOR_BLUE):string(prof) - if name and name.has_name then - dc:seek(2,4):pen(COLOR_BLUE):string(dfhack.TranslateName(name)) - end - - local cursor = guidm.getCursorPos() - - local vp = self:getViewport() - local mdc = gui.Painter.new(self.df_layout.map) - - if not self.has_path then - if gui.blink_visible(120) then - paintMapTile(mdc, vp, cursor, self.unit.pos, 15, COLOR_LIGHTRED, COLOR_RED) - end - - dc:seek(1,6):pen(COLOR_RED):string('Not following path') - else - self:renderPath(mdc,vp,cursor) - - dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal] or '?') - end - - dc:newline():pen(COLOR_GREY) - dc:newline(2):string('Speed: '..dfhack.units.computeMovementSpeed(self.unit)) - dc:newline(2):string('Slowdown: '..dfhack.units.computeSlowdownFactor(self.unit)) - - dc:newline():newline(1) - - local has_station = self.unit.idle_area_type >= 0 - - if has_station then - if gui.blink_visible(250) then - paintMapTile(mdc, vp, cursor, self.unit.idle_area, 21, COLOR_LIGHTCYAN) - end - - dc:pen(COLOR_GREEN):string(df.unit_station_type[self.unit.idle_area_type]) - dc:newline():newline(2):pen(COLOR_GREY):string('Threshold: '..self.unit.idle_area_threshold) - else - dc:pen(COLOR_RED):string('No station'):newline():newline(2) - end - - dc:newline():newline(1):string('At cursor:') - dc:newline():newline(2) - - local tile = getTileType(cursor) - dc:string(df.tiletype[tile],COLOR_CYAN) - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('CUSTOM_Z'):string(": Zoom unit, ") - dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,self.has_goal) - dc:newline(1) - dc:key('CUSTOM_N'):string(": Zoom station",COLOR_GREY,nil,has_station) - dc:newline():newline(1) - dc:key('LEAVESCREEN'):string(": Back") -end - -function UnitPathUI:onInput(keys) - if keys.CUSTOM_Z then - self:moveCursorTo(copyall(self.unit.pos)) - elseif keys.CUSTOM_G then - if self.has_goal then - self:moveCursorTo(copyall(self.unit.path.dest)) - end - elseif keys.CUSTOM_N then - if self.unit.idle_area_type > 0 then - self:moveCursorTo(copyall(self.unit.idle_area)) - end - elseif keys.LEAVESCREEN then - self:dismiss() - elseif self:propagateMoveKeys(keys) then - return - end -end - -function UnitPathUI:onGetSelectedUnit() - return self.unit -end - -local unit = dfhack.gui.getSelectedUnit(true) - -if not unit or not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/ViewUnits/Some/') then - qerror("This script requires the main dwarfmode view in 'v' mode with a unit selected") -end - -UnitPathUI{ unit = unit }:show() diff --git a/scripts/devel/watch-minecarts.lua b/scripts/devel/watch-minecarts.lua deleted file mode 100644 index f7eba0ca6..000000000 --- a/scripts/devel/watch-minecarts.lua +++ /dev/null @@ -1,83 +0,0 @@ --- Logs minecart coordinates and speeds to console. ---[[=begin - -devel/watch-minecarts -===================== -Logs minecart coordinates and speeds to console. - -Usage: ``devel/watch-minecarts start|stop`` - -=end]] - -last_stats = last_stats or {} - -function compare_one(vehicle) - local last = last_stats[vehicle.id] - local item = df.item.find(vehicle.item_id) - local ipos = item.pos - local new = { - ipos.x*100000 + vehicle.offset_x, vehicle.speed_x, - ipos.y*100000 + vehicle.offset_y, vehicle.speed_y, - ipos.z*100000 + vehicle.offset_z, vehicle.speed_z - } - - if (last == nil) or item.flags.on_ground then - local delta = { vehicle.id } - local show = (last == nil) - - for i=1,6 do - local rv = 0 - if last then - rv = last[i] - end - delta[i*2] = new[i]/100000 - local dv = new[i] - rv - delta[i*2+1] = dv/100000 - if dv ~= 0 then - show = true - end - end - - if show then - print(table.unpack(delta)) - end - end - - last_stats[vehicle.id] = new -end - -function compare_all() - local seen = {} - for _,v in ipairs(df.global.world.vehicles.all) do - seen[v.id] = true - compare_one(v) - end - for k,v in pairs(last_stats) do - if not seen[k] then - print(k,'DEAD') - end - end -end - -function start_timer() - if not dfhack.timeout_active(timeout_id) then - timeout_id = dfhack.timeout(1, 'ticks', function() - compare_all() - start_timer() - end); - if not timeout_id then - dfhack.printerr('Could not start timer in watch-minecarts') - end - end -end - -compare_all() - -local cmd = ... - -if cmd == 'start' then - start_timer() -elseif cmd == 'stop' then - dfhack.timeout_active(timeout_id, nil) -end - diff --git a/scripts/digfort.rb b/scripts/digfort.rb deleted file mode 100644 index d83928172..000000000 --- a/scripts/digfort.rb +++ /dev/null @@ -1,94 +0,0 @@ -# designate an area based on a '.csv' plan -=begin - -digfort -======= -A script to designate an area for digging according to a plan in csv format. - -This script, inspired from quickfort, can designate an area for digging. -Your plan should be stored in a .csv file like this:: - - # this is a comment - d;d;u;d;d;skip this tile;d - d;d;d;i - -Available tile shapes are named after the 'dig' menu shortcuts: -``d`` for dig, ``u`` for upstairs, ``j`` downstairs, ``i`` updown, -``h`` channel, ``r`` upward ramp, ``x`` remove designation. -Unrecognized characters are ignored (eg the 'skip this tile' in the sample). - -Empty lines and data after a ``#`` are ignored as comments. -To skip a row in your design, use a single ``;``. - -One comment in the file may contain the phrase ``start(3,5)``. It is interpreted -as an offset for the pattern: instead of starting at the cursor, it will start -3 tiles left and 5 tiles up from the cursor. - -The script takes the plan filename, starting from the root df folder (where -``Dwarf Fortress.exe`` is found). - -=end - -fname = $script_args[0].to_s - -if not $script_args[0] then - puts " Usage: digfort " - throw :script_finished -end -if not fname[-4..-1] == ".csv" then - puts " The plan file must be in .csv format." - throw :script_finished -end -if not File.file?(fname) then - puts " The specified file does not exist." - throw :script_finished -end - -planfile = File.read(fname) - -if df.cursor.x == -30000 - puts "place the game cursor to the top-left corner of the design and retry" - throw :script_finished -end - -offset = [0, 0] -tiles = [] -planfile.each_line { |l| - if l =~ /#.*start\s*\(\s*(-?\d+)\s*[,;]\s*(-?\d+)/ - raise "Error: multiple start() comments" if offset != [0, 0] - offset = [$1.to_i, $2.to_i] - end - - l = l.chomp.sub(/#.*/, '') - next if l == '' - tiles << l.split(/[;,]/).map { |t| - t = t.strip - (t[0] == ?") ? t[1..-2] : t - } -} - -x = df.cursor.x - offset[0] -y = df.cursor.y - offset[1] -z = df.cursor.z - -tiles.each { |line| - next if line.empty? or line == [''] - line.each { |tile| - t = df.map_tile_at(x, y, z) - s = t.shape_basic - case tile - when 'd'; t.dig(:Default) if s == :Wall - when 'u'; t.dig(:UpStair) if s == :Wall - when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor - when 'i'; t.dig(:UpDownStair) if s == :Wall - when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor - when 'r'; t.dig(:Ramp) if s == :Wall - when 'x'; t.dig(:No) - end - x += 1 - } - x = df.cursor.x - offset[0] - y += 1 -} - -puts ' done' diff --git a/scripts/drain-aquifer.lua b/scripts/drain-aquifer.lua deleted file mode 100644 index 6a79017bf..000000000 --- a/scripts/drain-aquifer.lua +++ /dev/null @@ -1,40 +0,0 @@ --- Remove all aquifers from the map ---[[=begin - -drain-aquifer -============= -Remove all 'aquifer' tag from the map blocks. Irreversible. - -=end]] - -local function drain() - local layers = {} - local layer_count = 0 - local tile_count = 0 - - for k, block in ipairs(df.global.world.map.map_blocks) do - if block.flags.has_aquifer then - block.flags.has_aquifer = false - block.flags.check_aquifer = false - - for x, row in ipairs(block.designation) do - for y, tile in ipairs(row) do - if tile.water_table then - tile.water_table = false - tile_count = tile_count + 1 - end - end - end - - if not layers[block.map_pos.z] then - layers[block.map_pos.z] = true - layer_count = layer_count + 1 - end - end - end - - print("Cleared "..tile_count.." aquifer tile"..((tile_count ~= 1) and "s" or "").. - " in "..layer_count.." layer"..((layer_count ~= 1) and "s" or "")..".") -end - -drain(...) diff --git a/scripts/elevate-mental.lua b/scripts/elevate-mental.lua deleted file mode 100644 index 9db61ed79..000000000 --- a/scripts/elevate-mental.lua +++ /dev/null @@ -1,52 +0,0 @@ --- Elevate all the mental attributes of a unit --- by vjek ---[[=begin - -elevate-mental -============== -Set all mental attributes of the selected dwarf to 2600, which is very high. -Numbers between 0 and 5000 can be passed as an argument: ``elevate-mental 100`` -for example would make the dwarf very stupid indeed. - -=end]] - -function ElevateMentalAttributes(value) - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - --print name of dwarf - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(unit))) - --walk through available attributes, adjust current to max - local ok,f,t,k = pcall(pairs,unit.status.current_soul.mental_attrs) - if ok then - for k,v in f,t,k do - if value ~= nil then - print("Adjusting current value for "..tostring(k).." of "..v.value.." to the value of "..value) - v.value=value - else - print("Adjusting current value for "..tostring(k).." of "..v.value.." to max value of "..v.max_value) - v.value=v.max_value - --below will reset values back to "normal" - --v.value=v.max_value/2 - end - end - end -end ---script execution starts here -local opt = ... -opt = tonumber(opt) - -if opt ~= nil then - if opt >=0 and opt <=5000 then - ElevateMentalAttributes(opt) - end - if opt <0 or opt >5000 then - print("Invalid Range or argument. This script accepts either no argument, in which case it will increase the attribute to the max_value for the unit, or an argument between 0 and 5000, which will set all attributes to that value.") - end -end - -if opt == nil then - ElevateMentalAttributes() -end diff --git a/scripts/elevate-physical.lua b/scripts/elevate-physical.lua deleted file mode 100644 index c210b4ffa..000000000 --- a/scripts/elevate-physical.lua +++ /dev/null @@ -1,51 +0,0 @@ --- Elevate all the physical attributes of a unit --- by vjek ---[[=begin - -elevate-physical -================ -As for elevate-mental, but for physical traits. High is good for soldiers, -while having an ineffective hammerer can be useful too... - -=end]] - -function ElevatePhysicalAttributes(value) - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - --print name of dwarf - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(unit))) - --walk through available attributes, adjust current to max - local ok,f,t,k = pcall(pairs,unit.body.physical_attrs) - if ok then - for k,v in f,t,k do - if value ~= nil then - print("Adjusting current value for "..tostring(k).." of "..v.value.." to the value of "..value) - v.value=value - else - print("Adjusting current value for "..tostring(k).." of "..v.value.." to max value of "..v.max_value) - v.value=v.max_value - --below will reset values back to "normal" - --v.value=v.max_value/2 - end - end - end -end ---script execution starts here -local opt = ... -opt = tonumber(opt) - -if opt ~= nil then - if opt >=0 and opt <=5000 then - ElevatePhysicalAttributes(opt) - end - if opt <0 or opt >5000 then - print("Invalid Range or argument. This script accepts either no argument, in which case it will increase the attribute to the max_value for the unit, or an argument between 0 and 5000, which will set all attributes to that value.") - end -end - -if opt == nil then - ElevatePhysicalAttributes() -end diff --git a/scripts/emigration.lua b/scripts/emigration.lua deleted file mode 100644 index aba62fa2b..000000000 --- a/scripts/emigration.lua +++ /dev/null @@ -1,132 +0,0 @@ ---Allow stressed dwarves to emigrate from the fortress --- For 34.11 by IndigoFenix; update and cleanup by PeridexisErrant --- old version: http://dffd.bay12games.com/file.php?id=8404 ---[[=begin - -emigration -========== -Allows dwarves to emigrate from the fortress when stressed, -in proportion to how badly stressed they are and adjusted -for who they would have to leave with - a dwarven merchant -being more attractive than leaving alone (or with an elf). -The check is made monthly. - -A happy dwarf (ie with negative stress) will never emigrate. - -Usage: ``emigration enable|disable`` - -=end]] - -local args = {...} -if args[1] == "enable" then - enabled = true -elseif args[1] == "disable" then - enabled = false -end - -function desireToStay(unit,method,civ_id) - -- on a percentage scale - local value = 100 - unit.status.current_soul.personality.stress_level / 5000 - if method == 'merchant' or method == 'diplomat' then - if civ_id ~= unit.civ_id then value = value*2 end end - if method == 'wild' then - value = value*5 end - return value -end - -function desert(u,method,civ) - u.relations.following = nil - local line = dfhack.TranslateName(dfhack.units.getVisibleName(u)) .. " has " - if method == 'merchant' then - line = line.."joined the merchants" - u.flags1.merchant = true - u.civ_id = civ - elseif method == 'diplomat' then - line = line.."followed the diplomat" - u.flags1.diplomat = true - u.civ_id = civ - else - line = line.."abandoned the settlement in search of a better life." - u.civ_id = -1 - u.flags1.forest = true - u.animal.leave_countdown = 2 - end - print(line) - dfhack.gui.showAnnouncement(line, COLOR_WHITE) -end - -function canLeave(unit) - for _, skill in pairs(unit.status.current_soul.skills) do - if skill.rating > 14 then return false end - end - if unit.flags1.caged - or unit.race ~= df.global.ui.race_id - or unit.civ_id ~= df.global.ui.civ_id - or dfhack.units.isDead(unit) - or dfhack.units.isOpposedToLife(unit) - or unit.flags1.merchant - or unit.flags1.diplomat - or unit.flags1.chained - or dfhack.units.getNoblePositions(unit) ~= nil - or unit.military.squad_id ~= -1 - or dfhack.units.isCitizen(unit) - or dfhack.units.isSane(unit) - or unit.profession ~= 103 - or not dfhack.units.isDead(unit) - then return false end - return true -end - -function checkForDeserters(method,civ_id) - local allUnits = df.global.world.units.active - for i=#allUnits-1,0,-1 do -- search list in reverse - local u = allUnits[i] - if canLeave(u) and math.random(100) < desireToStay(u,method,civ_id) then - desert(u,method,civ_id) - end - end -end - -function checkmigrationnow() - local merchant_civ_ids = {} - local diplomat_civ_ids = {} - local allUnits = df.global.world.units.active - for i=0, #allUnits-1 do - local unit = allUnits[i] - if dfhack.units.isSane(unit) - and not dfhack.units.isDead(unit) - and not dfhack.units.isOpposedToLife(unit) - and not unit.flags1.tame - then - if unit.flags1.merchant then table.insert(merchant_civ_ids, unit.civ_id) end - if unit.flags1.diplomat then table.insert(diplomat_civ_ids, unit.civ_id) end - end - end - - for _, civ_id in pairs(merchant_civ_ids) do checkForDeserters('merchant', civ_id) end - for _, civ_id in pairs(diplomat_civ_ids) do checkForDeserters('diplomat', civ_id) end - checkForDeserters('wild', -1) -end - -local function event_loop() - if enabled then - checkmigrationnow() - dfhack.timeout(1, 'months', event_loop) - end -end - -dfhack.onStateChange.loadEmigration = function(code) - if code==SC_MAP_LOADED then - if enabled then - print("Emigration enabled.") - event_loop() - else - print("Emigration disabled.") - end - end -end - -if dfhack.isMapLoaded() then - dfhack.onStateChange.loadEmigration(SC_MAP_LOADED) -end - diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua deleted file mode 100644 index 86542f930..000000000 --- a/scripts/exportlegends.lua +++ /dev/null @@ -1,836 +0,0 @@ --- Export everything from legends mode ---[[=begin - -exportlegends -============= -Controls legends mode to export data - especially useful to set-and-forget large -worlds, or when you want a map of every site when there are several hundred. - -The 'info' option exports more data than is possible in vanilla, to a -:file:`region-date-legends_plus.xml` file developed to extend -:forums:`World Viewer <128932>` and other legends utilities. - -Options: - -:info: Exports the world/gen info, the legends XML, and a custom XML with more information -:custom: Exports a custom XML with more information -:sites: Exports all available site maps -:maps: Exports all seventeen detailed maps -:all: Equivalent to calling all of the above, in that order - -=end]] - -gui = require 'gui' -local args = {...} -local vs = dfhack.gui.getCurViewscreen() -local i = 1 - -local MAPS = { - "Standard biome+site map", - "Elevations including lake and ocean floors", - "Elevations respecting water level", - "Biome", - "Hydrosphere", - "Temperature", - "Rainfall", - "Drainage", - "Savagery", - "Volcanism", - "Current vegetation", - "Evil", - "Salinity", - "Structures/fields/roads/etc.", - "Trade", - "Nobility and Holdings", - "Diplomacy", -} - -function getItemSubTypeName(itemType, subType) - if (dfhack.items.getSubtypeCount(itemType)) <= 0 then - return tostring(-1) - end - local subtypename = dfhack.items.getSubtypeDef(itemType, subType) - if (subtypename == nil) then - return tostring(-1) - else - return tostring(subtypename.name):lower() - end -end - -function findEntity(id) - for k,v in ipairs(df.global.world.entities.all) do - if (v.id == id) then - return v - end - end - return nil -end - -function table.contains(table, element) - for _, value in pairs(table) do - if value == element then - return true - end - end - return false -end - -function table.containskey(table, key) - for value, _ in pairs(table) do - if value == key then - return true - end - end - return false -end - --- wrapper that returns "unknown N" for df.enum_type[BAD_VALUE], --- instead of returning nil or causing an error -df_enums = {} -setmetatable(df_enums, { - __index = function(self, enum) - if not df[enum] or df[enum]._kind ~= 'enum-type' then - error('invalid enum: ' .. enum) - end - local t = {} - setmetatable(t, { - __index = function(self, k) - return df[enum][k] or 'unknown ' .. k - end - }) - return t - end, - __newindex = function() error('read-only') end -}) - ---create an extra legends xml with extra data, by Mason11987 for World Viewer -function export_more_legends_xml() - local month = dfhack.world.ReadCurrentMonth() + 1 --days and months are 1-indexed - local day = dfhack.world.ReadCurrentDay() - local year_str = string.format('%0'..math.max(5, string.len(''..df.global.cur_year))..'d', df.global.cur_year) - local date_str = year_str..string.format('-%02d-%02d', month, day) - - local filename = df.global.world.cur_savegame.save_dir.."-"..date_str.."-legends_plus.xml" - local file = io.open(filename, 'w') - if not file then qerror("could not open file: " .. filename) end - - file:write("\n") - file:write("\n") - file:write(""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."\n") - file:write(""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."\n") - - file:write("\n") - for landmassK, landmassV in ipairs(df.global.world.world_data.landmasses) do - file:write("\t\n") - file:write("\t\t"..landmassV.index.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(landmassV.name,1)).."\n") - file:write("\t\t"..landmassV.min_x..","..landmassV.min_y.."\n") - file:write("\t\t"..landmassV.max_x..","..landmassV.max_y.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for mountainK, mountainV in ipairs(df.global.world.world_data.mountain_peaks) do - file:write("\t\n") - file:write("\t\t"..mountainK.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(mountainV.name,1)).."\n") - file:write("\t\t"..mountainV.pos.x..","..mountainV.pos.y.."\n") - file:write("\t\t"..mountainV.height.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for regionK, regionV in ipairs(df.global.world.world_data.regions) do - file:write("\t\n") - file:write("\t\t"..regionV.index.."\n") - file:write("\t\t") - for xK, xVal in ipairs(regionV.region_coords.x) do - file:write(xVal..","..regionV.region_coords.y[xK].."|") - end - file:write("\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do - file:write("\t\n") - file:write("\t\t"..regionV.index.."\n") - file:write("\t\t") - for xK, xVal in ipairs(regionV.region_coords.x) do - file:write(xVal..","..regionV.region_coords.y[xK].."|") - end - file:write("\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for siteK, siteV in ipairs(df.global.world.world_data.sites) do - file:write("\t\n") - for k,v in pairs(siteV) do - if (k == "id" or k == "civ_id" or k == "cur_owner_id") then - file:write("\t\t<"..k..">"..tostring(v).."\n") - elseif (k == "buildings") then - if (#siteV.buildings > 0) then - file:write("\t\t\n") - for buildingK, buildingV in ipairs(siteV.buildings) do - file:write("\t\t\t\n") - file:write("\t\t\t\t"..buildingV.id.."\n") - file:write("\t\t\t\t"..df_enums.abstract_building_type[buildingV:getType()]:lower().."\n") - if (df_enums.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire" or table.containskey(buildingV,"name")) then - file:write("\t\t\t\t"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."\n") - file:write("\t\t\t\t"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."\n") - end - if (buildingV:getType() == df.abstract_building_type.TEMPLE) then - file:write("\t\t\t\t"..buildingV.deity.."\n") - file:write("\t\t\t\t"..buildingV.religion.."\n") - end - if (buildingV:getType() == df.abstract_building_type.DUNGEON) then - file:write("\t\t\t\t"..buildingV.dungeon_type.."\n") - end - for inhabitabntK,inhabitabntV in pairs(buildingV.inhabitants) do - file:write("\t\t\t\t"..inhabitabntV.anon_2.."\n") - end - file:write("\t\t\t\n") - end - file:write("\t\t\n") - end - end - end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do - file:write("\t\n") - file:write("\t\t"..wcV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."\n") - file:write("\t\t"..(df_enums.world_construction_type[wcV:getType()]):lower().."\n") - file:write("\t\t") - for xK, xVal in ipairs(wcV.square_pos.x) do - file:write(xVal..","..wcV.square_pos.y[xK].."|") - end - file:write("\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do - file:write("\t\n") - file:write("\t\t"..artifactV.id.."\n") - if (artifactV.item:getType() ~= -1) then - file:write("\t\t"..tostring(df_enums.item_type[artifactV.item:getType()]):lower().."\n") - if (artifactV.item:getSubtype() ~= -1) then - file:write("\t\t"..artifactV.item.subtype.name.."\n") - end - for improvementK,impovementV in pairs(artifactV.item.improvements) do - if impovementV:getType() == df.improvement_type.WRITING then - for writingk,writingV in pairs(impovementV["itemimprovement_writingst.anon_1"]) do - file:write("\t\t"..writingV.."\n") - end - elseif impovementV:getType() == df.improvement_type.PAGES then - file:write("\t\t"..impovementV.count.."\n") - for writingk,writingV in pairs(impovementV.contents) do - file:write("\t\t"..writingV.."\n") - end - end - end - end - if (table.containskey(artifactV.item,"description")) then - file:write("\t\t"..dfhack.df2utf(artifactV.item.description:lower()).."\n") - end - if artifactV.item:getMaterial() ~= -1 then - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."\n") - end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for hfK, hfV in ipairs(df.global.world.history.figures) do - file:write("\t\n") - file:write("\t\t"..hfV.id.."\n") - file:write("\t\t"..hfV.sex.."\n") - if hfV.race >= 0 then file:write("\t\t"..df.global.world.raws.creatures.all[hfV.race].name[0].."\n") end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do - file:write("\t\n") - file:write("\t\t"..entityPopV.id.."\n") - for raceK, raceV in ipairs(entityPopV.races) do - local raceName = (df.global.world.raws.creatures.all[raceV].creature_id):lower() - file:write("\t\t"..raceName..":"..entityPopV.counts[raceK].."\n") - end - file:write("\t\t"..entityPopV.civ_id.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for entityK, entityV in ipairs(df.global.world.entities.all) do - file:write("\t\n") - file:write("\t\t"..entityV.id.."\n") - if entityV.race >= 0 then - file:write("\t\t"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."\n") - end - file:write("\t\t"..(df_enums.historical_entity_type[entityV.type]):lower().."\n") - if entityV.type == df.historical_entity_type.Religion then -- Get worshipped figure - if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nil) then - for k,v in pairs(entityV.unknown1b.worship) do - file:write("\t\t"..v.."\n") - end - end - end - for id, link in pairs(entityV.entity_links) do - file:write("\t\t\n") - for k, v in pairs(link) do - if (k == "type") then - file:write("\t\t\t<"..k..">"..tostring(df_enums.entity_entity_link_type[v]).."\n") - else - file:write("\t\t\t<"..k..">"..v.."\n") - end - end - file:write("\t\t\n") - end - for positionK,positionV in pairs(entityV.positions.own) do - file:write("\t\t\n") - file:write("\t\t\t"..positionV.id.."\n") - if positionV.name[0] ~= "" then file:write("\t\t\t"..positionV.name[0].."\n") end - if positionV.name_male[0] ~= "" then file:write("\t\t\t"..positionV.name_male[0].."\n") end - if positionV.name_female[0] ~= "" then file:write("\t\t\t"..positionV.name_female[0].."\n") end - if positionV.spouse[0] ~= "" then file:write("\t\t\t"..positionV.spouse[0].."\n") end - if positionV.spouse_male[0] ~= "" then file:write("\t\t\t"..positionV.spouse_male[0].."\n") end - if positionV.spouse_female[0] ~= "" then file:write("\t\t\t"..positionV.spouse_female[0].."\n") end - file:write("\t\t\n") - end - for assignmentK,assignmentV in pairs(entityV.positions.assignments) do - file:write("\t\t\n") - for k, v in pairs(assignmentV) do - if (k == "id" or k == "histfig" or k == "position_id" or k == "squad_id") then - file:write("\t\t\t<"..k..">"..v.."\n") - end - end - file:write("\t\t\n") - end - for idx,id in pairs(entityV.histfig_ids) do - file:write("\t\t"..id.."\n") - end - for id, link in ipairs(entityV.children) do - file:write("\t\t"..link.."\n") - end - file:write("\t\t") - for xK, xVal in ipairs(entityV.claims.unk2.x) do - file:write(xVal..","..entityV.claims.unk2.y[xK].."|") - end - file:write("\t\t\n") - if (table.containskey(entityV,"occasion_info") and entityV.occasion_info ~= nil) then - for occasionK, occasionV in pairs(entityV.occasion_info.occasions) do - file:write("\t\t\n") - file:write("\t\t\t"..occasionV.id.."\n") - file:write("\t\t\t"..dfhack.df2utf(dfhack.TranslateName(occasionV.name,1)).."\n") - file:write("\t\t\t"..occasionV.event.."\n") - for scheduleK, scheduleV in pairs(occasionV.schedule) do - file:write("\t\t\t\n") - file:write("\t\t\t\t"..scheduleK.."\n") - file:write("\t\t\t\t"..df_enums.occasion_schedule_type[scheduleV.type]:lower().."\n") - if(scheduleV.type == df.occasion_schedule_type.THROWING_COMPETITION) then - file:write("\t\t\t\t"..df_enums.item_type[scheduleV.reference]:lower().."\n") - file:write("\t\t\t\t"..getItemSubTypeName(scheduleV.reference,scheduleV.reference2).."\n") - else - file:write("\t\t\t\t"..scheduleV.reference.."\n") - file:write("\t\t\t\t"..scheduleV.reference2.."\n") - end - for featureK, featureV in pairs(scheduleV.features) do - file:write("\t\t\t\t\n") - if(df_enums.occasion_schedule_feature[featureV.feature] ~= nil) then - file:write("\t\t\t\t\t"..df_enums.occasion_schedule_feature[featureV.feature]:lower().."\n") - else - file:write("\t\t\t\t\t"..featureV.feature.."\n") - end - file:write("\t\t\t\t\t"..featureV.reference.."\n") - file:write("\t\t\t\t\n") - end - file:write("\t\t\t\n") - end - file:write("\t\t\n") - end - end - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for formK, formV in ipairs(df.global.world.poetic_forms.all) do - file:write("\t\n") - file:write("\t\t"..formV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for formK, formV in ipairs(df.global.world.musical_forms.all) do - file:write("\t\n") - file:write("\t\t"..formV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for formK, formV in ipairs(df.global.world.dance_forms.all) do - file:write("\t\n") - file:write("\t\t"..formV.id.."\n") - file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for wcK, wcV in ipairs(df.global.world.written_contents.all) do - file:write("\t\n") - file:write("\t\t"..wcV.id.."\n") - file:write("\t\t"..wcV.title.."\n") - file:write("\t\t"..wcV.page_start.."\n") - file:write("\t\t"..wcV.page_end.."\n") - for refK, refV in pairs(wcV.refs) do - file:write("\t\t\n") - file:write("\t\t\t"..df_enums.general_ref_type[refV:getType()].."\n") - if refV:getType() == df.general_ref_type.ARTIFACT then file:write("\t\t\t"..refV.artifact_id.."\n") -- artifact - elseif refV:getType() == df.general_ref_type.ENTITY then file:write("\t\t\t"..refV.entity_id.."\n") -- entity - elseif refV:getType() == df.general_ref_type.HISTORICAL_EVENT then file:write("\t\t\t"..refV.event_id.."\n") -- event - elseif refV:getType() == df.general_ref_type.SITE then file:write("\t\t\t"..refV.site_id.."\n") -- site - elseif refV:getType() == df.general_ref_type.SUBREGION then file:write("\t\t\t"..refV.region_id.."\n") -- region - elseif refV:getType() == df.general_ref_type.HISTORICAL_FIGURE then file:write("\t\t\t"..refV.hist_figure_id.."\n") -- hist figure - elseif refV:getType() == df.general_ref_type.WRITTEN_CONTENT then file:write("\t\t\t"..refV.anon_1.."\n") - elseif refV:getType() == df.general_ref_type.POETIC_FORM then file:write("\t\t\t"..refV.poetic_form_id.."\n") -- poetic form - elseif refV:getType() == df.general_ref_type.MUSICAL_FORM then file:write("\t\t\t"..refV.musical_form_id.."\n") -- musical form - elseif refV:getType() == df.general_ref_type.DANCE_FORM then file:write("\t\t\t"..refV.dance_form_id.."\n") -- dance form - elseif refV:getType() == df.general_ref_type.INTERACTION then -- TODO INTERACTION - elseif refV:getType() == df.general_ref_type.KNOWLEDGE_SCHOLAR_FLAG then -- TODO KNOWLEDGE_SCHOLAR_FLAG - elseif refV:getType() == df.general_ref_type.VALUE_LEVEL then -- TODO VALUE_LEVEL - elseif refV:getType() == df.general_ref_type.LANGUAGE then -- TODO LANGUAGE - else - print("unknown reference",refV:getType(),df_enums.general_ref_type[refV:getType()]) - --for k,v in pairs(refV) do print(k,v) end - end - file:write("\t\t\n") - end - file:write("\t\t"..(df_enums.written_content_type[wcV.type] or wcV.type).."\n") - for styleK, styleV in pairs(wcV.styles) do - file:write("\t\t\n") - end - file:write("\t\t"..wcV.author.."\n") - file:write("\t\n") - end - file:write("\n") - - file:write("\n") - for ID, event in ipairs(df.global.world.history.events) do - if event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK - or event:getType() == df.history_event_type.ADD_HF_SITE_LINK - or event:getType() == df.history_event_type.ADD_HF_HF_LINK - or event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK - or event:getType() == df.history_event_type.TOPICAGREEMENT_CONCLUDED - or event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED - or event:getType() == df.history_event_type.TOPICAGREEMENT_MADE - or event:getType() == df.history_event_type.BODY_ABUSED - or event:getType() == df.history_event_type.CHANGE_CREATURE_TYPE - or event:getType() == df.history_event_type.CHANGE_HF_JOB - or event:getType() == df.history_event_type.CHANGE_HF_STATE - or event:getType() == df.history_event_type.CREATED_BUILDING - or event:getType() == df.history_event_type.CREATURE_DEVOURED - or event:getType() == df.history_event_type.HF_DOES_INTERACTION - or event:getType() == df.history_event_type.HF_LEARNS_SECRET - or event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET - or event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT - or event:getType() == df.history_event_type.ITEM_STOLEN - or event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK - or event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK - or event:getType() == df.history_event_type.REPLACED_BUILDING - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_DESIGN - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD - or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ENGRAVING - or event:getType() == df.history_event_type.MASTERPIECE_LOST - or event:getType() == df.history_event_type.ENTITY_ACTION - or event:getType() == df.history_event_type.HF_ACT_ON_BUILDING - or event:getType() == df.history_event_type.ARTIFACT_CREATED - or event:getType() == df.history_event_type.ASSUME_IDENTITY - or event:getType() == df.history_event_type.CREATE_ENTITY_POSITION - or event:getType() == df.history_event_type.DIPLOMAT_LOST - or event:getType() == df.history_event_type.MERCHANT - or event:getType() == df.history_event_type.WAR_PEACE_ACCEPTED - or event:getType() == df.history_event_type.WAR_PEACE_REJECTED - or event:getType() == df.history_event_type.HIST_FIGURE_WOUNDED - or event:getType() == df.history_event_type.HIST_FIGURE_DIED - then - file:write("\t\n") - file:write("\t\t"..event.id.."\n") - file:write("\t\t"..tostring(df_enums.history_event_type[event:getType()]):lower().."\n") - for k,v in pairs(event) do - if k == "year" or k == "seconds" or k == "flags" or k == "id" - or (k == "region" and event:getType() ~= df.history_event_type.HF_DOES_INTERACTION) - or k == "region_pos" or k == "layer" or k == "feature_layer" or k == "subregion" - or k == "anon_1" or k == "anon_2" or k == "flags2" or k == "unk1" then - - elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "link_type" then - file:write("\t\t<"..k..">"..df_enums.histfig_entity_link_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "position_id" then - local entity = findEntity(event.civ) - if (entity ~= nil and event.civ > -1 and v > -1) then - for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do - if entityPositionsV.id == v then - file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") - break - end - end - else - file:write("\t\t-1\n") - end - elseif event:getType() == df.history_event_type.CREATE_ENTITY_POSITION and k == "position" then - local entity = findEntity(event.site_civ) - if (entity ~= nil and v > -1) then - for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do - if entityPositionsV.id == v then - file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") - break - end - end - else - file:write("\t\t-1\n") - end - elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "link_type" then - file:write("\t\t<"..k..">"..df_enums.histfig_entity_link_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "position_id" then - local entity = findEntity(event.civ) - if (entity ~= nil and event.civ > -1 and v > -1) then - for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do - if entityPositionsV.id == v then - file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") - break - end - end - else - file:write("\t\t-1\n") - end - elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then - file:write("\t\t"..df_enums.histfig_hf_link_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then - file:write("\t\t"..df_enums.histfig_site_link_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then - file:write("\t\t"..df_enums.histfig_site_link_type[v]:lower().."\n") - elseif (event:getType() == df.history_event_type.ITEM_STOLEN or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - ) and k == "item_type" then - file:write("\t\t"..df_enums.item_type[v]:lower().."\n") - elseif (event:getType() == df.history_event_type.ITEM_STOLEN or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - ) and k == "item_subtype" then - --if event.item_type > -1 and v > -1 then - file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,v).."\n") - --end - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD and k == "item_subtype" then - --if event.item_type > -1 and v > -1 then - file:write("\t\tfood\n") - file:write("\t\t<"..k..">"..getItemSubTypeName(df.item_type.FOOD,v).."\n") - --end - elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "mattype" then - if (v > -1) then - if (dfhack.matinfo.decode(event.mattype, event.matindex) == nil) then - file:write("\t\t"..event.mattype.."\n") - file:write("\t\t"..event.matindex.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."\n") - end - end - elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM - ) and k == "mat_type" then - if (v > -1) then - if (dfhack.matinfo.decode(event.mat_type, event.mat_index) == nil) then - file:write("\t\t"..event.mat_type.."\n") - file:write("\t\t"..event.mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."\n") - end - end - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_type" then - if (v > -1) then - if (dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index) == nil) then - file:write("\t\t"..event.imp_mat_type.."\n") - file:write("\t\t"..event.imp_mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."\n") - end - end - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM and k == "dye_mat_type" then - if (v > -1) then - if (dfhack.matinfo.decode(event.dye_mat_type, event.dye_mat_index) == nil) then - file:write("\t\t"..event.dye_mat_type.."\n") - file:write("\t\t"..event.dye_mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.dye_mat_type, event.dye_mat_index)).."\n") - end - end - - elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "matindex" then - --skip - elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "item" and v == -1 then - --skip - elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or - event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT - ) and k == "mat_index" then - --skip - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_index" then - --skip - elseif (event:getType() == df.history_event_type.WAR_PEACE_ACCEPTED or - event:getType() == df.history_event_type.WAR_PEACE_REJECTED or - event:getType() == df.history_event_type.TOPICAGREEMENT_CONCLUDED or - event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED or - event:getType() == df.history_event_type.TOPICAGREEMENT_MADE - ) and k == "topic" then - file:write("\t\t"..tostring(df_enums.meeting_topic[v]):lower().."\n") - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then - file:write("\t\t"..df_enums.improvement_type[v]:lower().."\n") - elseif ((event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT and k == "group") - or (event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "group") - or (event:getType() == df.history_event_type.BODY_ABUSED and k == "bodies")) then - for detailK,detailV in pairs(v) do - file:write("\t\t<"..k..">"..detailV.."\n") - end - elseif event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "pets" then - for detailK,detailV in pairs(v) do - file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[detailV].name[0].."\n") - end - elseif event:getType() == df.history_event_type.BODY_ABUSED and (k == "props") then - file:write("\t\t"..tostring(df_enums.item_type[event.props.item.item_type]):lower().."\n") - file:write("\t\t"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."\n") - if (event.props.item.mat_type > -1) then - if (dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index) == nil) then - file:write("\t\t"..event.props.item.mat_type.."\n") - file:write("\t\t"..event.props.item.mat_index.."\n") - else - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."\n") - end - end - --file:write("\t\t<"..k.."_item_mat_type>"..tostring(event.props.item.mat_type).."\n") - --file:write("\t\t<"..k.."_item_mat_index>"..tostring(event.props.item.mat_index).."\n") - file:write("\t\t<"..k.."_pile_type>"..tostring(event.props.pile_type).."\n") - elseif event:getType() == df.history_event_type.ASSUME_IDENTITY and k == "identity" then - if (table.contains(df.global.world.identities.all,v)) then - if (df.global.world.identities.all[v].histfig_id == -1) then - local thisIdentity = df.global.world.identities.all[v] - file:write("\t\t"..thisIdentity.name.first_name.."\n") - file:write("\t\t"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."\n") - file:write("\t\t"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."\n") - else - file:write("\t\t"..df.global.world.identities.all[v].histfig_id.."\n") - end - end - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_type" then - file:write("\t\t"..df_enums.building_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_subtype" then - if (df_enums.building_type[event.building_type]:lower() == "furnace") then - file:write("\t\t"..df_enums.furnace_type[v]:lower().."\n") - elseif v > -1 then - file:write("\t\t"..tostring(v).."\n") - end - elseif k == "race" then - if v > -1 then - file:write("\t\t"..df.global.world.raws.creatures.all[v].name[0].."\n") - end - elseif k == "caste" then - if v > -1 then - file:write("\t\t"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."\n") - end - elseif k == "interaction" and event:getType() == df.history_event_type.HF_DOES_INTERACTION then - file:write("\t\t"..df.global.world.raws.interactions[v].str[3].value.."\n") - file:write("\t\t"..df.global.world.raws.interactions[v].str[4].value.."\n") - elseif k == "interaction" and event:getType() == df.history_event_type.HF_LEARNS_SECRET then - file:write("\t\t"..df.global.world.raws.interactions[v].str[2].value.."\n") - elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "weapon" then - for detailK,detailV in pairs(v) do - if (detailK == "item") then - if detailV > -1 then - file:write("\t\t<"..detailK..">"..detailV.."\n") - local thisItem = df.item.find(detailV) - if (thisItem ~= nil) then - if (thisItem.flags.artifact == true) then - for refk,refv in pairs(thisItem.general_refs) do - if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then - file:write("\t\t"..refv.artifact_id.."\n") - break - end - end - end - end - - end - elseif (detailK == "item_type") then - if event.weapon.item > -1 then - file:write("\t\t<"..detailK..">"..tostring(df_enums.item_type[detailV]):lower().."\n") - end - elseif (detailK == "item_subtype") then - if event.weapon.item > -1 and detailV > -1 then - file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."\n") - end - elseif (detailK == "mattype") then - if (detailV > -1) then - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."\n") - end - elseif (detailK == "matindex") then - - elseif (detailK == "shooter_item") then - if detailV > -1 then - file:write("\t\t<"..detailK..">"..detailV.."\n") - local thisItem = df.item.find(detailV) - if thisItem ~= nil then - if (thisItem.flags.artifact == true) then - for refk,refv in pairs(thisItem.general_refs) do - if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then - file:write("\t\t"..refv.artifact_id.."\n") - break - end - end - end - end - end - elseif (detailK == "shooter_item_type") then - if event.weapon.shooter_item > -1 then - file:write("\t\t<"..detailK..">"..tostring(df_enums.item_type[detailV]):lower().."\n") - end - elseif (detailK == "shooter_item_subtype") then - if event.weapon.shooter_item > -1 and detailV > -1 then - file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."\n") - end - elseif (detailK == "shooter_mattype") then - if (detailV > -1) then - file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."\n") - end - elseif (detailK == "shooter_matindex") then - --skip - elseif detailK == "slayer_race" or detailK == "slayer_caste" then - --skip - else - file:write("\t\t<"..detailK..">"..detailV.."\n") - end - end - elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "death_cause" then - file:write("\t\t<"..k..">"..df_enums.death_type[v]:lower().."\n") - elseif event:getType() == df.history_event_type.CHANGE_HF_JOB and (k == "new_job" or k == "old_job") then - file:write("\t\t<"..k..">"..df_enums.profession[v]:lower().."\n") - elseif event:getType() == df.history_event_type.CHANGE_CREATURE_TYPE and (k == "old_race" or k == "new_race") and v >= 0 then - file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[v].name[0].."\n") - else - file:write("\t\t<"..k..">"..tostring(v).."\n") - end - end - file:write("\t\n") - end - end - file:write("\n") - file:write("\n") - file:write("\n") - file:write("\n") - file:write("\n") - file:write("\n") - file:close() -end - --- export information and XML ('p, x') -function export_legends_info() - print(' Exporting: World map/gen info') - gui.simulateInput(vs, 'LEGENDS_EXPORT_MAP') - print(' Exporting: Legends xml') - gui.simulateInput(vs, 'LEGENDS_EXPORT_XML') - print(" Exporting: Extra legends_plus xml") - export_more_legends_xml() -end - ---- presses 'd' for detailed maps -function wait_for_legends_vs() - local vs = dfhack.gui.getCurViewscreen() - if i <= #MAPS then - if df.viewscreen_legendsst:is_instance(vs.parent) then - vs = vs.parent - end - if df.viewscreen_legendsst:is_instance(vs) then - gui.simulateInput(vs, 'LEGENDS_EXPORT_DETAILED_MAP') - dfhack.timeout(10,'frames',wait_for_export_maps_vs) - else - dfhack.timeout(10,'frames',wait_for_legends_vs) - end - end -end - --- selects detailed map and export it -function wait_for_export_maps_vs() - local vs = dfhack.gui.getCurViewscreen() - if dfhack.gui.getCurFocus() == "export_graphical_map" then - vs.sel_idx = i-1 - print(' Exporting: '..MAPS[i]..' map') - gui.simulateInput(vs, 'SELECT') - i = i + 1 - dfhack.timeout(10,'frames',wait_for_legends_vs) - else - dfhack.timeout(10,'frames',wait_for_export_maps_vs) - end -end - --- export site maps -function export_site_maps() - local vs = dfhack.gui.getCurViewscreen() - if ((dfhack.gui.getCurFocus() ~= "legends" ) and (not table.contains(vs, "main_cursor"))) then -- Using open-legends - vs = vs.parent - end - print(' Exporting: All possible site maps') - vs.main_cursor = 1 - gui.simulateInput(vs, 'SELECT') - for i=1, #vs.sites do - gui.simulateInput(vs, 'LEGENDS_EXPORT_MAP') - gui.simulateInput(vs, 'STANDARDSCROLL_DOWN') - end - gui.simulateInput(vs, 'LEAVESCREEN') -end - --- main() -if dfhack.gui.getCurFocus() == "legends" or dfhack.gui.getCurFocus() == "dfhack/lua/legends" then - -- either native legends mode, or using the open-legends.lua script - if args[1] == "all" then - export_legends_info() - export_site_maps() - wait_for_legends_vs() - elseif args[1] == "info" then - export_legends_info() - elseif args[1] == "custom" then - export_more_legends_xml() - elseif args[1] == "maps" then - wait_for_legends_vs() - elseif args[1] == "sites" then - export_site_maps() - else dfhack.printerr('Valid arguments are "all", "info", "maps" or "sites"') - end -elseif args[1] == "maps" and - dfhack.gui.getCurFocus() == "export_graphical_map" then - wait_for_export_maps_vs() -else - dfhack.printerr('Exportlegends must be run from the main legends view') -end diff --git a/scripts/exterminate.rb b/scripts/exterminate.rb deleted file mode 100644 index bc25a95b8..000000000 --- a/scripts/exterminate.rb +++ /dev/null @@ -1,192 +0,0 @@ -# exterminate creatures -=begin - -exterminate -=========== -Kills any unit of a given race. - -With no argument, lists the available races and count eligible targets. - -With the special argument ``him``, targets only the selected creature. - -With the special argument ``undead``, targets all undeads on the map, -regardless of their race. - -When specifying a race, a caste can be specified to further restrict the -targeting. To do that, append and colon and the caste name after the race. - -Any non-dead non-caged unit of the specified race gets its ``blood_count`` -set to 0, which means immediate death at the next game tick. For creatures -such as vampires, it also sets animal.vanish_countdown to 2. - -An alternate mode is selected by adding a 2nd argument to the command, -``magma``. In this case, a column of 7/7 magma is generated on top of the -targets until they die (Warning: do not call on magma-safe creatures. Also, -using this mode on birds is not recommended.) The final alternate mode -is ``butcher``, which marks them for butchering but does not kill. - -Will target any unit on a revealed tile of the map, including ambushers, -but ignore caged/chained creatures. - -Ex:: - - exterminate gob - exterminate gob:male - -To kill a single creature, select the unit with the 'v' cursor and:: - - exterminate him - -To purify all elves on the map with fire (may have side-effects):: - - exterminate elve magma - -=end - -race = $script_args[0] - -# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death -# if it is 'butcher' mark all units for butchering (wont work with hostiles) -kill_by = $script_args[1] - -case kill_by -when 'magma' - slain = 'burning' -when 'slaughter', 'butcher' - slain = 'marked for butcher' -when nil - slain = 'slain' -else - race = 'help' -end - -checkunit = lambda { |u| - (u.body.blood_count != 0 or u.body.blood_max == 0) and - not u.flags1.dead and - not u.flags1.caged and not u.flags1.chained and - #not u.flags1.hidden_in_ambush and - not df.map_designation_at(u).hidden -} - -slayit = lambda { |u| - case kill_by - when 'magma' - # it's getting hot around here - # !!WARNING!! do not call on a magma-safe creature - ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) { - if u.flags1.dead - df.onupdate_unregister(ouh) - else - x, y, z = u.pos.x, u.pos.y, u.pos.z - z += 1 while tile = df.map_tile_at(x, y, z+1) and - tile.shape_passableflow and tile.shape_passablelow - df.map_tile_at(x, y, z).spawn_magma(7) - end - } - when 'butcher', 'slaughter' - # mark for slaughter at butcher's shop - u.flags2.slaughter = true - else - # just make them drop dead - u.body.blood_count = 0 - # some races dont mind having no blood, ensure they are still taken care of. - u.animal.vanish_countdown = 2 - end -} - -all_races = Hash.new(0) - -df.world.units.active.map { |u| - if checkunit[u] - if (u.enemy.undead or - (u.curse.add_tags1.OPPOSED_TO_LIFE and not - u.curse.rem_tags1.OPPOSED_TO_LIFE)) - all_races['Undead'] += 1 - else - all_races[u.race_tg.creature_id] += 1 - end - end -} - -case race -when nil - all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } - -when 'help', '?' - puts <= #map_features then - qerror('Invalid feature ID') - end - map_features[tonumber(idx)].flags.Discovered = discovered -end - -function list_features() - local name = df.new('string') - for idx, feat in ipairs(map_features) do - local tags = '' - for _, t in pairs({'water', 'magma', 'subterranean', 'chasm', 'layer'}) do - if feat['is' .. t:sub(1, 1):upper() .. t:sub(2)](feat) then - tags = tags .. (' [%s]'):format(t) - end - end - feat:getName(name) - print(('Feature #%i is %s: "%s", type %s%s'):format( - idx, - feat.flags.Discovered and 'shown' or 'hidden', - name.value, - df.feature_type[feat:getType()], - tags - )) - end - df.delete(name) -end - -function enable_magma_funaces() - for idx, feat in ipairs(map_features) do - if tostring(feat):find('magma') ~= nil then - toggle_feature(idx, true) - print('Enabled magma furnaces.') - return - end - end - dfhack.printerr('Could not find a magma-bearing feature.') -end - -local args = {...} -if args[1] == 'list' then - list_features() -elseif args[1] == 'magma' then - enable_magma_funaces() -elseif args[1] == 'show' then - toggle_feature(args[2], true) -elseif args[1] == 'hide' then - toggle_feature(args[2], false) -else - print((help:gsub('=[a-z]+', ''))) -end diff --git a/scripts/fix-ster.lua b/scripts/fix-ster.lua deleted file mode 100644 index 66b4f6765..000000000 --- a/scripts/fix-ster.lua +++ /dev/null @@ -1,122 +0,0 @@ --- makes creatures [in]fertile, by modifying orientation --- usage: fix-ster [fert|ster] [all|animals|only:] --- original author: Tacomagic --- minor fixes by PeridexisErrant, Lethosor ---@ module = true ---[[=begin - -fix-ster -======== -Utilizes the orientation tag to either fix infertile creatures or inflict -infertility on creatures that you do not want to breed. Usage:: - - fix-ster [fert|ster] [all|animals|only:] - -``fert`` or ``ster`` is a required argument; whether to make the target fertile -or sterile. Optional arguments specify the target: no argument for the -selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf -creatures, or ``only:`` to only process matching creatures. - -=end]] - -function changeorient(unit, ori) - --Sets the fertility flag based on gender. - if not unit.status.current_soul then - return - end - if unit.sex == 0 then - unit.status.current_soul.orientation_flags.marry_male=ori - else - unit.status.current_soul.orientation_flags.marry_female=ori - end -end - -function changelots(creatures, ori, silent) - local v, unit - local c = 0 - --loops through indexes in creatures table and changes orientation flags - for _, v in ipairs(creatures) do - unit = df.global.world.units.active[v] - changeorient(unit,ori) - c = c + 1 - end - if not silent then - print("Changed " .. c .. " creatures.") - end -end - -function process_args(args) - local n, v, ori, crename, crenum - local creatures = {} - --Checks for any arguments at all. - if args == nil or #args == 0 then - print("No arguments. Usage is: fixster [all|animals|only:]") - return - end - for i, a in pairs(args) do - args[i] = tostring(a):lower() - end - if args[1]:sub(1, 1) == "s" then -- sterile - ori = false - elseif args[1]:sub(1, 1) == "f" then -- fertile - ori = true - else - qerror("Unrecognised first argument: " .. args[1] .. ". Aborting.") - end - - --Checks for the existence of the second argument. If it's missing, uses selected unit (if any) - if args[2] == nil then - unit = dfhack.gui.getSelectedUnit() - if not unit then return end - changeorient(unit, ori) - print('Changed selected creature.') - - --ALL arg processing - elseif args[2] == "all" then - --Create table of all current unit indexes - for n,v in ipairs(df.global.world.units.active) do - table.insert(creatures,n) - end - changelots(creatures,ori) - - --ANIMALS arg processing - elseif args[2] == "animals" then - --Create a table of all creature indexes except dwarves on the current map - for n,v in ipairs(df.global.world.units.active) do - if v.race ~= df.global.ui.race_id then - table.insert(creatures,n) - end - end - changelots(creatures,ori) - - -- ONLY: arg processing - elseif args[2]:sub(1,4) == "only" then - crename = args[2]:sub(6):upper() - - --Search raws for creature - for k,v in pairs(df.global.world.raws.creatures.all) do - if v.creature_id == crename then - crenum = k - end - end - --If no match, abort - if crenum == nil then - qerror("Creature not found. Check spelling.") - end - - --create a table of all the matching creature indexes on the map for processing - for n,v in ipairs(df.global.world.units.active) do - if v.race == crenum then - table.insert(creatures,n) - end - end - changelots(creatures,ori) - else - qerror("Unrecognised optional argument. Aborting") - end -end - -if not moduleMode then - local args = table.pack(...) - process_args(args) -end diff --git a/scripts/fix/about.txt b/scripts/fix/about.txt deleted file mode 100644 index 6a2d35c7e..000000000 --- a/scripts/fix/about.txt +++ /dev/null @@ -1 +0,0 @@ -``fix/*`` scripts fix various bugs and issues, some of them obscure. diff --git a/scripts/fix/blood-del.lua b/scripts/fix/blood-del.lua deleted file mode 100644 index ba32d4ab9..000000000 --- a/scripts/fix/blood-del.lua +++ /dev/null @@ -1,62 +0,0 @@ --- Stop traders bringing blood, ichor, or goo ---author Urist Da Vinci; edited by expwnent, scamtank ---[[=begin - -fix/blood-del -============= -Makes it so that future caravans won't bring barrels full of blood, ichor, or goo. - -=end]] -local my_entity=df.historical_entity.find(df.global.ui.civ_id) -local sText=" " -local k=0 -local v=1 - -for x,y in pairs(df.global.world.entities.all) do - my_entity=y - k=0 - while k < #my_entity.resources.misc_mat.extracts.mat_index do - v=my_entity.resources.misc_mat.extracts.mat_type[k] - sText=dfhack.matinfo.decode(v,my_entity.resources.misc_mat.extracts.mat_index[k]) - if (sText==nil) then - --LIQUID barrels - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - else - if(sText.material.id=="BLOOD") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="ICHOR") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="GOO") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="SWEAT") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - if(sText.material.id=="TEARS") then - my_entity.resources.misc_mat.extracts.mat_type:erase(k) - my_entity.resources.misc_mat.extracts.mat_index:erase(k) - k=k-1 - end - --VENOM - --POISON - --FLUID - --MILK - --EXTRACT - - end - k=k+1 - end -end - diff --git a/scripts/fix/build-location.lua b/scripts/fix/build-location.lua deleted file mode 100644 index 9df518ec3..000000000 --- a/scripts/fix/build-location.lua +++ /dev/null @@ -1,45 +0,0 @@ --- Lets constructions reconsider the build location. - --- Partial work-around for http://www.bay12games.com/dwarves/mantisbt/view.php?id=5991 ---[[=begin - -fix/build-location -================== -Fixes construction jobs that are stuck trying to build a wall while standing -on the same exact tile (:bug:`5991`), designates the tile restricted traffic to -hopefully avoid jamming it again, and unsuspends them. - -=end]] -local utils = require('utils') - -local count = 0 - -for link,job in utils.listpairs(df.global.world.job_list) do - local job = link.item - local place = dfhack.job.getHolder(job) - - if job.job_type == df.job_type.ConstructBuilding - and place and place:isImpassableAtCreation() - and job.item_category[0] - then - local cpos = utils.getBuildingCenter(place) - - if same_xyz(cpos, job.pos) then - -- Reset the flag - job.item_category[0] = false - job.flags.suspend = false - - -- Mark the tile restricted traffic - local dsgn,occ = dfhack.maps.getTileFlags(cpos) - dsgn.traffic = df.tile_traffic.Restricted - - count = count + 1 - end - end -end - -print('Found and unstuck '..count..' construct building jobs.') - -if count > 0 then - df.global.process_jobs = true -end diff --git a/scripts/fix/dead-units.lua b/scripts/fix/dead-units.lua deleted file mode 100644 index 7687a75cc..000000000 --- a/scripts/fix/dead-units.lua +++ /dev/null @@ -1,37 +0,0 @@ --- Remove uninteresting dead units from the unit list. ---[[=begin - -fix/dead-units -============== -Removes uninteresting dead units from the unit list. Doesn't seem to give any -noticeable performance gain, but migrants normally stop if the unit list grows -to around 3000 units, and this script reduces it back. - -=end]] -local units = df.global.world.units.active -local dwarf_race = df.global.ui.race_id -local dwarf_civ = df.global.ui.civ_id -local count = 0 - -for i=#units-1,0,-1 do - local unit = units[i] - local flags1 = unit.flags1 - local flags2 = unit.flags2 - if flags1.dead and unit.race ~= dwarf_race then - local remove = false - if flags2.slaughter then - remove = true - elseif not unit.name.has_name then - remove = true - elseif unit.civ_id ~= dwarf_civ and - not (flags1.merchant or flags1.diplomat) then - remove = true - end - if remove then - count = count + 1 - units:erase(i) - end - end -end - -print('Units removed from active: '..count) diff --git a/scripts/fix/diplomats.lua b/scripts/fix/diplomats.lua deleted file mode 100644 index 4e8c6daf7..000000000 --- a/scripts/fix/diplomats.lua +++ /dev/null @@ -1,106 +0,0 @@ --- Add Elven diplomats to negotiate tree caps ---[[=begin - -fix/diplomats -============= -Adds a Diplomat position to all Elven civilizations, allowing them to negotiate -tree cutting quotas - and you to violate them and start wars. -This was vanilla behaviour until ``0.31.12``, in which the "bug" was "fixed". - -=end]] - - -function update_pos(ent) - local pos = df.entity_position:new() - ent.positions.own:insert('#', pos) - - pos.code = "DIPLOMAT" - pos.id = ent.positions.next_position_id + 1 - ent.positions.next_position_id = ent.positions.next_position_id + 1 - - pos.flags.DO_NOT_CULL = true - pos.flags.MENIAL_WORK_EXEMPTION = true - pos.flags.SLEEP_PRETENSION = true - pos.flags.PUNISHMENT_EXEMPTION = true - pos.flags.ACCOUNT_EXEMPT = true - pos.flags.DUTY_BOUND = true - pos.flags.COLOR = true - pos.flags.HAS_RESPONSIBILITIES = true - pos.flags.IS_DIPLOMAT = true - pos.flags.IS_LEADER = true - -- not sure what these flags do, but the game sets them for a valid diplomat - pos.flags.unk_12 = true - pos.flags.unk_1a = true - pos.flags.unk_1b = true - pos.name[0] = "Diplomat" - pos.name[1] = "Diplomats" - pos.precedence = 70 - pos.color[0] = 7 - pos.color[1] = 0 - pos.color[2] = 1 - - return pos -end - - - -local checked = 0 -local fixed = 0 - -for _,ent in pairs(df.global.world.entities.all) do - if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.TREE_CAP_DIPLOMACY then - checked = checked + 1 - - update = true - local found_position - -- see if we need to add a new position or modify an existing one - for _,pos in pairs(ent.positions.own) do - if pos.responsibilities.MAKE_INTRODUCTIONS and - pos.responsibilities.MAKE_PEACE_AGREEMENTS and - pos.responsibilities.MAKE_TOPIC_AGREEMENTS then - -- a diplomat position exists with the proper responsibilities - skip to the end - update = false - found_position=pos - break - end - -- Diplomat position already exists, but has the wrong options - modify it instead of creating a new one - if pos.code == "DIPLOMAT" then - found_position=pos - break - end - end - if update then - -- either there's no diplomat, or there is one and it's got the wrong responsibilities - if not found_position then - found_position = add_guild_rep( ent ) - end - -- assign responsibilities - found_position.responsibilities.MAKE_INTRODUCTIONS = true - found_position.responsibilities.MAKE_PEACE_AGREEMENTS = true - found_position.responsibilities.MAKE_TOPIC_AGREEMENTS = true - end - -- make sure the diplomat position, whether we created it or not, is set up for proper assignment - local assign = true - for _,p in pairs(ent.positions.assignments) do - if p.position_id == found_position.id then -- it is - nothing more to do here - assign = false - break - end - end - if assign then -- it isn't - set it up - local asn = df.entity_position_assignment:new() - ent.positions.assignments:insert('#', asn); - - asn.id = ent.positions.next_assignment_id - ent.positions.next_assignment_id = asn.id + 1 - asn.position_id = found_position.id - asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags - asn.flags[0] = true -- and set the first one - end - if update or assign then - fixed = fixed + 1 - end - end -end - -print("Enabled tree cap diplomacy for "..fixed.." of "..checked.." civilizations.") diff --git a/scripts/fix/dry-buckets.lua b/scripts/fix/dry-buckets.lua deleted file mode 100644 index 91cff2cd1..000000000 --- a/scripts/fix/dry-buckets.lua +++ /dev/null @@ -1,28 +0,0 @@ --- Removes water from buckets (for lye-making). ---[[=begin - -fix/dry-buckets -=============== -Removes water from all buckets in your fortress, allowing them -to be used for making lye. Skips buckets in buildings (eg a well), -being carried, or currently used by a job. - -=end]] - -local emptied = 0 -local water_type = dfhack.matinfo.find('WATER').type - -for _,item in ipairs(df.global.world.items.all) do - container = dfhack.items.getContainer(item) - if container ~= nil - and container:getType() == df.item_type.BUCKET - and not (container.flags.in_job or container.flags.in_building) - and item:getMaterial() == water_type - and item:getType() == df.item_type.LIQUID_MISC - and not (item.flags.in_job or item.flags.in_building) then - dfhack.items.remove(item) - emptied = emptied + 1 - end -end - -print('Emptied '..emptied..' buckets.') diff --git a/scripts/fix/fat-dwarves.lua b/scripts/fix/fat-dwarves.lua deleted file mode 100644 index 09b5eb28e..000000000 --- a/scripts/fix/fat-dwarves.lua +++ /dev/null @@ -1,33 +0,0 @@ --- Makes fat dwarves non-fat. --- --- See for more info: --- http://www.bay12games.com/dwarves/mantisbt/view.php?id=5971 ---[[=begin - -fix/fat-dwarves -=============== -Avoids 5-10% FPS loss due to constant recalculation of insulation for dwarves at -maximum fatness, by reducing the cap from 1,000,000 to 999,999. -Recalculation is triggered in steps of 250 units, and very fat dwarves -constantly bounce off the maximum value while eating. - -=end]] -local num_fat = 0 -local num_lagging = 0 - -for _,v in ipairs(df.global.world.units.all) do - local fat = v.counters2.stored_fat - if fat > 850000 then - v.counters2.stored_fat = 500000 - if v.race == df.global.ui.race_id then - print(fat,dfhack.TranslateName(dfhack.units.getVisibleName(v))) - num_fat = num_fat + 1 - if fat > 999990 then - num_lagging = num_lagging + 1 - end - end - end -end - -print("Fat dwarves cured: "..num_fat) -print("Lag sources: "..num_lagging) diff --git a/scripts/fix/feeding-timers.lua b/scripts/fix/feeding-timers.lua deleted file mode 100644 index 84fab7086..000000000 --- a/scripts/fix/feeding-timers.lua +++ /dev/null @@ -1,43 +0,0 @@ --- feeding-timers.lua --- original author: tejón --- rewritten by expwnent --- see repeat.lua for how to run this every so often automatically ---[[=begin - -fix/feeding-timers -================== -Reset the GiveWater and GiveFood timers of all units as appropriate. - -=end]] -local args = {...} -if args[1] ~= nil then - print("fix/feeding-timers usage") - print(" fix/feeding-timers") - print(" reset the feeding timers of all units as appropriate") - print(" fix/feeding-timers help") - print(" print this help message") - print(" repeat -time [n] [years/months/ticks/days/etc] -command fix/feeding-timers") - print(" run this script every n time units") - print(" repeat -cancel fix/feeding-timers") - print(" stop automatically running this script") - return -end - -local fixcount = 0 -for _,unit in ipairs(df.global.world.units.all) do - if dfhack.units.isCitizen(unit) and not (unit.flags1.dead) then - for _,v in pairs(unit.status.misc_traits) do - local didfix = 0 - if v.id == 0 then -- I think this should have additional conditions... - v.value = 0 -- GiveWater cooldown set to zero - didfix = 1 - end - if v.id == 1 then -- I think this should have additional conditions... - v.value = 0 -- GiveFood cooldown set to zero - didfix = 1 - end - fixcount = fixcount + didfix - end - end -end -print("Fixed feeding timers for " .. fixcount .. " citizens.") diff --git a/scripts/fix/item-occupancy.lua b/scripts/fix/item-occupancy.lua deleted file mode 100644 index bcf1b8903..000000000 --- a/scripts/fix/item-occupancy.lua +++ /dev/null @@ -1,131 +0,0 @@ --- Verify item occupancy and block item list integrity. - ---[[=begin - -fix/item-occupancy -================== -Diagnoses and fixes issues with nonexistant 'items occupying site', usually -caused by `autodump` bugs or other hacking mishaps. Checks that: - -#. Item has ``flags.on_ground`` <=> it is in the correct block item list -#. A tile has items in block item list <=> it has ``occupancy.item`` -#. The block item lists are sorted - -=end]] -local utils = require 'utils' - -function check_block_items(fix) - local cnt = 0 - local icnt = 0 - local found = {} - local found_somewhere = {} - - local should_fix = false - local can_fix = true - - for _,block in ipairs(df.global.world.map.map_blocks) do - local itable = {} - local bx,by,bz = pos2xyz(block.map_pos) - - -- Scan the block item vector - local last_id = nil - local resort = false - - for _,id in ipairs(block.items) do - local item = df.item.find(id) - local ix,iy,iz = pos2xyz(item.pos) - local dx,dy,dz = ix-bx,iy-by,iz-bz - - -- Check sorted order - if last_id and last_id >= id then - print(bx,by,bz,last_id,id,'block items not sorted') - resort = true - else - last_id = id - end - - -- Check valid coordinates and flags - if not item.flags.on_ground then - print(bx,by,bz,id,dx,dy,'in block & not on ground') - elseif dx < 0 or dx >= 16 or dy < 0 or dy >= 16 or dz ~= 0 then - found_somewhere[id] = true - print(bx,by,bz,id,dx,dy,dz,'invalid pos') - can_fix = false - else - found[id] = true - itable[dx + dy*16] = true; - - -- Check missing occupancy - if not block.occupancy[dx][dy].item then - print(bx,by,bz,dx,dy,'item & not occupied') - if fix then - block.occupancy[dx][dy].item = true - else - should_fix = true - end - end - end - end - - -- Sort the vector if needed - if resort then - if fix then - utils.sort_vector(block.items) - else - should_fix = true - end - end - - icnt = icnt + #block.items - - -- Scan occupancy for spurious marks - for x=0,15 do - local ocx = block.occupancy[x] - for y=0,15 do - if ocx[y].item and not itable[x + y*16] then - print(bx,by,bz,x,y,'occupied & no item') - if fix then - ocx[y].item = false - else - should_fix = true - end - end - end - end - - cnt = cnt + 256 - end - - -- Check if any items are missing from blocks - for _,item in ipairs(df.global.world.items.all) do - if item.flags.on_ground and not found[item.id] then - can_fix = false - if not found_somewhere[item.id] then - print(id,item.pos.x,item.pos.y,item.pos.z,'on ground & not in block') - end - end - end - - -- Report - print(cnt.." tiles and "..icnt.." items checked.") - - if should_fix and can_fix then - print("Use 'fix/item-occupancy --fix' to fix the listed problems.") - elseif should_fix then - print("The problems are too severe to be fixed by this script.") - end -end - -local opt = ... -local fix = false - -if opt then - if opt == '--fix' then - fix = true - else - qerror('Invalid option: '..opt) - end -end - -print("Checking item occupancy - this will take a few seconds.") -check_block_items(fix) diff --git a/scripts/fix/loyaltycascade.rb b/scripts/fix/loyaltycascade.rb deleted file mode 100644 index 561e8b9e4..000000000 --- a/scripts/fix/loyaltycascade.rb +++ /dev/null @@ -1,70 +0,0 @@ -# Cancels a 'loyalty cascade' when citizens are killed -=begin - -fix/loyaltycascade -================== -Aborts loyalty cascades by fixing units whose own civ is the enemy. - -=end -def fixunit(unit) - return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id - links = unit.hist_figure_tg.entity_links - fixed = false - - # check if the unit is a civ renegade - if i1 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and - l.entity_id == df.ui.civ_id - } and i2 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and - l.entity_id == df.ui.civ_id - } - fixed = true - i1, i2 = i2, i1 if i1 > i2 - links.delete_at i2 - links.delete_at i1 - links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100) - df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again" - end - - # check if the unit is a group renegade - if i1 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and - l.entity_id == df.ui.group_id - } and i2 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and - l.entity_id == df.ui.group_id - } - fixed = true - i1, i2 = i2, i1 if i1 > i2 - links.delete_at i2 - links.delete_at i1 - links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100) - df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again" - end - - # fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed) - if fixed and unit.enemy.enemy_status_slot != -1 - i = unit.enemy.enemy_status_slot - unit.enemy.enemy_status_slot = -1 - cache = df.world.enemy_status_cache - cache.slot_used[i] = false - cache.rel_map[i].map! { -1 } - cache.rel_map.each { |a| a[i] = -1 } - cache.next_slot = i if cache.next_slot > i - end - - # return true if we actually fixed the unit - fixed -end - -count = 0 -df.unit_citizens.each { |u| - count += 1 if fixunit(u) -} - -if count > 0 - puts "loyalty cascade fixed (#{count} dwarves)" -else - puts "no loyalty cascade found" -end diff --git a/scripts/fix/merchants.lua b/scripts/fix/merchants.lua deleted file mode 100644 index ae3ef4233..000000000 --- a/scripts/fix/merchants.lua +++ /dev/null @@ -1,104 +0,0 @@ --- Allow humans to make trade agreements ---[[=begin - -fix/merchants -============= -Adds the Guild Representative position to all Human civilizations, -allowing them to make trade agreements. This was the default behaviour in -``0.28.181.40d`` and earlier. - -=end]] - - -function add_guild_rep(ent) - -- there was no guild rep - create it - local pos = df.entity_position:new() - ent.positions.own:insert('#', pos) - - pos.code = "GUILD_REPRESENTATIVE" - pos.id = ent.positions.next_position_id + 1 - ent.positions.next_position_id = ent.positions.next_position_id + 1 - - pos.flags.DO_NOT_CULL = true - pos.flags.MENIAL_WORK_EXEMPTION = true - pos.flags.SLEEP_PRETENSION = true - pos.flags.PUNISHMENT_EXEMPTION = true - pos.flags.ACCOUNT_EXEMPT = true - pos.flags.DUTY_BOUND = true - pos.flags.COLOR = true - pos.flags.HAS_RESPONSIBILITIES = true - pos.flags.IS_DIPLOMAT = true - pos.flags.IS_LEADER = true - -- not sure what these flags do, but the game sets them for a valid guild rep - pos.flags.unk_12 = true - pos.flags.unk_1a = true - pos.flags.unk_1b = true - pos.name[0] = "Guild Representative" - pos.name[1] = "Guild Representatives" - pos.precedence = 40 - pos.color[0] = 7 - pos.color[1] = 0 - pos.color[2] = 1 - - return pos -end - -local checked = 0 -local fixed = 0 - -for _,ent in pairs(df.global.world.entities.all) do - if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.MERCHANT_NOBILITY then - checked = checked + 1 - - update = true - -- see if we need to add a new position or modify an existing one - local found_position - for _,pos in pairs(ent.positions.own) do - if pos.responsibilities.TRADE and pos.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS then - -- a guild rep exists with the proper responsibilities - skip to the end - update = false - found_position=pos - break - end - -- Guild Representative position already exists, but has the wrong options - modify it instead of creating a new one - if pos.code == "GUILD_REPRESENTATIVE" then - found_position=pos - break - end - end - if update then - -- either there's no guild rep, or there is one and it's got the wrong responsibilities - if not found_position then - found_position = add_guild_rep(ent) - end - -- assign responsibilities - found_position.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS = true - found_position.responsibilities.TRADE=true - end - - -- make sure the guild rep position, whether we created it or not, is set up for proper assignment - local assign = true - for _,p in pairs(ent.positions.assignments) do - if p.position_id == found_position.id then -- it is - nothing more to do here - assign = false - break - end - end - if assign then - -- it isn't - set it up - local asn = df.entity_position_assignment:new() - ent.positions.assignments:insert('#', asn) - - asn.id = ent.positions.next_assignment_id - ent.positions.next_assignment_id = asn.id + 1 - asn.position_id = found_position.id - asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags - asn.flags[0] = true -- and set the first one - end - if update or assign then - fixed = fixed + 1 - end - end -end - -print("Added merchant nobility for "..fixed.." of "..checked.." civilizations.") diff --git a/scripts/fix/population-cap.lua b/scripts/fix/population-cap.lua deleted file mode 100644 index 1261fb504..000000000 --- a/scripts/fix/population-cap.lua +++ /dev/null @@ -1,47 +0,0 @@ --- Tells mountainhomes your pop. to avoid overshoot - ---[[=begin - -fix/population-cap -================== -Run this after every migrant wave to ensure your population cap is not exceeded. - -The reason for population cap problems is that the population value it -is compared against comes from the last dwarven caravan that successfully -left for mountainhomes. This script instantly updates it. -Note that a migration wave can still overshoot the limit by 1-2 dwarves because -of the last migrant bringing his family. Likewise, king arrival ignores cap. - -=end]] -local args = {...} - -local ui = df.global.ui -local ui_stats = ui.tasks -local civ = df.historical_entity.find(ui.civ_id) - -if not civ then - qerror('No active fortress.') -end - -local civ_stats = civ.activity_stats - -if not civ_stats then - if args[1] ~= 'force' then - qerror('No caravan report object; use "fix/population-cap force" to create one') - end - print('Creating an empty statistics structure...') - civ.activity_stats = { - new = true, - created_weapons = { resize = #ui_stats.created_weapons }, - discovered_creature_foods = { resize = #ui_stats.discovered_creature_foods }, - discovered_creatures = { resize = #ui_stats.discovered_creatures }, - discovered_plant_foods = { resize = #ui_stats.discovered_plant_foods }, - discovered_plants = { resize = #ui_stats.discovered_plants }, - } - civ_stats = civ.activity_stats -end - --- Use max to keep at least some of the original caravan communication idea -civ_stats.population = math.max(civ_stats.population, ui_stats.population) - -print('Home civ notified about current population.') diff --git a/scripts/fix/stable-temp.lua b/scripts/fix/stable-temp.lua deleted file mode 100644 index 7238d88d9..000000000 --- a/scripts/fix/stable-temp.lua +++ /dev/null @@ -1,71 +0,0 @@ --- Reset item temperature to the value of their tile. ---[[=begin - -fix/stable-temp -=============== -Instantly sets the temperature of all free-lying items to be in equilibrium with -the environment, which stops temperature updates until something changes. -To maintain this efficient state, use `tweak fast-heat `. - -=end]] -local args = {...} - -local apply = (args[1] == 'apply') - -local count = 0 -local types = {} - -local function update_temp(item,btemp) - if item.temperature.whole ~= btemp then - count = count + 1 - local tid = item:getType() - types[tid] = (types[tid] or 0) + 1 - end - - if apply then - item.temperature.whole = btemp - item.temperature.fraction = 0 - - if item.contaminants then - for _,c in ipairs(item.contaminants) do - c.temperature.whole = btemp - c.temperature.fraction = 0 - end - end - end - - for _,sub in ipairs(dfhack.items.getContainedItems(item)) do - update_temp(sub,btemp) - end - - if apply then - item:checkTemperatureDamage() - end -end - -local last_frame = df.global.world.frame_counter-1 - -for _,item in ipairs(df.global.world.items.all) do - if item.flags.on_ground and df.item_actual:is_instance(item) and - item.temp_updated_frame == last_frame then - local pos = item.pos - local block = dfhack.maps.getTileBlock(pos) - if block then - update_temp(item, block.temperature_1[pos.x%16][pos.y%16]) - end - end -end - -if apply then - print('Items updated: '..count) -else - print("Use 'fix/stable-temp apply' to force-change temperature.") - print('Items not in equilibrium: '..count) -end - -local tlist = {} -for k,_ in pairs(types) do tlist[#tlist+1] = k end -table.sort(tlist, function(a,b) return types[a] > types[b] end) -for _,k in ipairs(tlist) do - print(' '..df.item_type[k]..':', types[k]) -end diff --git a/scripts/fix/stuckdoors.rb b/scripts/fix/stuckdoors.rb deleted file mode 100644 index f99d0f263..000000000 --- a/scripts/fix/stuckdoors.rb +++ /dev/null @@ -1,32 +0,0 @@ -# fix doors that are frozen in 'open' state - -# this may happen after people mess with the game by (incorrectly) teleporting units or items -# a door may stick open if the map occupancy flags are wrong -=begin - -fix/stuckdoors -============== -Fix doors that are stuck open due to incorrect map occupancy flags, eg due to -incorrect use of `teleport`. - -=end -count = 0 -df.world.buildings.all.each { |bld| - # for all doors - next if bld._rtti_classname != :building_doorst - # check if it is open - next if bld.close_timer == 0 - # check if occupancy is set - occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) - if (occ.unit or occ.unit_grounded) and not - # check if an unit is present - df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z } - count += 1 - occ.unit = occ.unit_grounded = false - end - if occ.item and not df.world.items.all.find { |i| i.pos.x == bld.x1 and i.pos.y == bld.y1 and i.pos.z == bld.z } - count += 1 - occ.item = false - end -} -puts "unstuck #{count} doors" diff --git a/scripts/fixnaked.lua b/scripts/fixnaked.lua deleted file mode 100644 index 39cfd2b77..000000000 --- a/scripts/fixnaked.lua +++ /dev/null @@ -1,50 +0,0 @@ ---removes unhappy thoughts due to lack of clothing ---[[=begin - -fixnaked -======== -Removes all unhappy thoughts due to lack of clothing. - -=end]] - -function fixnaked() -local total_fixed = 0 -local total_removed = 0 - -for fnUnitCount,fnUnit in ipairs(df.global.world.units.all) do - if fnUnit.race == df.global.ui.race_id then - local listEvents = fnUnit.status.recent_events - --for lkey,lvalue in pairs(listEvents) do - -- print(df.unit_thought_type[lvalue.type],lvalue.type,lvalue.age,lvalue.subtype,lvalue.severity) - --end - - local found = 1 - local fixed = 0 - while found == 1 do - local events = fnUnit.status.recent_events - found = 0 - for k,v in pairs(events) do - if v.type == df.unit_thought_type.Uncovered - or v.type == df.unit_thought_type.NoShirt - or v.type == df.unit_thought_type.NoShoes - or v.type == df.unit_thought_type.NoCloak - or v.type == df.unit_thought_type.OldClothing - or v.type == df.unit_thought_type.TatteredClothing - or v.type == df.unit_thought_type.RottedClothing then - events:erase(k) - found = 1 - total_removed = total_removed + 1 - fixed = 1 - break - end - end - end - if fixed == 1 then - total_fixed = total_fixed + 1 - print(total_fixed, total_removed, dfhack.TranslateName(dfhack.units.getVisibleName(fnUnit))) - end - end -end -print("Total Fixed: "..total_fixed) -end -fixnaked() diff --git a/scripts/forum-dwarves.lua b/scripts/forum-dwarves.lua deleted file mode 100644 index e6b98395a..000000000 --- a/scripts/forum-dwarves.lua +++ /dev/null @@ -1,133 +0,0 @@ --- Save a copy of a text screen for the DF forums --- original author: Caldfir; edited by expwnent, Mchl ---[[=begin - -forum-dwarves -============= -Saves a copy of a text screen, formatted in bbcode for posting to the Bay12 Forums. -Use ``forum-dwarves help`` for more information. - -=end]] - -local args = {...} - -if args[1] == 'help' then - print([[ -description: - This script will attempt to read the current df-screen, and if it is a - text-viewscreen (such as the dwarf 'thoughts' screen or an item - 'description') then append a marked-up version of this text to the - target file. Previous entries in the file are not overwritten, so you - may use the 'forumdwarves' command multiple times to create a single - document containing the text from multiple screens (eg: text screens - from several dwarves, or text screens from multiple artifacts/items, - or some combination). -known screens: - The screens which have been tested and known to function properly with - this script are: - 1: dwarf/unit 'thoughts' screen - 2: item/art 'description' screen - 3: individual 'historical item/figure' screens - There may be other screens to which the script applies. It should be - safe to attempt running the script with any screen active, with an - error message to inform you when the selected screen is not appropriate - for this script. -target file: - The target file's name is 'forumdwarves.txt'. A remider to this effect - will be displayed if the script is successful. -character encoding: - The text will likely be using system-default encoding, and as such - will likely NOT display special characters (eg:È,ı,Ã) correctly. To - fix this, you need to modify the character set that you are reading - the document with. 'Notepad++' is a freely available program which - can do this using the following steps: - 1: open the document in Notepad++ - 2: in the menu-bar, select - Encoding->Character Sets->Western European->OEM-US - 3: copy the text normally to wherever you want to use it -]]) - return -end -local utils = require 'utils' -local gui = require 'gui' -local dialog = require 'gui.dialogs' -local colors_css = { - [0] = 'black', - [1] = 'navy', - [2] = 'green', - [3] = 'teal', - [4] = 'maroon', - [5] = 'purple', - [6] = 'olive', - [7] = 'silver', - [8] = 'gray', - [9] = 'blue', - [10] = 'lime', - [11] = 'cyan', - [12] = 'red', - [13] = 'magenta', - [14] = 'yellow', - [15] = 'white' -} - -local scrn = dfhack.gui.getCurViewscreen() -local flerb = dfhack.gui.getFocusString(scrn) - -local function format_for_forum(strin) - local strout = strin - - local newline_idx = string.find(strout, '[P]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3) - newline_idx = string.find(strout, '[P]', 1, true) - end - - newline_idx = string.find(strout, '[B]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3) - newline_idx = string.find(strout, '[B]', 1, true) - end - - newline_idx = string.find(strout, '[R]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3) - newline_idx = string.find(strout, '[R]', 1, true) - end - - local color_idx = string.find(strout, '[C:', 1, true) - while color_idx ~= nil do - local colormatch = (string.byte(strout, color_idx+3)-48)+((string.byte(strout, color_idx+7)-48)*8) - strout = string.sub(strout,1, color_idx-1)..'[/color][color='..colors_css[colormatch]..']'..string.sub(strout,color_idx+9) - color_idx = string.find(strout, '[C:', 1, true) - end - - return strout -end - -if flerb == 'textviewer' then - print(scrn) - printall(scrn) - local lines = scrn.src_text - local line = "" - - if lines ~= nil then - local log = io.open('forumdwarves.txt', 'a') - log:write("[color=silver]") - log:write(scrn.title) - for n,x in ipairs(lines) do - print(x) - printall(x) - print(x.value) - printall(x.value) - if (x ~= nil) and (x.value ~= nil) then - log:write(format_for_forum(x.value), ' ') - --log:write(x[0],'\n') - end - end - log:write("[/color]\n") - log:close() - end - print 'data prepared for forum in \"forumdwarves.txt\"' -else - print 'this is not a textview screen' -end diff --git a/scripts/full-heal.lua b/scripts/full-heal.lua deleted file mode 100644 index f9388058c..000000000 --- a/scripts/full-heal.lua +++ /dev/null @@ -1,153 +0,0 @@ --- Attempts to fully heal the selected unit ---author Kurik Amudnil, Urist DaVinci ---edited by expwnent - ---[[=begin - -full-heal -========= -Attempts to fully heal the selected unit. ``full-heal -r`` attempts to resurrect the unit. - -=end]] - -local utils=require('utils') - -validArgs = validArgs or utils.invert({ - 'r', - 'help', - 'unit', - 'keep_corpse' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print('full-heal: heal a unit completely from anything, optionally including death.') - print(' full-heal -unit [unitId]') - print(' heal the unit with the given id') - print(' full-heal -r -unit [unitId]') - print(' heal the unit with the given id and bring them back from death if they are dead') - print(' full-heal -r -keep_corpse -unit [unitId]') - print(' heal the unit with the given id and bring them back from death if they are dead, without removing their corpse') - print(' full-heal') - print(' heal the currently selected unit') - print(' full-heal -r') - print(' heal the currently selected unit and bring them back from death if they are dead') - print(' full-heal -help') - print(' print this help message') - return -end - -if(args.unit) then - unit = df.unit.find(args.unit) -else - unit = dfhack.gui.getSelectedUnit() -end - -if not unit then - qerror('Error: please select a unit or pass its id as an argument.') -end - -if unit then - if args.r then - if unit.flags1.dead then - --print("Resurrecting...") - unit.flags2.slaughter = false - unit.flags3.scuttle = false - end - unit.flags1.dead = false - unit.flags2.killed = false - unit.flags3.ghostly = false - if not args.keep_corpse then - for _,corpse in ipairs(df.global.world.items.other.CORPSE) do - if corpse.unit_id==unit.id then - corpse.flags.garbage_collect=true - corpse.flags.forbid=true - corpse.flags.hidden=true - end - end - end - --unit.unk_100 = 3 - end - - --print("Erasing wounds...") - while #unit.body.wounds > 0 do - unit.body.wounds:erase(#unit.body.wounds-1) - end - unit.body.wound_next_id=1 - - --print("Refilling blood...") - unit.body.blood_count=unit.body.blood_max - - --print("Resetting grasp/stand status...") - unit.status2.limbs_stand_count=unit.status2.limbs_stand_max - unit.status2.limbs_grasp_count=unit.status2.limbs_grasp_max - - --print("Resetting status flags...") - unit.flags2.has_breaks=false - unit.flags2.gutted=false - unit.flags2.circulatory_spray=false - unit.flags2.vision_good=true - unit.flags2.vision_damaged=false - unit.flags2.vision_missing=false - unit.flags2.breathing_good=true - unit.flags2.breathing_problem=false - - unit.flags2.calculated_nerves=false - unit.flags2.calculated_bodyparts=false - unit.flags2.calculated_insulation=false - unit.flags3.compute_health=true - - --print("Resetting counters...") - unit.counters.winded=0 - unit.counters.stunned=0 - unit.counters.unconscious=0 - unit.counters.webbed=0 - unit.counters.pain=0 - unit.counters.nausea=0 - unit.counters.dizziness=0 - - unit.counters2.paralysis=0 - unit.counters2.fever=0 - unit.counters2.exhaustion=0 - unit.counters2.hunger_timer=0 - unit.counters2.thirst_timer=0 - unit.counters2.sleepiness_timer=0 - unit.counters2.vomit_timeout=0 - - --print("Resetting body part status...") - local v=unit.body.components - for i=0,#v.nonsolid_remaining - 1,1 do - v.nonsolid_remaining[i] = 100 -- percent remaining of fluid layers (Urist Da Vinci) - end - - v=unit.body.components - for i=0,#v.layer_wound_area - 1,1 do - v.layer_status[i].whole = 0 -- severed, leaking layers (Urist Da Vinci) - v.layer_wound_area[i] = 0 -- wound contact areas (Urist Da Vinci) - v.layer_cut_fraction[i] = 0 -- 100*surface percentage of cuts/fractures on the body part layer (Urist Da Vinci) - v.layer_dent_fraction[i] = 0 -- 100*surface percentage of dents on the body part layer (Urist Da Vinci) - v.layer_effect_fraction[i] = 0 -- 100*surface percentage of "effects" on the body part layer (Urist Da Vinci) - end - - v=unit.body.components.body_part_status - for i=0,#v-1,1 do - v[i].on_fire = false - v[i].missing = false - v[i].organ_loss = false - v[i].organ_damage = false - v[i].muscle_loss = false - v[i].muscle_damage = false - v[i].bone_loss = false - v[i].bone_damage = false - v[i].skin_damage = false - v[i].motor_nerve_severed = false - v[i].sensory_nerve_severed = false - end - - if unit.job.current_job and unit.job.current_job.job_type == df.job_type.Rest then - --print("Wake from rest -> clean self...") - unit.job.current_job = df.job_type.CleanSelf - end -end - diff --git a/scripts/gaydar.lua b/scripts/gaydar.lua deleted file mode 100644 index 66aec2c12..000000000 --- a/scripts/gaydar.lua +++ /dev/null @@ -1,208 +0,0 @@ --- Shows the sexual orientation of units ---[[=begin - -gaydar -====== -Shows the sexual orientation of units, useful for social engineering or checking -the viability of livestock breeding programs. Use ``gaydar -help`` for information -on available filters for orientation, citizenship, species, etc. - -=end]] -local utils = require('utils') - -validArgs = utils.invert({ - 'all', - 'citizens', - 'named', - 'notStraight', - 'gayOnly', - 'biOnly', - 'straightOnly', - 'asexualOnly', - 'help' -}) - - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print( -[[gaydar.lua -arguments: - -help - print this help message -unit filters: - -all - shows orientation of every creature - -citizens - shows only orientation of citizens in fort mode - -named - shows orientation of all named units on map -orientation filters: - -notStraight - shows only creatures who are not strictly straight - -gayOnly - shows only creatures who are strictly gay - -biOnly - shows only creatures who can get into romances with - both sexes - -straightOnly - shows only creatures who are strictly straight. - -asexualOnly - shows only creatures who are strictly asexual. - - No argument will show the orientation of the unit - under the cursor. -]]) - return -end - -function dfprint(s) - print(dfhack.df2console(s)) -end - -function getSexString(sex) - local sexStr - if sex==0 then - sexStr=string.char(12) - elseif sex==1 then - sexStr=string.char(11) - else - return "" - end - return string.char(40)..sexStr..string.char(41) -end - -local function determineorientation(unit) - if unit.sex~=-1 and unit.status.current_soul then - local return_string='' - local orientation=unit.status.current_soul.orientation_flags - if orientation.indeterminate then - return ' indeterminate (probably adventurer)' - end - local male_interested,asexual=false,true - if orientation.romance_male then - return_string=return_string..' likes males' - male_interested=true - asexual=false - elseif orientation.marry_male then - return_string=return_string..' will marry males' - male_interested=true - asexual=false - end - if orientation.romance_female then - if male_interested then - return_string=return_string..' and likes females' - else - return_string=return_string..' likes females' - end - asexual=false - elseif orientation.marry_female then - if male_interested then - return_string=return_string..' and will marry females' - else - return_string=return_string..' will marry females' - end - asexual=false - end - if asexual then - return_string=' is asexual' - end - return return_string - else - return " is not biologically capable of sex" - end -end - -local function nameOrSpeciesAndNumber(unit) - if unit.name.has_name then - return dfhack.TranslateName(dfhack.units.getVisibleName(unit))..' '..getSexString(unit.sex),true - else - return 'Unit #'..unit.id..' ('..df.creature_raw.find(unit.race).caste[unit.caste].caste_name[0]..' '..getSexString(unit.sex)..')',false - end -end - -local orientations={} - -if args.citizens then - for k,v in ipairs(df.global.world.units.active) do - if dfhack.units.isCitizen(v) then - table.insert(orientations,nameOrSpeciesAndNumber(v) .. determineorientation(v)) - end - end -elseif args.all then - for k,v in ipairs(df.global.world.units.active) do - table.insert(orientations,nameOrSpeciesAndNumber(v)..determineorientation(v)) - end -elseif args.named then - for k,v in ipairs(df.global.world.units.active) do - local name,ok=nameOrSpeciesAndNumber(v) - if ok then - table.insert(orientations,name..determineorientation(v)) - end - end -else - local unit=dfhack.gui.getSelectedUnit(true) - local name,ok=nameOrSpeciesAndNumber(unit) - dfprint(name..determineorientation(unit)) - return -end - -function isNotStraight(v) - if v:find(string.char(12)) and v:find(' female') then return true end - if v:find(string.char(11)) and v:find(' male') then return true end - if v:find('asexual') then return true end - if v:find('indeterminate') then return true end - return false -end - -function isGay(v) - if v:find('asexual') then return false end - if v:find(string.char(12)) and not v:find(' male') then return true end - if v:find(string.char(11)) and not v:find(' female') then return true end - return false -end - -function isAsexual(v) - if v:find('asexual') or v:find('indeterminate') then return true else return false end -end - -function isBi(v) - if v:find(' female') and v:find(' male') then return true else return false end -end - -if args.notStraight then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isNotStraight(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.gayOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isGay(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.asexualOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isAsexual(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.straightOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if not isNotStraight(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -elseif args.biOnly then - local totalNotShown=0 - for k,v in ipairs(orientations) do - if isBi(v) then dfprint(v) else totalNotShown=totalNotShown+1 end - end - print('Total not shown: '..totalNotShown) -else - for k,v in ipairs(orientations) do - dfprint(v) - end -end diff --git a/scripts/growcrops.rb b/scripts/growcrops.rb deleted file mode 100644 index c3c143254..000000000 --- a/scripts/growcrops.rb +++ /dev/null @@ -1,64 +0,0 @@ -# Instantly grow crops in farm plots -=begin - -growcrops -========= -Instantly grow seeds inside farming plots. - -With no argument, this command list the various seed types currently in -use in your farming plots. With a seed type, the script will grow 100 of -these seeds, ready to be harvested. Set the number with a 2nd argument. - -For example, to grow 40 plump helmet spawn:: - - growcrops plump 40 - -=end - -material = $script_args[0] -count_max = $script_args[1].to_i -count_max = 100 if count_max == 0 - -# cache information from the raws -@raws_plant_name ||= {} -@raws_plant_growdur ||= {} -if @raws_plant_name.empty? - df.world.raws.plants.all.each_with_index { |p, idx| - @raws_plant_name[idx] = p.id - @raws_plant_growdur[idx] = p.growdur - } -end - -inventory = Hash.new(0) -df.world.items.other[:SEEDS].each { |seed| - next if not seed.flags.in_building - next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } - next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] - inventory[seed.mat_index] += 1 -} - -if !material or material == 'help' or material == 'list' - # show a list of available crop types - inventory.sort_by { |mat, c| c }.each { |mat, c| - name = df.world.raws.plants.all[mat].id - puts " #{name} #{c}" - } - -else - - mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] }) - unless wantmat = @raws_plant_name.index(mat) - raise "invalid plant material #{material}" - end - - count = 0 - df.world.items.other[:SEEDS].each { |seed| - next if seed.mat_index != wantmat - next if not seed.flags.in_building - next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } - next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] - seed.grow_counter = @raws_plant_growdur[seed.mat_index] - count += 1 - } - puts "Grown #{count} #{mat}" -end diff --git a/scripts/gui/about.txt b/scripts/gui/about.txt deleted file mode 100644 index 1c83e50b2..000000000 --- a/scripts/gui/about.txt +++ /dev/null @@ -1,6 +0,0 @@ -``gui/*`` scripts implement dialogs in the main game window. - -In order to avoid user confusion, as a matter of policy all these tools -display the word "DFHack" on the screen somewhere while active. -When that is not appropriate because they merely add keybinding hints to -existing DF screens, they deliberately use red instead of green for the key. diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua deleted file mode 100644 index 70d4bc083..000000000 --- a/scripts/gui/advfort.lua +++ /dev/null @@ -1,1846 +0,0 @@ --- allows to do jobs in adv. mode. - ---[[=begin - -gui/advfort -=========== -This script allows to perform jobs in adventure mode. For more complete help -press :kbd:`?` while script is running. It's most comfortable to use this as a -keybinding. (e.g. ``keybinding set Ctrl-T gui/advfort``). Possible arguments: - -:-a, --nodfassign: uses different method to assign items. -:-i, --inventory: checks inventory for possible items to use in the job. -:-c, --cheat: relaxes item requirements for buildings (e.g. walls from bones). Implies -a -:job: selects that job (e.g. Dig or FellTree) - -An example of player digging in adventure mode: - -.. image:: /docs/images/advfort.png - -**WARNING:** changes only persist in non procedural sites, namely: player forts, caves, and camps. - -=end]] - ---[==[ - version: 0.044 - changelog: - *0.044 - - added output to clear_jobs of number of cleared jobs - - another failed attempt at gather plants fix - - added track stop configuration window - *0.043 - - fixed track carving: up/down was reversed and removed (temp) requirements because they were not working correctly - - added checks for unsafe conditions (currently quite stupid). Should save few adventurers that are trying to work in dangerous conditions (e.g. fishing) - - unsafe checks disabled by "-u" ir "--unsafe" - *0.042 - - fixed (probably for sure now) the crash bug. - - added --clear_jobs debug option. Will delete ALL JOBS! - *0.041 - - fixed cooking allowing already cooked meals - *0.04 - - add (-q)uick mode. Autoselects materials. - - fixed few(?) crash bugs - - fixed job errors not being shown in df - *0.031 - - make forbiding optional (-s)afe mode - *0.03 - - forbid doing anything in non-sites unless you are (-c)heating - - a bit more documentation and tidying - - add a deadlock fix - *0.021 - - advfort_items now autofills items - - tried out few things to fix gather plants - *0.02 - - fixed axles not being able to be placed in other direction (thanks SyrusLD) - - added lever linking - - restructured advfort_items, don't forget to update that too! - - Added clutter view if shop is cluttered. - *0.013 - - fixed siege weapons and traps (somewhat). Now you can load them with new menu :) - *0.012 - - fix for some jobs not finding correct building. - *0.011 - - fixed crash with building jobs (other jobs might have been crashing too!) - - fixed bug with building asking twice to input items - *0.01 - - instant job startation - - item selection screen (!) - - BUG:custom jobs need stuff on ground to work - *0.003 - - fixed farms (i think...) - - added faster time pasing (yay for random deaths from local wildlife) - - still hasn't fixed gather plants. but you can visit local market, buy a few local fruits/vegetables eat them and use seeds - - other stuff - *0.002 - - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) - - gather plants still not working... Other jobs seem to work. - - added new-and-improved waiting. Interestingly it could be improved to be interuptable. - todo list: - - document everything! Maybe somebody would understand what is happening then and help me :< - - when building trap add to known traps (or known adventurers?) so that it does not trigger on adventurer - bugs list: - - items blocking construction stuck the game - - burning charcoal crashed game - - gem thingies probably broken - - custom reactions semibroken - - gathering plants still broken - ---]==] - ---keybinding, change to your hearts content. Only the key part. -keybinds={ -nextJob={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, -prevJob={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, -continue={key="A_WAIT",desc="Continue job if available"}, -down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, -down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, -up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, -up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, -use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, -workshop={key="CHANGETAB",desc="Show building menu"}, -quick={key="CUSTOM_Q",desc="Toggle quick item select"}, -} --- building filters -build_filter={ - forbid_all=false, --this forbits all except the "allow" - allow={"MetalSmithsForge"}, --ignored if forbit_all=false - forbid={} --ignored if forbit_all==true -} -build_filter.HUMANish={ - forbid_all=true, - allow={"Masons"}, - forbid={} -} - ---economic stone fix: just disable all of them ---[[ FIXME: maybe let player select which to disable?]] -for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end - -local gui = require 'gui' -local wid=require 'gui.widgets' -local dialog=require 'gui.dialogs' -local buildings=require 'dfhack.buildings' -local bdialog=require 'gui.buildings' -local workshopJobs=require 'dfhack.workshops' -local utils=require 'utils' -local gscript=require 'gui.script' - -local tile_attrs = df.tiletype.attrs - -settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false,gui_item_select=true,only_in_sites=false} - -function hasValue(tbl,val) - for k,v in pairs(tbl) do - if v==val then - return true - end - end - return false -end -function reverseRaceLookup(id) - return df.global.world.raws.creatures.all[id].creature_id -end -function deon_filter(name,type_id,subtype_id,custom_id, parent) - --print(name) - local adv=df.global.world.units.active[0] - local race_filter=build_filter[reverseRaceLookup(adv.race)] - if race_filter then - if race_filter.forbid_all then - return hasValue(race_filter.allow,name) - else - return not hasValue(race_filter.forbid,name) - end - else - if build_filter.forbid_all then - return hasValue(build_filter.allow,name) - else - return not hasValue(build_filter.forbid,name) - end - end -end -local mode_name -for k,v in ipairs({...}) do --setting parsing - if v=="-c" or v=="--cheat" then - settings.build_by_items=true - settings.df_assign=false - elseif v=="-q" or v=="--quick" then - settings.quick=true - elseif v=="-u" or v=="--unsafe" then --ignore pain and etc - settings.unsafe=true - elseif v=="-s" or v=="--safe" then - settings.safe=true - elseif v=="-i" or v=="--inventory" then - settings.check_inv=true - settings.df_assign=false - elseif v=="-a" or v=="--nodfassign" then - settings.df_assign=false - elseif v=="-h" or v=="--help" then - settings.help=true - elseif v=="--clear_jobs" then - settings.clear_jobs=true - else - mode_name=v - end -end - -mode=mode or 0 -last_building=last_building or {} - -function Disclaimer(tlb) - local dsc={"The Gathering Against ",{text="Goblin ",pen=dfhack.pen.parse{fg=COLOR_GREEN,bg=0}}, "Oppresion ", - "(TGAGO) is not responsible for all ",NEWLINE,"the damage that this tool can (and will) cause to you and your loved worlds",NEWLINE,"and/or sanity.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} - if tlb then - for _,v in ipairs(dsc) do - table.insert(tlb,v) - end - end - return dsc -end -function showHelp() - local helptext={ - "This tool allow you to perform jobs as a dwarf would in dwarf mode. When ",NEWLINE, - "cursor is available you can press ",{key="SELECT", text="select",key_sep="()"}, - " to enqueue a job from",NEWLINE,"pointer location. If job is 'Build' and there is no planed construction",NEWLINE, - "at cursor this tool show possible building choices.",NEWLINE,NEWLINE,{text="Keybindings:",pen=dfhack.pen.parse{fg=COLOR_CYAN,bg=0}},NEWLINE - } - for k,v in pairs(keybinds) do - table.insert(helptext,{key=v.key,text=v.desc,key_sep=":"}) - table.insert(helptext,NEWLINE) - end - table.insert(helptext,{text="CAREFULL MOVE",pen=dfhack.pen.parse{fg=COLOR_LIGHTGREEN,bg=0}}) - table.insert(helptext,": use job in that direction") - table.insert(helptext,NEWLINE) - table.insert(helptext,NEWLINE) - Disclaimer(helptext) - dialog.showMessage("Help!?!",helptext) -end - -if settings.help then - showHelp() - return -end - ---[[ Util functions ]]-- -function advGlobalPos() - local map=df.global.world.map - local wd=df.global.world.world_data - local adv=df.global.world.units.active[0] - --wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y - --return wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y - --return wd.adv_region_x*16+wd.adv_emb_x+adv.pos.x/16,wd.adv_region_y*16+wd.adv_emb_y+adv.pos.y/16 - --print(map.region_x,map.region_y,adv.pos.x,adv.pos.y) - --print(map.region_x+adv.pos.x/48, map.region_y+adv.pos.y/48,wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y) - return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48) -end -function inSite() - - local tx,ty=advGlobalPos() - - for k,v in pairs(df.global.world.world_data.sites) do - local tp={v.pos.x,v.pos.y} - if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and - ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then - return v - end - end -end ---[[ low level job management ]]-- -function findAction(unit,ltype) - ltype=ltype or df.unit_action_type.None - for i,v in ipairs(unit.actions) do - if v.type==ltype then - return v - end - end -end -function add_action(unit,action_data) - local action=findAction(unit) --find empty action - if action then - action:assign(action_data) - action.id=unit.next_action_id - unit.next_action_id=unit.next_action_id+1 - else - local tbl=copyall(action_data) - tbl.new=true - tbl.id=unit.next_action_id - unit.actions:insert("#",tbl) - unit.next_action_id=unit.next_action_id+1 - end -end -function addJobAction(job,unit) --what about job2? - if job==nil then - error("invalid job") - end - if findAction(unit,df.unit_action_type.Job) or findAction(unit,df.unit_action_type.Job2) then - print("Already has job action") - return - end - local action=findAction(unit) - local pos=copyall(unit.pos) - --local pos=copyall(job.pos) - unit.path.dest:assign(pos) - --job - local data={type=df.unit_action_type.Job,data={job={x=pos.x,y=pos.y,z=pos.z,timer=10}}} - --job2: - --local data={type=df.unit_action_type.Job2,data={job2={timer=10}}} - add_action(unit,data) - --add_action(unit,{type=df.unit_action_type.Unsteady,data={unsteady={timer=5}}}) -end - -function make_native_job(args) - if args.job == nil then - local newJob=df.job:new() - newJob.id=df.global.job_next_id - df.global.job_next_id=df.global.job_next_id+1 - newJob.flags.special=true - newJob.job_type=args.job_type - newJob.completion_timer=-1 - - newJob.pos:assign(args.pos) - --newJob.pos:assign(args.unit.pos) - args.job=newJob - args.unlinked=true - end -end -function smart_job_delete( job ) - local gref_types=df.general_ref_type - --TODO: unmark items as in job - for i,v in ipairs(job.general_refs) do - if v:getType()==gref_types.BUILDING_HOLDER then - local b=v:getBuilding() - if b then - --remove from building - for i,v in ipairs(b.jobs) do - if v==job then - b.jobs:erase(i) - break - end - end - else - print("Warning: building holder ref was invalid while deleting job") - end - elseif v:getType()==gref_types.UNIT_WORKER then - local u=v:getUnit() - if u then - u.job.current_job =nil - else - print("Warning: unit worker ref was invalid while deleting job") - end - else - print("Warning: failed to remove link from job with type:",gref_types[v:getType()]) - end - end - --unlink job - local link=job.list_link - if link.prev then - link.prev.next=link.next - end - if link.next then - link.next.prev=link.prev - end - link:delete() - --finally delete the job - job:delete() -end ---TODO: this logic might be better with other --starting logic-- -if settings.clear_jobs then - print("Clearing job list!") - local counter=0 - local job_link=df.global.world.job_list.next - while job_link and job_link.item do - local job=job_link.item - job_link=job_link.next - smart_job_delete(job) - counter=counter+1 - end - print("Deleted: "..counter.." jobs") - return -end -function makeJob(args) - gscript.start(function () - make_native_job(args) - local failed - for k,v in ipairs(args.pre_actions or {}) do - local ok,msg=v(args) - if not ok then - failed=msg - break - end - end - if failed==nil then - AssignUnitToJob(args.job,args.unit,args.from_pos) - for k,v in ipairs(args.post_actions or {}) do - local ok,msg=v(args) - if not ok then - failed=msg - break - end - end - if failed then - UnassignJob(args.job,args.unit) - end - end - if failed==nil then - if args.unlinked then - dfhack.job.linkIntoWorld(args.job,true) - args.unlinked=false - end - addJobAction(args.job,args.unit) - args.screen:wait_tick() - else - if not args.no_job_delete then - smart_job_delete(args.job) - end - dfhack.gui.showAnnouncement("Job failed:"..failed,5,1) - end - end) -end - -function UnassignJob(job,unit,unit_pos) - unit.job.current_job=nil -end -function AssignUnitToJob(job,unit,unit_pos) - job.general_refs:insert("#",{new=df.general_ref_unit_workerst,unit_id=unit.id}) - unit.job.current_job=job - unit_pos=unit_pos or {x=job.pos.x,y=job.pos.y,z=job.pos.z} - unit.path.dest:assign(unit_pos) - return true -end -function SetCreatureRef(args) - local job=args.job - local pos=args.pos - for k,v in pairs(df.global.world.units.active) do - if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - job.general_refs:insert("#",{new=df.general_ref_unit_cageest,unit_id=v.id}) - return - end - end -end - -function SetWebRef(args) - local pos=args.pos - for k,v in pairs(df.global.world.items.other.ANY_WEBS) do - if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - args.job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) - return - end - end -end -function SetPatientRef(args) - local job=args.job - local pos=args.pos - for k,v in pairs(df.global.world.units.active) do - if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then - job.general_refs:insert("#",{new=df.general_ref_unit_patientst,unit_id=v.id}) - return - end - end -end -function SetCarveDir(args) - local job=args.job - local pos=args.pos - local from_pos=args.from_pos - local dirs={up=18,down=19,right=20,left=21} - if pos.x>from_pos.x then - job.item_category[dirs.right]=true - elseif pos.xfrom_pos.y then - job.item_category[dirs.down]=true - elseif pos.y0 and item_suitable) or settings.build_by_items then - --cur_item.flags.in_job=true - job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) - item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() - --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) - used_item_id[cur_item.id]=true - end - end - end - end - end - - return item_suitability,item_counts -end -function AssignJobItems(args) - if settings.df_assign then --use df default logic and hope that it would work - return true - end - -- first find items that you want to use for the job - local job=args.job - local its=EnumItems_with_settings(args) - - local item_suitability,item_counts=find_suitable_items(job,its) - --[[while(#job.items>0) do --clear old job items - job.items[#job.items-1]:delete() - job.items:erase(#job.items-1) - end]] - - if settings.gui_item_select and #job.job_items>0 then - local item_dialog=require('hack.scripts.gui.advfort_items') - - if settings.quick then --TODO not so nice hack. instead of rewriting logic for job item filling i'm using one in gui dialog... - local item_editor=item_dialog.jobitemEditor{ - job = job, - items = item_suitability, - } - if item_editor:jobValid() then - item_editor:commit() - finish_item_assign(args) - return true - else - return false, "Quick select items" - end - else - local ret=item_dialog.showItemEditor(job,item_suitability) - if ret then - finish_item_assign(args) - return true - else - print("Failed job, i'm confused...") - end - --end) - return false,"Selecting items" - end - else - if not settings.build_by_items then - for job_id, trg_job_item in ipairs(job.job_items) do - if item_counts[job_id]>0 then - print("Not enough items for this job") - return false, "Not enough items for this job" - end - end - end - finish_item_assign(args) - return true - end - -end - -CheckAndFinishBuilding=function (args,bld) - args.building=args.building or bld - for idx,job in pairs(bld.jobs) do - if job.job_type==df.job_type.ConstructBuilding then - args.job=job - args.no_job_delete=true - break - end - end - - if args.job~=nil then - args.pre_actions={AssignJobItems} - else - local t={items=buildings.getFiltersByType({},bld:getType(),bld:getSubtype(),bld:getCustomType())} - args.pre_actions={dfhack.curry(setFiltersUp,t),AssignBuildingRef}--,AssignJobItems - end - args.no_job_delete=true - makeJob(args) -end -function AssignJobToBuild(args) - local bld=args.building or dfhack.buildings.findAtTile(args.pos) - args.building=bld - args.job_type=df.job_type.ConstructBuilding - if bld~=nil then - CheckAndFinishBuilding(args,bld) - else - bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true,building_filter=deon_filter}:show() - end - return true -end -function BuildLast(args) - local bld=dfhack.buildings.findAtTile(args.pos) - args.job_type=df.job_type.ConstructBuilding - if bld~=nil then - CheckAndFinishBuilding(args,bld) - else - --bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true}:show() - if last_building and last_building.type then - BuildingChosen(args,last_building.type,last_building.subtype,last_building.custom) - end - end - return true -end -function CancelJob(unit) - local c_job=unit.job.current_job - if c_job then - unit.job.current_job =nil --todo add real cancelation - for k,v in pairs(c_job.general_refs) do - if df.general_ref_unit_workerst:is_instance(v) then - v:delete() - c_job.general_refs:erase(k) - return - end - end - end -end -function ContinueJob(unit) - local c_job=unit.job.current_job - --no job to continue - if not c_job then return end - --reset suspends... - c_job.flags.suspend=false - for k,v in pairs(c_job.items) do --try fetching missing items - if v.is_fetching==1 then - unit.path.dest:assign(v.item.pos) - return - end - end - - --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! - addJobAction(c_job,unit) -end ---TODO: in far far future maybe add real linking? --- function assign_link_refs(args ) --- local job=args.job --- --job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=args.building.id}) --- job.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=args.triggertarget.id}) --- printall(job) --- end --- function assign_link_roles( args ) --- if #args.job.items~=2 then --- print("AAA FAILED!") --- return false --- end --- args.job.items[0].role=df.job_item_ref.T_role.LinkToTarget --- args.job.items[1].role=df.job_item_ref.T_role.LinkToTrigger --- end -function fake_linking(lever,building,slots) - local item1=slots[1].items[1] - local item2=slots[2].items[1] - if not dfhack.items.moveToBuilding(item1,lever,2) then - qerror("failed to move item to building") - end - if not dfhack.items.moveToBuilding(item2,building,2) then - qerror("failed to move item2 to building") - end - item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id}) - item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id}) - - lever.linked_mechanisms:insert("#",item2) - --fixes... - if building:getType()==df.building_type.Door then - building.door_flags.operated_by_mechanisms=true - end - - dfhack.gui.showAnnouncement("Linked!",COLOR_YELLOW,true) -end -function LinkBuilding(args) - local bld=args.building or dfhack.buildings.findAtTile(args.pos) - args.building=bld - - local lever_bld - if lever_id then --intentionally global! - lever_bld=df.building.find(lever_id) - if lever_bld==nil then - lever_id=nil - end - end - if lever_bld==nil then - if bld:getType()==df.building_type.Trap and bld:getSubtype()==df.trap_type.Lever then - lever_id=bld.id - dfhack.gui.showAnnouncement("Selected lever for linking",COLOR_YELLOW,true) - return - else - dfhack.gui.showAnnouncement("You first need a lever",COLOR_RED,true) - end - else - if lever_bld==bld then - dfhack.gui.showAnnouncement("Invalid target",COLOR_RED,true) --todo more invalid targets - return - end - -- args.job_type=df.job_type.LinkBuildingToTrigger - -- args.building=lever_bld - -- args.triggertarget=bld - -- args.pre_actions={ - -- dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.item_type.TRAPPARTS},{quantity=1,item_type=df.item_type.TRAPPARTS}}}), - -- AssignJobItems, - -- assign_link_refs,} - -- args.post_actions={AssignBuildingRef,assign_link_roles} - -- makeJob(args) - local input_filter_defaults = { --stolen from buildings lua to better customize... - item_type = df.item_type.TRAPPARTS, - item_subtype = -1, - mat_type = -1, - mat_index = -1, - flags1 = {}, - flags2 = { allow_artifact = true }, - flags3 = {}, - flags4 = 0, - flags5 = 0, - reaction_class = '', - has_material_reaction_product = '', - metal_ore = -1, - min_dimension = -1, - has_tool_use = -1, - quantity = 1 - } - local job_items={copyall(input_filter_defaults),copyall(input_filter_defaults)} - local its=EnumItems_with_settings(args) - local suitability=find_suitable_items(nil,its,job_items) - require('hack.scripts.gui.advfort_items').jobitemEditor{items=suitability,job_items=job_items,on_okay=dfhack.curry(fake_linking,lever_bld,bld)}:show() - lever_id=nil - end - --one item as LinkToTrigger role - --one item as LinkToTarget - --genref for holder(lever) - --genref for triggertarget - -end ---[[ Plant gathering attemped fix No. 35]] --[=[ still did not work!]=] -function get_design_block_ev(blk) - for i,v in ipairs(blk.block_events) do - if v:getType()==df.block_square_event_type.designation_priority then - return v - end - end -end -function PlantGatherFix(args) - local pos=args.pos - --[[args.job.flags[17]=false --?? - - - local block=dfhack.maps.getTileBlock(pos) - local ev=get_design_block_ev(block) - if ev==nil then - block.block_events:insert("#",{new=df.block_square_event_designation_priorityst}) - ev=block.block_events[#block.block_events-1] - end - ev.priority[pos.x % 16][pos.y % 16]=bit32.bor(ev.priority[pos.x % 16][pos.y % 16],4000) - - args.job.item_category:assign{furniture=true,corpses=true,ammo=true} --this is actually required in fort mode - ]] - local path=args.unit.path - path.dest=pos - path.goal=df.unit_path_goal.GatherPlant - path.path.x:insert("#",pos.x) - path.path.y:insert("#",pos.y) - path.path.z:insert("#",pos.z) - printall(path) -end -actions={ - {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, - {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, - {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, - {"CarveTrack" ,df.job_type.CarveTrack,{} --TODO: check this- carving modifies standing tile but depends on direction! - ,{SetCarveDir}}, - {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, - {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, - {"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, - {"CarveUpDownStaircase" ,df.job_type.CarveUpDownStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, - {"CarveRamp" ,df.job_type.CarveRamp,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, - {"DigChannel" ,df.job_type.DigChannel,{MakePredicateWieldsItem(df.job_skill.MINING)}}, - {"FellTree" ,df.job_type.FellTree,{MakePredicateWieldsItem(df.job_skill.AXE),IsTree}}, - {"Fish" ,df.job_type.Fish,{IsWater}}, - --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, - --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, - {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, - {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare},{PlantGatherFix}}, - {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, - {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, - {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, - --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, - {"Build" ,AssignJobToBuild,{NoConstructedBuilding}}, - {"BuildLast" ,BuildLast,{NoConstructedBuilding}}, - {"Clean" ,df.job_type.Clean,{}}, - {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, - {"Link Buildings" ,LinkBuilding,{IsBuilding}}, -} - -for id,action in pairs(actions) do - if action[1]==mode_name then - mode=id-1 - break - end -end -usetool=defclass(usetool,gui.Screen) -usetool.focus_path = 'advfort' -function usetool:getModeName() - local adv=df.global.world.units.active[0] - local ret - if adv.job.current_job then - ret= string.format("%s working(%d) ",(actions[(mode or 0)+1][1] or ""),adv.job.current_job.completion_timer) - else - ret= actions[(mode or 0)+1][1] or " " - end - if settings.quick then - ret=ret.."*" - end - return ret -end - -function usetool:update_site() - local site=inSite() - self.current_site=site - local site_label=self.subviews.siteLabel - if site then - - site_label:itemById("site").text=dfhack.TranslateName(site.name) - else - if settings.safe then - site_label:itemById("site").text="" - else - site_label:itemById("site").text="" - end - end -end - -function usetool:init(args) - self:addviews{ - wid.Label{ - view_id="mainLabel", - frame = {xalign=0,yalign=0}, - text={{key=keybinds.prevJob.key},{gap=1,text=self:callback("getModeName")},{gap=1,key=keybinds.nextJob.key}, - } - }, - - wid.Label{ - view_id="shopLabel", - frame = {l=35,xalign=0,yalign=0}, - visible=false, - text={ - {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{id="clutter"}} - }, - - wid.Label{ - view_id="siteLabel", - frame = {t=1,xalign=-1,yalign=0}, - text={ - {id="text1", text="Site:"},{id="site", text="name"} - } - } - } - local labors=df.global.world.units.active[0].status.labors - for i,v in ipairs(labors) do - labors[i]=true - end - self:update_site() -end -MOVEMENT_KEYS = { - A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, - A_CARE_MOVE_W = { -1, 0, 0 }, A_CARE_MOVE_E = { 1, 0, 0 }, - A_CARE_MOVE_NW = { -1, -1, 0 }, A_CARE_MOVE_NE = { 1, -1, 0 }, - A_CARE_MOVE_SW = { -1, 1, 0 }, A_CARE_MOVE_SE = { 1, 1, 0 }, - --[[A_MOVE_N = { 0, -1, 0 }, A_MOVE_S = { 0, 1, 0 }, - A_MOVE_W = { -1, 0, 0 }, A_MOVE_E = { 1, 0, 0 }, - A_MOVE_NW = { -1, -1, 0 }, A_MOVE_NE = { 1, -1, 0 }, - A_MOVE_SW = { -1, 1, 0 }, A_MOVE_SE = { 1, 1, 0 },--]] - A_CUSTOM_CTRL_D = { 0, 0, -1 }, - A_CUSTOM_CTRL_E = { 0, 0, 1 }, - CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, - A_MOVE_SAME_SQUARE={0,0,0}, - SELECT={0,0,0}, -} -ALLOWED_KEYS={ - A_MOVE_N=true,A_MOVE_S=true,A_MOVE_W=true,A_MOVE_E=true,A_MOVE_NW=true, - A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true,A_MOVE_DOWN_AUX=true, - A_MOVE_UP_AUX=true,A_LOOK=true,CURSOR_DOWN=true,CURSOR_UP=true,CURSOR_LEFT=true,CURSOR_RIGHT=true, - CURSOR_UPLEFT=true,CURSOR_UPRIGHT=true,CURSOR_DOWNLEFT=true,CURSOR_DOWNRIGHT=true,A_CLEAR_ANNOUNCEMENTS=true, - CURSOR_UP_Z=true,CURSOR_DOWN_Z=true, -} -function moddedpos(pos,delta) - return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} -end -function usetool:onHelp() - showHelp() -end -function setFiltersUp(specific,args) - local job=args.job - if specific.job_fields~=nil then - job:assign(specific.job_fields) - end - --printall(specific) - for _,v in ipairs(specific.items) do - --printall(v) - local filter=v - filter.new=true - job.job_items:insert("#",filter) - end - return true -end -function onWorkShopJobChosen(args,idx,choice) - args.pos=args.from_pos - args.building=args.building or dfhack.buildings.findAtTile(args.pos) - args.job_type=choice.job_id - args.post_actions={AssignBuildingRef} - args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} - makeJob(args) -end -function siegeWeaponActionChosen(args,actionid) - local building=args.building - if actionid==1 then --Turn - building.facing=(args.building.facing+1)%4 - return - elseif actionid==2 then --Load - local action=df.job_type.LoadBallista - if building:getSubtype()==df.siegeengine_type.Catapult then - action=df.job_type.LoadCatapult - args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1}}}),AssignJobItems} --TODO just boulders here - else - args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.SIEGEAMMO}}}),AssignJobItems} - end - args.job_type=action - args.unit=df.global.world.units.active[0] - local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} - args.from_pos=from_pos - args.pos=from_pos - elseif actionid==3 then --Fire - local action=df.job_type.FireBallista - if building:getSubtype()==df.siegeengine_type.Catapult then - action=df.job_type.FireCatapult - end - args.job_type=action - args.unit=df.global.world.units.active[0] - local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} - args.from_pos=from_pos - args.pos=from_pos - end - args.post_actions={AssignBuildingRef} - makeJob(args) -end -function putItemToBuilding(building,item) - if building:getType()==df.building_type.Table then - dfhack.items.moveToBuilding(item,building,0) - else - local container=building.contained_items[0].item --todo maybe iterate over all, add if usemode==2? - dfhack.items.moveToContainer(item,container) - end -end -function usetool:openPutWindow(building) - local adv=df.global.world.units.active[0] - local items=EnumItems{pos=adv.pos,unit=adv, - inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true, - [df.unit_inventory_item.T_mode.Weapon]=true,},deep=true} - local choices={} - for k,v in pairs(items) do - table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) - end - dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) -end -function usetool:openSiegeWindow(building) - local args={building=building,screen=self} - dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, - dfhack.curry(siegeWeaponActionChosen,args)) -end -function usetool:onWorkShopButtonClicked(building,index,choice) - local adv=df.global.world.units.active[0] - local args={unit=adv,building=building} - if df.interface_button_building_new_jobst:is_instance(choice.button) then - choice.button:click() - if #building.jobs>0 then - local job=building.jobs[#building.jobs-1] - args.job=job - args.pos=adv.pos - args.from_pos=adv.pos - args.pre_actions={AssignJobItems} - args.screen=self - makeJob(args) - end - elseif df.interface_button_building_category_selectorst:is_instance(choice.button) or - df.interface_button_building_material_selectorst:is_instance(choice.button) then - choice.button:click() - self:openShopWindowButtoned(building,true) - end -end - -function usetool:openShopWindowButtoned(building,no_reset) - self:setupFields() - local wui=df.global.ui_sidebar_menus.workshop_job - if not no_reset then - -- [[ manual reset incase the df-one does not exist? - wui:assign{category_id=-1,mat_type=-1,mat_index=-1} - for k,v in pairs(wui.material_category) do - wui.material_category[k]=false - end - --]] - --[[building:fillSidebarMenu() - if #wui.choices_all>0 then - wui.choices_all[#wui.choices_all-1]:click() - end - --]] - end - building:fillSidebarMenu() - - local list={} - for id,choice in pairs(wui.choices_visible) do - table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice}) - end - if #list ==0 and not no_reset then - print("Fallback") - self:openShopWindow(building) - return - --qerror("No jobs for this workshop") - end - dialog.showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,list,self:callback("onWorkShopButtonClicked",building) - ,nil, nil,true) -end -function usetool:openShopWindow(building) - local adv=df.global.world.units.active[0] - - local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) - if filter_pile then - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building - ,screen=self,bld=building,common=filter_pile.common} - choices={} - for k,v in pairs(filter_pile) do - table.insert(choices,{job_id=0,text=v.name:lower(),filter=v}) - end - dialog.showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) - ,nil, nil,true) - else - qerror("No jobs for this workshop") - end -end -function track_stop_configure(bld) --TODO: dedicated widget with nice interface and current setting display - local dump_choices={ - {text="no dumping"}, - {text="N",x=0,y=-1},--{t="NE",x=1,y=-1}, - {text="E",x=1,y=0},--{t="SE",x=1,y=1}, - {text="S",x=0,y=1},--{t="SW",x=-1,y=1}, - {text="W",x=-1,y=0},--{t="NW",x=-1,y=-1} - } - local choices={"Friction","Dumping"} - local function chosen(index,choice) - if choice.text=="Friction" then - dialog.showInputPrompt("Choose friction","Friction",nil,tostring(bld.friction),function ( txt ) - local num=tonumber(txt) --TODO allow only vanilla friction settings - if num then - bld.friction=num - end - end) - else - dialog.showListPrompt("Dumping direction", "Choose dumping:",COLOR_WHITE,dump_choices,function ( index,choice) - if choice.x then - bld.use_dump=1 --?? - bld.dump_x_shift=choice.x - bld.dump_y_shift=choice.y - else - bld.use_dump=0 - end - end) - end - end - dialog.showListPrompt("Track stop configure", "Choose what to change:",COLOR_WHITE,choices,chosen) -end -function usetool:armCleanTrap(building) - local adv=df.global.world.units.active[0] - --[[ - Lever, - PressurePlate, - CageTrap, - StoneFallTrap, - WeaponTrap, - TrackStop - --]] - if building.state==0 then - --CleanTrap - --[[ LoadCageTrap, - LoadStoneTrap, - LoadWeaponTrap, - ]] - if building.trap_type==df.trap_type.Lever then - --link - return - end - --building.trap_type==df.trap_type.PressurePlate then - --settings/link - local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos, - building=building,job_type=df.job_type.CleanTrap} - if building.trap_type==df.trap_type.CageTrap then - args.job_type=df.job_type.LoadCageTrap - local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} - - elseif building.trap_type==df.trap_type.StoneFallTrap then - args.job_type=df.job_type.LoadStoneTrap - local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} - elseif building.trap_type==df.trap_type.TrackStop then - --set dump and friction - track_stop_configure(building) - return - else - print("TODO: trap type:"..df.trap_type[building.trap_type]) - return - end - args.screen=self - makeJob(args) - end -end -function usetool:hiveActions(building) - local adv=df.global.world.units.active[0] - local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos, - from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self} - local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } - args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems} - makeJob(args) - --InstallColonyInHive, - --CollectHiveProducts, -end -function usetool:operatePump(building) - - local adv=df.global.world.units.active[0] - makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self} -end -function usetool:farmPlot(building) - local adv=df.global.world.units.active[0] - local do_harvest=false - for id, con_item in pairs(building.contained_items) do - if con_item.use_mode==2 and con_item.item:getType()==df.item_type.PLANT then - if same_xyz(adv.pos,con_item.item.pos) then - do_harvest=true - end - end - end - --check if there tile is without plantseeds,add job - - local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self} - if do_harvest then - args.job_type=df.job_type.HarvestPlants - args.post_actions={AssignBuildingRef} - else - local seedjob={items={{quantity=1,item_type=df.item_type.SEEDS}}} - args.job_type=df.job_type.PlantSeeds - args.pre_actions={dfhack.curry(setFiltersUp,seedjob)} - args.post_actions={AssignBuildingRef,AssignJobItems} - end - - makeJob(args) -end -function usetool:bedActions(building) - local adv=df.global.world.units.active[0] - local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self,building=building, - job_type=df.job_type.Sleep,post_actions={AssignBuildingRef}} - makeJob(args) -end -function usetool:chairActions(building) - local adv=df.global.world.units.active[0] - local eatjob={items={{quantity=1,item_type=df.item_type.FOOD}}} - local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self,job_type=df.job_type.Eat,building=building, - pre_actions={dfhack.curry(setFiltersUp,eatjob),AssignJobItems},post_actions={AssignBuildingRef}} - makeJob(args) -end -MODES={ - [df.building_type.Table]={ --todo filters... - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Coffin]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Box]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Weaponrack]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Armorstand]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Cabinet]={ - name="Put items", - input=usetool.openPutWindow, - }, - [df.building_type.Workshop]={ - name="Workshop menu", - input=usetool.openShopWindowButtoned, - }, - [df.building_type.Furnace]={ - name="Workshop menu", - input=usetool.openShopWindowButtoned, - }, - [df.building_type.SiegeEngine]={ - name="Siege menu", - input=usetool.openSiegeWindow, - }, - [df.building_type.FarmPlot]={ - name="Plant/Harvest", - input=usetool.farmPlot, - }, - [df.building_type.ScrewPump]={ - name="Operate Pump", - input=usetool.operatePump, - }, - [df.building_type.Trap]={ - name="Interact", - input=usetool.armCleanTrap, - }, - [df.building_type.Hive]={ - name="Hive actions", - input=usetool.hiveActions, - }, - [df.building_type.Bed]={ - name="Rest", - input=usetool.bedActions, - }, - [df.building_type.Chair]={ - name="Eat", - input=usetool.chairActions, - }, -} -function usetool:shopMode(enable,mode,building) - self.subviews.shopLabel.visible=enable - if mode then - self.subviews.shopLabel:itemById("text1").text=mode.name - if building:getClutterLevel()<=1 then - self.subviews.shopLabel:itemById("clutter").text="" - else - self.subviews.shopLabel:itemById("clutter").text=" Clutter:"..tostring(building:getClutterLevel()) - end - self.building=building - end - self.mode=mode -end -function usetool:shopInput(keys) - if keys[keybinds.workshop.key] then - self:openShopWindowButtoned(self.in_shop) - end -end -function usetool:wait_tick() - self:sendInputToParent("A_SHORT_WAIT") -end -function usetool:setupFields() - local adv=df.global.world.units.active[0] - local civ_id=df.global.world.units.active[0].civ_id - local ui=df.global.ui - ui.civ_id = civ_id - ui.main.fortress_entity=df.historical_entity.find(civ_id) - ui.race_id=adv.race - local nem=dfhack.units.getNemesis(adv) - if nem then - local links=nem.figure.entity_links - for _,link in ipairs(links) do - local hist_entity=df.historical_entity.find(link.entity_id) - if hist_entity and hist_entity.type==df.historical_entity_type.SiteGovernment then - ui.group_id=link.entity_id - break - end - end - end - local site= inSite() - if site then - ui.site_id=site.id - end -end -function usetool:siteCheck() - if self.site ~= nil or not settings.safe then --TODO: add check if it's correct site (the persistant ones) - return true - end - return false, "You are not on site" -end ---movement and co... Also passes on allowed keys -function usetool:fieldInput(keys) - local adv=df.global.world.units.active[0] - local cur_mode=actions[(mode or 0)+1] - local failed=false - for code,_ in pairs(keys) do - --print(code) - if MOVEMENT_KEYS[code] then - - local state={ - unit=adv, - pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]), - dir=MOVEMENT_KEYS[code], - from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}, - post_actions=cur_mode[4], - pre_actions=cur_mode[5], - job_type=cur_mode[2], - screen=self} - - if code=="SELECT" then --do job in the distance, TODO: check if you can still cheat-mine (and co.) remotely - if df.global.cursor.x~=-30000 then - state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} - else - break - end - end - - --First check site - local ok,msg=self:siteCheck() --TODO: some jobs might be possible without a site? - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - failed=true - else - for _,p in pairs(cur_mode[3] or {}) do --then check predicates - local ok,msg=p(state) - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - failed=true - end - end - end - - if not failed then - local ok,msg - if type(cur_mode[2])=="function" then - ok,msg=cur_mode[2](state) - else - makeJob(state) - --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) - - end - - if code=="SELECT" then - self:sendInputToParent("LEAVESCREEN") - end - self.long_wait=true - end - return code - end - if code~="_STRING" and code~="_MOUSE_L" and code~="_MOUSE_R" then - if ALLOWED_KEYS[code] then - self:sendInputToParent(code) - end - end - end - -end - -function usetool:onInput(keys) - - self:update_site() - - local adv=df.global.world.units.active[0] - - if keys.LEAVESCREEN then - if df.global.cursor.x~=-30000 then --if not poiting at anything - self:sendInputToParent("LEAVESCREEN") --leave poiting - else - self:dismiss() --leave the adv-tools all together - CancelJob(adv) - end - elseif keys[keybinds.nextJob.key] then --next job with looping - mode=(mode+1)%#actions - elseif keys[keybinds.prevJob.key] then --prev job with looping - mode=mode-1 - if mode<0 then mode=#actions-1 end - elseif keys["A_SHORT_WAIT"] then - --ContinueJob(adv) - self:sendInputToParent("A_SHORT_WAIT") - elseif keys[keybinds.quick.key] then - settings.quick=not settings.quick - elseif keys[keybinds.continue.key] then - --ContinueJob(adv) - --self:sendInputToParent("A_SHORT_WAIT") - self.long_wait=true - self.long_wait_timer=nil - else - if self.mode~=nil then - if keys[keybinds.workshop.key] then - self.mode.input(self,self.building) - end - self:fieldInput(keys) - else - self:fieldInput(keys) - end - end - -end -function usetool:cancel_wait() - self.long_wait_timer=nil - self.long_wait=false -end -function usetool:onIdle() - local adv=df.global.world.units.active[0] - local job_ptr=adv.job.current_job - local job_action=findAction(adv,df.unit_action_type.Job) - - --some heuristics for unsafe conditions - if self.long_wait and not settings.unsafe then --check if player wants for canceling to happen - local counters=adv.counters - local checked_counters={pain=true,winded=true,stunned=true,unconscious=true,suffocation=true,webbed=true,nausea=true,dizziness=true} - for k,v in pairs(checked_counters) do - if counters[k]>0 then - dfhack.gui.showAnnouncement("Job: canceled waiting because unsafe -"..k,5,1) - self:cancel_wait() - return - end - end - end - - if self.long_wait and self.long_wait_timer==nil then - self.long_wait_timer=1000 --TODO tweak this - end - - if job_ptr and self.long_wait and not job_action then - - if self.long_wait_timer<=0 then --fix deadlocks with force-canceling of waiting - self:cancel_wait() - return - else - self.long_wait_timer=self.long_wait_timer-1 - end - - if adv.job.current_job.completion_timer==-1 then - self.long_wait=false - end - ContinueJob(adv) - self:sendInputToParent("A_SHORT_WAIT") --todo continue till finished - end - self._native.parent:logic() -end -function usetool:isOnBuilding() - local adv=df.global.world.units.active[0] - local bld=dfhack.buildings.findAtTile(adv.pos) - if bld and MODES[bld:getType()]~=nil and bld:getBuildStage()==bld:getMaxBuildStage() then - return true,MODES[bld:getType()],bld - else - return false - end -end -function usetool:onRenderBody(dc) - self:shopMode(self:isOnBuilding()) - self:renderParent() -end -if not (dfhack.gui.getCurFocus()=="dungeonmode/Look" or dfhack.gui.getCurFocus()=="dungeonmode/Default") then - qerror("This script requires an adventurer mode with (l)ook or default mode.") -end -usetool():show() diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua deleted file mode 100644 index befb12ef5..000000000 --- a/scripts/gui/advfort_items.lua +++ /dev/null @@ -1,207 +0,0 @@ ---Does something with items in adventure mode jobs ---[[=begin - -gui/advfort_items -================= -Does something with items in adventure mode jobs. - -=end]] -local _ENV = mkmodule('hack.scripts.gui.advfort_items') - -local gui=require('gui') -local wid=require('gui.widgets') -local gscript=require('gui.script') - -jobitemEditor=defclass(jobitemEditor,gui.FramedScreen) -jobitemEditor.ATTRS{ - frame_style = gui.GREY_LINE_FRAME, - frame_inset = 1, - allow_add=false, - allow_remove=false, - allow_any_item=false, - job=DEFAULT_NIL, - job_items=DEFAULT_NIL, - items=DEFAULT_NIL, - on_okay=DEFAULT_NIL, - autofill=true, -} -function update_slot_text(slot) - local items="" - for i,v in ipairs(slot.items) do - items=items.." "..dfhack.items.getDescription(v,0) - if i~=#slot.items then - items=items.."," - end - end - - slot.text=string.format("%02d. Filled(%d/%d):%s",slot.id+1,slot.filled_amount,slot.job_item.quantity,items) -end ---items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref) -function jobitemEditor:init(args) - --self.job=args.job - if self.job==nil and self.job_items==nil then qerror("This screen must have job target or job_items list") end - if self.items==nil then qerror("This screen must have item list") end - - self:addviews{ - wid.Label{ - view_id = 'label', - text = args.prompt, - text_pen = args.text_pen, - frame = { l = 0, t = 0 }, - }, - wid.List{ - view_id = 'itemList', - frame = { l = 0, t = 2 ,b=2}, - }, - wid.Label{ - frame = { b=1,l=1}, - text ={{text= ": cancel", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - { - gap=3, - text= ": accept", - key = "SEC_SELECT", - on_activate= self:callback("commit"), - enabled=self:callback("jobValid") - }, - { - gap=3, - text= ": add", - key = "CUSTOM_A", - enabled=self:callback("can_add"), - on_activate= self:callback("add_item") - }, - { - gap=3, - text= ": remove", - key = "CUSTOM_R", - enabled=self:callback("can_remove"), - on_activate= self:callback("remove_item") - },} - }, - } - self.assigned={} - self:fill() - if self.autofill then - self:fill_slots() - end -end -function jobitemEditor:get_slot() - local idx,choice=self.subviews.itemList:getSelected() - return choice -end -function jobitemEditor:can_add() - local slot=self:get_slot() - return slot.filled_amount0 -end -function jobitemEditor:add_item() - local cur_slot=self:get_slot() - local choices={} - table.insert(choices,{text=""}) - for k,v in pairs(cur_slot.choices) do - if not self.assigned[v.id] then - table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) - end - end - gscript.start(function () - local _,_2,choice=gscript.showListPrompt("which item?", "Select item", COLOR_WHITE, choices) - if choice ~= nil and choice.item~=nil then - self:add_item_to_slot(cur_slot,choice.item) - end - end - ) -end -function jobitemEditor:fill_slots() - for i,v in ipairs(self.slots) do - while v.filled_amount`, which has not -been available since DF 0.34.11 - -See :bug:`1445` for more info about the patches. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' -local bp = require 'binpatch' - -AssignRack = defclass(AssignRack, guidm.MenuOverlay) - -AssignRack.focus_path = 'assign-rack' - -AssignRack.ATTRS { - building = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function list_squads(building,squad_table,squad_list) - local sqlist = building:getSquads() - if not sqlist then - return - end - - for i,v in ipairs(sqlist) do - local obj = df.squad.find(v.squad_id) - if obj then - if not squad_table[v.squad_id] then - squad_table[v.squad_id] = { id = v.squad_id, obj = obj } - table.insert(squad_list, squad_table[v.squad_id]) - end - - -- Set specific use flags - for n,ok in pairs(v.mode) do - if ok then - squad_table[v.squad_id][n] = true - end - end - - -- Check if any use is possible - local btype = building:getType() - if btype == df.building_type.Bed then - if v.mode.sleep then - squad_table[v.squad_id].any = true - end - elseif btype == df.building.Weaponrack then - if v.mode.train or v.mode.indiv_eq then - squad_table[v.squad_id].any = true - end - else - if v.mode.indiv_eq then - squad_table[v.squad_id].any = true - end - end - end - end - - for i,v in ipairs(building.parents) do - list_squads(v, squad_table, squad_list) - end -end - -function filter_invalid(list, id) - for i=#list-1,0,-1 do - local bld = df.building.find(list[i]) - if not bld or bld:getSpecificSquad() ~= id then - list:erase(i) - end - end -end - -function AssignRack:init(args) - self.squad_table = {} - self.squad_list = {} - list_squads(self.building, self.squad_table, self.squad_list) - table.sort(self.squad_list, function(a,b) return a.id < b.id end) - - self.choices = {} - for i,v in ipairs(self.squad_list) do - if v.any and (v.train or v.indiv_eq) then - local name = v.obj.alias - if name == '' then - name = dfhack.TranslateName(v.obj.name, true) - end - - filter_invalid(v.obj.rack_combat, v.id) - filter_invalid(v.obj.rack_training, v.id) - - table.insert(self.choices, { - icon = self:callback('isSelected', v), - icon_pen = COLOR_LIGHTGREEN, - obj = v, - text = { - name, NEWLINE, ' ', - { text = function() - return string.format('%d combat, %d training', #v.obj.rack_combat, #v.obj.rack_training) - end } - } - }) - end - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - 'Assign Weapon Rack' - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 2, b = 2 }, - icon_width = 2, row_height = 2, - scroll_keys = widgets.SECONDSCROLL, - choices = self.choices, - on_submit = self:callback('onSubmit'), - }, - widgets.Label{ - frame = { l = 0, t = 2 }, - text_pen = COLOR_LIGHTRED, - text = 'No appropriate barracks\n\nNote: weapon racks use the\nIndividual equipment flag', - visible = (#self.choices == 0), - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } - } - }, - } -end - -function AssignRack:isSelected(info) - if self.building.specific_squad == info.id then - return '\xfb' - else - return nil - end -end - -function AssignRack:onSubmit(idx, choice) - local rid = self.building.id - local curid = self.building.specific_squad - - local cur = df.squad.find(curid) - if cur then - utils.erase_sorted(cur.rack_combat, rid) - utils.erase_sorted(cur.rack_training, rid) - end - - self.building.specific_squad = -1 - df.global.ui.equipment.update.buildings = true - - local new = df.squad.find(choice.obj.id) - if new and choice.obj.id ~= curid then - self.building.specific_squad = choice.obj.id - - if choice.obj.indiv_eq then - utils.insert_sorted(new.rack_combat, rid) - end - if choice.obj.train then - utils.insert_sorted(new.rack_training, rid) - end - end -end - -function AssignRack:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - AssignRack.super.onInput(self, keys) - end -end - -if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some/Weaponrack' then - qerror("This script requires a weapon rack selected in the 'q' mode") -end - -AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show() - -if not already_patched then - local patch = bp.load_dif_file('weaponrack-unassign') - if patch and patch:isApplied() then - already_patched = true - end -end - -if not already_patched then - dlg.showMessage( - 'BUG ALERT', - { 'This script requires applying the binary patch', NEWLINE, - 'named weaponrack-unassign. Otherwise the game', NEWLINE, - 'will lose your settings due to a bug.' }, - COLOR_YELLOW - ) -end diff --git a/scripts/gui/autobutcher.lua b/scripts/gui/autobutcher.lua deleted file mode 100644 index 59f3733c1..000000000 --- a/scripts/gui/autobutcher.lua +++ /dev/null @@ -1,662 +0,0 @@ --- A GUI front-end for the autobutcher plugin. ---[[=begin - -gui/autobutcher -=============== -An in-game interface for `autobutcher`. - -=end]] -local gui = require 'gui' -local utils = require 'utils' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -local plugin = require 'plugins.zone' - -WatchList = defclass(WatchList, gui.FramedScreen) - -WatchList.ATTRS { - frame_title = 'Autobutcher Watchlist', - frame_inset = 0, -- cover full DF window - frame_background = COLOR_BLACK, - frame_style = gui.BOUNDARY_FRAME, -} - --- width of the race name column in the UI -local racewidth = 25 - -function nextAutowatchState() - if(plugin.autowatch_isEnabled()) then - return 'Stop ' - end - return 'Start' -end - -function nextAutobutcherState() - if(plugin.autobutcher_isEnabled()) then - return 'Stop ' - end - return 'Start' -end - -function getSleepTimer() - return plugin.autobutcher_getSleep() -end - -function setSleepTimer(ticks) - plugin.autobutcher_setSleep(ticks) -end - -function WatchList:init(args) - local colwidth = 7 - self:addviews{ - widgets.Panel{ - frame = { l = 0, r = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - text_pen = COLOR_CYAN, - text = { - { text = 'Race', width = racewidth }, ' ', - { text = 'female', width = colwidth }, ' ', - { text = ' male', width = colwidth }, ' ', - { text = 'Female', width = colwidth }, ' ', - { text = ' Male', width = colwidth }, ' ', - { text = 'watch? ' }, - { text = ' butchering' }, - NEWLINE, - { text = '', width = racewidth }, ' ', - { text = ' kids', width = colwidth }, ' ', - { text = ' kids', width = colwidth }, ' ', - { text = 'adults', width = colwidth }, ' ', - { text = 'adults', width = colwidth }, ' ', - { text = ' ' }, - { text = ' ordered' }, - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 3, b = 5 }, - not_found_label = 'Watchlist is empty.', - edit_pen = COLOR_LIGHTCYAN, - text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, - cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, - --on_select = self:callback('onSelectEntry'), - }, - widgets.Label{ - view_id = 'bottom_ui', - frame = { b = 0, h = 1 }, - text = 'filled by updateBottom()' - } - } - }, - } - - self:initListChoices() - self:updateBottom() -end - --- change the viewmode for stock data displayed in left section of columns -local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' } -local viewmode = 1 -function WatchList:onToggleView() - if viewmode < #viewmodes then - viewmode = viewmode + 1 - else - viewmode = 1 - end - self:initListChoices() - self:updateBottom() -end - --- update the bottom part of the UI (after sleep timer changed etc) -function WatchList:updateBottom() - self.subviews.bottom_ui:setText( - { - { key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max', - on_activate = self:callback('onToggleView') }, NEWLINE, - { key = 'CUSTOM_F', text = ': f kids', - on_activate = self:callback('onEditFK') }, ', ', - { key = 'CUSTOM_M', text = ': m kids', - on_activate = self:callback('onEditMK') }, ', ', - { key = 'CUSTOM_SHIFT_F', text = ': f adults', - on_activate = self:callback('onEditFA') }, ', ', - { key = 'CUSTOM_SHIFT_M', text = ': m adults', - on_activate = self:callback('onEditMA') }, '. ', - { key = 'CUSTOM_W', text = ': Toggle watch', - on_activate = self:callback('onToggleWatching') }, '. ', - { key = 'CUSTOM_X', text = ': Delete', - on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE, - --{ key = 'CUSTOM_A', text = ': Add race', - -- on_activate = self:callback('onAddRace') }, ', ', - { key = 'CUSTOM_SHIFT_R', text = ': Set whole row', - on_activate = self:callback('onSetRow') }, '. ', - { key = 'CUSTOM_B', text = ': Remove butcher orders', - on_activate = self:callback('onUnbutcherRace') }, '. ', - { key = 'CUSTOM_SHIFT_B', text = ': Butcher race', - on_activate = self:callback('onButcherRace') }, '. ', NEWLINE, - { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', - on_activate = self:callback('onToggleAutobutcher') }, '. ', - { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', - on_activate = self:callback('onToggleAutowatch') }, '. ', - { key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)', - on_activate = self:callback('onEditSleepTimer') }, '. ', - }) -end - -function stringify(number) - -- cap displayed number to 3 digits - -- after population of 50 per race is reached pets stop breeding anyways - -- so probably this could safely be reduced to 99 - local max = 999 - if number > max then number = max end - return tostring(number) -end - -function WatchList:initListChoices() - - local choices = {} - - -- first two rows are for "edit all races" and "edit new races" - local settings = plugin.autobutcher_getSettings() - local fk = stringify(settings.fk) - local fa = stringify(settings.fa) - local mk = stringify(settings.mk) - local ma = stringify(settings.ma) - - local watched = '' - - local colwidth = 7 - - table.insert (choices, { - text = { - { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = watched, width = 6, rjustify = true } - } - }) - - table.insert (choices, { - text = { - { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', - { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', - { text = watched, width = 6, rjustify = true } - } - }) - - local watchlist = plugin.autobutcher_getWatchList() - - for i,entry in ipairs(watchlist) do - fk = stringify(entry.fk) - fa = stringify(entry.fa) - mk = stringify(entry.mk) - ma = stringify(entry.ma) - if viewmode == 1 then - fkc = stringify(entry.fk_total) - fac = stringify(entry.fa_total) - mkc = stringify(entry.mk_total) - mac = stringify(entry.ma_total) - end - if viewmode == 2 then - fkc = stringify(entry.fk_protected) - fac = stringify(entry.fa_protected) - mkc = stringify(entry.mk_protected) - mac = stringify(entry.ma_protected) - end - if viewmode == 3 then - fkc = stringify(entry.fk_butcherable) - fac = stringify(entry.fa_butcherable) - mkc = stringify(entry.mk_butcherable) - mac = stringify(entry.ma_butcherable) - end - if viewmode == 4 then - fkc = stringify(entry.fk_butcherflag) - fac = stringify(entry.fa_butcherflag) - mkc = stringify(entry.mk_butcherflag) - mac = stringify(entry.ma_butcherflag) - end - local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag - local bo = ' ' - if butcher_ordered > 0 then bo = stringify(butcher_ordered) end - - local watched = 'no' - if entry.watched then watched = 'yes' end - - local racestr = entry.name - - -- highlight entries where the target quota can't be met because too many are protected - bad_pen = COLOR_LIGHTRED - good_pen = NONE -- this is stupid, but it works. sue me - fk_pen = good_pen - fa_pen = good_pen - mk_pen = good_pen - ma_pen = good_pen - if entry.fk_protected > entry.fk then fk_pen = bad_pen end - if entry.fa_protected > entry.fa then fa_pen = bad_pen end - if entry.mk_protected > entry.mk then mk_pen = bad_pen end - if entry.ma_protected > entry.ma then ma_pen = bad_pen end - - table.insert (choices, { - text = { - { text = racestr, width = racewidth, pad_char = ' ' }, --' ', - { text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ', - { text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ', - { text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ', - { text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/', - { text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ', - { text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ', - { text = bo, width = 8, rjustify = true, pad_char = ' ' } - }, - obj = entry, - }) - end - - local list = self.subviews.list - list:setChoices(choices) -end - -function WatchList:onInput(keys) - if keys.LEAVESCREEN then - self:dismiss() - else - WatchList.super.onInput(self, keys) - end -end - --- check the user input for target population values -function WatchList:checkUserInput(count, text) - if count == nil then - dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) - return false - end - if count < 0 then - dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) - return false - end - return true -end - --- check the user input for sleep timer -function WatchList:checkUserInputSleep(count, text) - if count == nil then - dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) - return false - end - if count < 1000 then - dlg.showMessage('Invalid Number', - 'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!', - COLOR_LIGHTRED) - return false - end - return true -end - -function WatchList:onEditFK() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of female kids:', - COLOR_WHITE, - ' '..fk, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - fk = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditMK() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of male kids:', - COLOR_WHITE, - ' '..mk, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - mk = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditFA() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of female adults:', - COLOR_WHITE, - ' '..fa, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - fa = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditMA() - local selidx,selobj = self.subviews.list:getSelected() - local settings = plugin.autobutcher_getSettings() - local fk = settings.fk - local mk = settings.mk - local fa = settings.fa - local ma = settings.ma - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - if selidx > 2 then - local entry = selobj.obj - fk = entry.fk - mk = entry.mk - fa = entry.fa - ma = entry.ma - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Race: '..race, - 'Enter desired maximum of male adults:', - COLOR_WHITE, - ' '..ma, - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - ma = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onEditSleepTimer() - local sleep = getSleepTimer() - dlg.showInputPrompt( - 'Edit Sleep Timer', - 'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)', - COLOR_WHITE, - ' '..sleep, - function(text) - local count = tonumber(text) - if self:checkUserInputSleep(count, text) then - sleep = count - setSleepTimer(sleep) - self:updateBottom() - end - end - ) -end - -function WatchList:onToggleWatching() - local selidx,selobj = self.subviews.list:getSelected() - if selidx > 2 then - local entry = selobj.obj - plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched) - end - self:initListChoices() -end - -function WatchList:onDeleteEntry() - local selidx,selobj = self.subviews.list:getSelected() - if(selidx < 3 or selobj == nil) then - return - end - dlg.showYesNoPrompt( - 'Delete from Watchlist', - 'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)', - COLOR_YELLOW, - function() - plugin.autobutcher_removeFromWatchList(selobj.obj.id) - self:initListChoices() - end - ) -end - -function WatchList:onAddRace() - print('onAddRace - not implemented yet') -end - -function WatchList:onUnbutcherRace() - local selidx,selobj = self.subviews.list:getSelected() - if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end - if selidx > 2 then - local entry = selobj.obj - local race = entry.name - plugin.autobutcher_unbutcherRace(entry.id) - self:initListChoices() - self:updateBottom() - end -end - -function WatchList:onButcherRace() - local selidx,selobj = self.subviews.list:getSelected() - if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end - if selidx > 2 then - local entry = selobj.obj - local race = entry.name - plugin.autobutcher_butcherRace(entry.id) - self:initListChoices() - self:updateBottom() - end -end - --- set whole row (fk, mk, fa, ma) to one value -function WatchList:onSetRow() - local selidx,selobj = self.subviews.list:getSelected() - local race = 'ALL RACES PLUS NEW' - local id = -1 - local watched = false - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - local watchindex = selidx - 3 - if selidx > 2 then - local entry = selobj.obj - race = entry.name - id = entry.id - watched = entry.watched - end - - dlg.showInputPrompt( - 'Set whole row for '..race, - 'Enter desired maximum for all subtypes:', - COLOR_WHITE, - ' ', - function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( count, count, count, count ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( count, count, count, count ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched) - end - self:initListChoices() - end - end - ) -end - -function WatchList:onToggleAutobutcher() - if(plugin.autobutcher_isEnabled()) then - plugin.autobutcher_setEnabled(false) - plugin.autobutcher_sortWatchList() - else - plugin.autobutcher_setEnabled(true) - end - self:initListChoices() - self:updateBottom() -end - -function WatchList:onToggleAutowatch() - if(plugin.autowatch_isEnabled()) then - plugin.autowatch_setEnabled(false) - else - plugin.autowatch_setEnabled(true) - end - self:initListChoices() - self:updateBottom() -end - -if not dfhack.isMapLoaded() then - qerror('Map is not loaded.') -end - -if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then - qerror("This script must not be called while other lua gui stuff is running.") -end - --- maybe this is too strict, there is not really a reason why it can only be called from the status screen --- (other than the hotkey might overlap with other scripts) -if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then - qerror("This script must either be called from the overall status screen or the animal list screen.") -end - - -local screen = WatchList{ } -screen:show() diff --git a/scripts/gui/choose-weapons.lua b/scripts/gui/choose-weapons.lua deleted file mode 100644 index 39e5fbf77..000000000 --- a/scripts/gui/choose-weapons.lua +++ /dev/null @@ -1,169 +0,0 @@ --- Rewrite individual choice weapons to specific types ---[[=begin - -gui/choose-weapons -================== -Bind to a key (the example config uses :kbd:`Ctrl`:kbd:`W`), and activate in the Equip->View/Customize -page of the military screen. - -Depending on the cursor location, it rewrites all 'individual choice weapon' entries -in the selected squad or position to use a specific weapon type matching the assigned -unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites -only that entry, and does it even if it is not 'individual choice'. - -Rationale: individual choice seems to be unreliable when there is a weapon shortage, -and may lead to inappropriate weapons being selected. - -=end]] -local utils = require 'utils' -local dlg = require 'gui.dialogs' - -local defs = df.global.world.raws.itemdefs -local entity = df.global.ui.main.fortress_entity -local tasks = df.global.ui.tasks -local equipment = df.global.ui.equipment - -function find_best_weapon(unit,mode) - local best = nil - local skill = nil - local skill_level = nil - local count = 0 - local function try(id,iskill) - local slevel = dfhack.units.getNominalSkill(unit,iskill) - -- Choose most skill - if (skill ~= nil and slevel > skill_level) - or (skill == nil and slevel > 0) then - best,skill,skill_level,count = id,iskill,slevel,0 - end - -- Then most produced within same skill - if skill == iskill then - local cnt = tasks.created_weapons[id] - if cnt > count then - best,count = id,cnt - end - end - end - for _,id in ipairs(entity.resources.weapon_type) do - local def = defs.weapons[id] - if def.skill_ranged >= 0 then - if mode == nil or mode == 'ranged' then - try(id, def.skill_ranged) - end - else - if mode == nil or mode == 'melee' then - try(id, def.skill_melee) - end - end - end - return best -end - -function unassign_wrong_items(unit,position,spec,subtype) - for i=#spec.assigned-1,0,-1 do - local id = spec.assigned[i] - local item = df.item.find(id) - - if item.subtype.subtype ~= subtype then - spec.assigned:erase(i) - - -- TODO: somewhat unexplored area; maybe missing some steps - utils.erase_sorted(position.assigned_items,id) - if utils.erase_sorted(equipment.items_assigned.WEAPON,item,'id') then - utils.insert_sorted(equipment.items_unassigned.WEAPON,item,'id') - end - equipment.update.weapon = true - unit.military.pickup_flags.update = true - end - end -end - -local count = 0 - -function adjust_uniform_spec(unit,position,spec,force) - if not unit then return end - local best - if spec.indiv_choice.melee then - best = find_best_weapon(unit, 'melee') - elseif spec.indiv_choice.ranged then - best = find_best_weapon(unit, 'ranged') - elseif spec.indiv_choice.any or force then - best = find_best_weapon(unit, nil) - end - if best then - count = count + 1 - spec.item_filter.item_subtype = best - spec.indiv_choice.any = false - spec.indiv_choice.melee = false - spec.indiv_choice.ranged = false - unassign_wrong_items(unit, position, spec, best) - end -end - -function adjust_position(unit,position,force) - if not unit then - local fig = df.historical_figure.find(position.occupant) - if not fig then return end - unit = df.unit.find(fig.unit_id) - end - - for _,v in ipairs(position.uniform.weapon) do - adjust_uniform_spec(unit, position, v, force) - end -end - -function adjust_squad(squad, force) - for _,pos in ipairs(squad.positions) do - adjust_position(nil, pos, force) - end -end - -local args = {...} -local vs = dfhack.gui.getCurViewscreen() -local vstype = df.viewscreen_layer_militaryst -if not vstype:is_instance(vs) then - qerror('Call this from the military screen') -end - -if vs.page == vstype.T_page.Equip -and vs.equip.mode == vstype.T_equip.T_mode.Customize then - local slist = vs.layer_objects[0] - local squad = vs.equip.squads[slist:getListCursor()] - - if slist.active then - print('Adjusting squad.') - adjust_squad(squad) - else - local plist = vs.layer_objects[1] - local pidx = plist:getListCursor() - local pos = squad.positions[pidx] - local unit = vs.equip.units[pidx] - - if plist.active then - print('Adjusting position.') - adjust_position(unit, pos) - elseif unit and vs.equip.edit_mode < 0 then - local wlist = vs.layer_objects[2] - local idx = wlist:getListCursor() - local cat = vs.equip.assigned.category[idx] - - if wlist.active and cat == df.uniform_category.weapon then - print('Adjusting spec.') - adjust_uniform_spec(unit, pos, vs.equip.assigned.spec[idx], true) - end - end - end -else - qerror('Call this from the Equip page of military screen') -end - -if count > 1 then - dlg.showMessage( - 'Choose Weapons', - 'Updated '..count..' uniform entries.', COLOR_GREEN - ) -elseif count == 0 then - dlg.showMessage( - 'Choose Weapons', - 'Did not find any entries to update.', COLOR_YELLOW - ) -end diff --git a/scripts/gui/clone-uniform.lua b/scripts/gui/clone-uniform.lua deleted file mode 100644 index e6ae2fa8a..000000000 --- a/scripts/gui/clone-uniform.lua +++ /dev/null @@ -1,58 +0,0 @@ --- Clone a uniform template in the military screen ---[[=begin - -gui/clone-uniform -================= -Bind to a key (the example config uses :kbd:`Ctrl`:kbd:`C`), and activate in the Uniforms -page of the military screen with the cursor in the leftmost list. - -When invoked, the script duplicates the currently selected uniform template, -and selects the newly created copy. - -=end]] -local utils = require 'utils' -local gui = require 'gui' - -local entity = df.global.ui.main.fortress_entity - -local args = {...} -local vs = dfhack.gui.getCurViewscreen() -local vstype = df.viewscreen_layer_militaryst -if not vstype:is_instance(vs) then - qerror('Call this from the military screen') -end - -local slist = vs.layer_objects[0] - -if vs.page == vstype.T_page.Uniforms -and slist.active and slist.num_entries > 0 -and not vs.equip.in_name_uniform -then - local idx = slist.num_entries - - if #vs.equip.uniforms ~= idx or #entity.uniforms ~= idx then - error('Uniform vector length mismatch') - end - - local uniform = vs.equip.uniforms[slist:getListCursor()] - - local ucopy = uniform:new() - ucopy.id = entity.next_uniform_id - ucopy.name = ucopy.name..'(Copy)' - - for k,v in ipairs(ucopy.uniform_item_info) do - for k2,v2 in ipairs(v) do - v[k2] = v2:new() - end - end - - entity.next_uniform_id = entity.next_uniform_id + 1 - entity.uniforms:insert('#',ucopy) - vs.equip.uniforms:insert('#',ucopy) - - slist.num_entries = idx+1 - slist.cursor = idx-1 - gui.simulateInput(vs, 'STANDARDSCROLL_DOWN') -else - qerror('Call this with a uniform selected on the Uniforms page of military screen') -end diff --git a/scripts/gui/companion-order.lua b/scripts/gui/companion-order.lua deleted file mode 100644 index 60a141d0f..000000000 --- a/scripts/gui/companion-order.lua +++ /dev/null @@ -1,490 +0,0 @@ --- Issue orders to companions in Adventure mode ---[[=begin - -gui/companion-order -=================== -A script to issue orders for companions. Select companions with lower case chars, issue orders with upper -case. Must be in look or talk mode to issue command on tile. - -.. image:: /docs/images/companion-order.png - -* move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you. -* equip - try to equip items on the ground. -* pick-up - try to take items into hand (also wield) -* unequip - remove and drop equipment -* unwield - drop held items -* wait - temporarily remove from party -* follow - rejoin the party after "wait" -* leave - remove from party (can be rejoined by talking) - -=end]] - -local gui = require 'gui' -local dlg = require 'gui.dialogs' -local args={...} -local is_cheat=(#args>0 and args[1]=="-c") -local cursor=xyz2pos(df.global.cursor.x,df.global.cursor.y,df.global.cursor.z) -local permited_equips={} - -permited_equips[df.item_backpackst]="UPPERBODY" -permited_equips[df.item_quiverst]="UPPERBODY" -permited_equips[df.item_flaskst]="UPPERBODY" -permited_equips[df.item_armorst]="UPPERBODY" -permited_equips[df.item_shoesst]="STANCE" -permited_equips[df.item_glovesst]="GRASP" -permited_equips[df.item_helmst]="HEAD" -permited_equips[df.item_pantsst]="LOWERBODY" -function DoesHaveSubtype(item) - if df.item_backpackst:is_instance(item) or df.item_flaskst:is_instance(item) or df.item_quiverst:is_instance(item) then - return false - end - return true -end -function CheckCursor(p) - if p.x==-30000 then - dlg.showMessage( - 'Companion orders', - 'You must have a cursor on some tile!', COLOR_LIGHTRED - ) - return false - end - return true -end -function getxyz() -- this will return pointers x,y and z coordinates. - local x=df.global.cursor.x - local y=df.global.cursor.y - local z=df.global.cursor.z - return x,y,z -- return the coords -end - -function GetCaste(race_id,caste_id) - local race=df.creature_raw.find(race_id) - return race.caste[caste_id] -end - -function EnumBodyEquipable(race_id,caste_id) - local caste=GetCaste(race_id,caste_id) - local bps=caste.body_info.body_parts - local ret={} - for k,v in pairs(bps) do - ret[k]={bp=v,layers={[0]={size=0,permit=0},[1]={size=0,permit=0},[2]={size=0,permit=0},[3]={size=0,permit=0} } } - end - return ret -end -function ReadCurrentEquiped(body_equip,unit) - for k,v in pairs(unit.inventory) do - if v.mode==2 then - local bpid=v.body_part_id - if DoesHaveSubtype(v.item) then - local sb=v.item.subtype.props - local trg=body_equip[bpid] - local trg_layer=trg.layers[sb.layer] - - if trg_layer.permit==0 then - trg_layer.permit=sb.layer_permit - else - if trg_layer.permit>sb.layer_permit then - trg_layer.permit=sb.layer_permit - end - end - trg_layer.size=trg_layer.size+sb.layer_size - end - end - end -end -function LayeringPermits(body_part,item) - if not DoesHaveSubtype(item) then - return true - end - local sb=item.subtype.props - local trg_layer=body_part.layers[sb.layer] - if math.min(trg_layer.permit ,sb.layer_permit)0 and #items>0 do - if(dfhack.items.moveToInventory(items[#items],v,1,grasps[#grasps])) then - table.remove(grasps) - end - table.remove(items) - end - local backpack=GetBackpack(v) - if backpack then - while #items>0 do - dfhack.items.moveToContainer(items[#items],backpack) - table.remove(items) - end - end - end - return true -end}, -{name="unequip",f=function (unit_list) - --remove and drop all the stuff (todo maybe a gui too?) - for k,v in pairs(unit_list) do - while #v.inventory ~=0 do - dfhack.items.moveToGround(v.inventory[0].item,v.pos) - end - end - return true -end}, -{name="unwield",f=function (unit_list) - - for k,v in pairs(unit_list) do - local wep_count=0 - for _,it in pairs(v.inventory) do - if it.mode==1 then - wep_count=wep_count+1 - end - end - for i=1,wep_count do - for _,it in pairs(v.inventory) do - if it.mode==1 then - dfhack.items.moveToGround(it.item,v.pos) - break - end - end - end - end - return true -end}, ---[=[ -{name="roam not working :<",f=function (unit_list,pos,dist) --does not work - if not CheckCursor(pos) then - return false - end - dist=dist or 5 - for k,v in pairs(unit_list) do - v.idle_area:assign(pos) - v.idle_area_threshold=dist - end - return true -end}, ---]=] -{name="wait",f=function (unit_list) - for k,v in pairs(unit_list) do - v.relations.group_leader_id=-1 - end - return true -end}, -{name="follow",f=function (unit_list) - local adv=df.global.world.units.active[0] - for k,v in pairs(unit_list) do - v.relations.group_leader_id=adv.id - end - return true -end}, -{name="leave",f=function (unit_list) - local adv=df.global.world.units.active[0] - local t_nem=dfhack.units.getNemesis(adv) - for k,v in pairs(unit_list) do - - v.relations.group_leader_id=-1 - local u_nem=dfhack.units.getNemesis(v) - if u_nem then - u_nem.group_leader_id=-1 - end - if t_nem and u_nem then - for k,v in pairs(t_nem.companions) do - if v==u_nem.id then - t_nem.companions:erase(k) - break - end - end - end - end - return true -end}, - -} -local cheats={ -{name="Patch up",f=function (unit_list) - local dft=require("plugins.dfusion.tools") - for k,v in pairs(unit_list) do - dft.healunit(v) - end - return true -end}, -{name="Power up",f=function (unit_list) - local dft=require("plugins.dfusion.tools") - for k,d in pairs(unit_list) do - dft.powerup(d) - end - return true -end}, -{name="get in",f=function (unit_list,pos) - if not CheckCursor(pos) then - return false - end - adv=df.global.world.units.active[0] - item=getItemsAtPos(getxyz())[1] - print(item.id) - for k,v in pairs(unit_list) do - v.riding_item_id=item.id - local ref=df.general_ref_unit_riderst:new() - ref.unit_id=v.id - item.general_refs:insert("#",ref) - end - return true -end}, -} ---[[ todo: add cheats...]]-- -function getCompanions(unit) - unit=unit or df.global.world.units.active[0] - local t_nem=dfhack.units.getNemesis(unit) - if t_nem==nil then - qerror("Invalid unit! No nemesis record") - end - local ret={} - for k,v in pairs(t_nem.companions) do - local u=df.nemesis_record.find(v) - if u.unit then - table.insert(ret,u.unit) - end - end - return ret -end - - -CompanionUi=defclass(CompanionUi,gui.FramedScreen) -CompanionUi.ATTRS{ - frame_title = "Companions", -} -function CompanionUi:init(args) - self.unit_list=args.unit_list - self.selected={} - for i=0,26 do - self.selected[i]=true - end -end -function CompanionUi:GetSelectedUnits() - local ret={} - for k,v in pairs(self.unit_list) do - if self.selected[k] then - table.insert(ret,v) - end - end - return ret -end -function CompanionUi:onInput(keys) - - - if keys.LEAVESCREEN then - self:dismiss() - elseif keys._STRING then - local s=keys._STRING - if s==string.byte('*') then - local v=self.selected[1] or false - for i=0,26 do - - self.selected[i]=not v - end - end - if s>=string.byte('a') and s<=string.byte('z') then - local idx=s-string.byte('a')+1 - if self.selected[idx] then - self.selected[idx]=false - else - self.selected[idx]=true - end - end - if s>=string.byte('A') and s<=string.byte('Z') then - local idx=s-string.byte('A')+1 - if orders[idx] and orders[idx].f then - if orders[idx].f(self:GetSelectedUnits(),cursor) then - self:dismiss() - end - end - if is_cheat then - idx=idx-#orders - if cheats[idx] and cheats[idx].f then - if cheats[idx].f(self:GetSelectedUnits(),cursor) then - self:dismiss() - end - end - end - end - end -end -function CompanionUi:onRenderBody( dc) - --list widget goes here... - local char_a=string.byte('a')-1 - dc:newline(1):string("*. All") - for k,v in ipairs(self.unit_list) do - if self.selected[k] then - dc:pen(COLOR_GREEN) - else - dc:pen(COLOR_GREY) - end - dc:newline(1):string(string.char(k+char_a)..". "):string(dfhack.TranslateName(v.name)); - end - dc:pen(COLOR_GREY) - local w,h=self:getWindowSize() - local char_A=string.byte('A')-1 - for k,v in ipairs(orders) do - dc:seek(w/2,k):string(string.char(k+char_A)..". "):string(v.name); - end - if is_cheat then - for k,v in ipairs(cheats) do - dc:seek(w/2,k+#orders):string(string.char(k+#orders+char_A)..". "):string(v.name); - end - end -end -local screen=CompanionUi{unit_list=getCompanions()} -screen:show() \ No newline at end of file diff --git a/scripts/gui/confirm-opts.lua b/scripts/gui/confirm-opts.lua deleted file mode 100644 index d2aba2a94..000000000 --- a/scripts/gui/confirm-opts.lua +++ /dev/null @@ -1,74 +0,0 @@ --- confirm plugin options ---[[=begin - -gui/confirm-opts -================ -A basic configuration interface for the `confirm` plugin. - -=end]] - - -confirm = require 'plugins.confirm' -gui = require 'gui' - -Opts = defclass(Opts, gui.FramedScreen) -Opts.ATTRS = { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'Confirmation dialogs', - frame_width = 32, - frame_height = 20, - frame_inset = 1, - focus_path = 'confirm/opts', -} - -function Opts:init() - self:refresh() - self.cursor = 1 - local active_id = confirm.get_active_id() - for i, c in pairs(self.data) do - if c.id == active_id then - self.cursor = i - break - end - end -end - -function Opts:refresh() - self.data = confirm.get_conf_data() - self.frame_height = #self.data -end - -function Opts:onRenderBody(p) - for i, c in pairs(self.data) do - local highlight = (i == self.cursor and 8 or 0) - p:pen(COLOR_GREY + highlight) - p:string(c.id .. ': ') - p:pen((c.enabled and COLOR_GREEN or COLOR_RED) + highlight) - p:string(c.enabled and 'Enabled' or 'Disabled') - p:newline() - end -end - -function Opts:onInput(keys) - local conf = self.data[self.cursor] - if keys.LEAVESCREEN then - self:dismiss() - elseif keys.SELECT then - confirm.set_conf_state(conf.id, not conf.enabled) - self:refresh() - elseif keys.SEC_SELECT then - for _, c in pairs(self.data) do - confirm.set_conf_state(c.id, not conf.enabled) - end - self:refresh() - elseif keys.STANDARDSCROLL_UP or keys.STANDARDSCROLL_DOWN then - self.cursor = self.cursor + (keys.STANDARDSCROLL_UP and -1 or 1) - if self.cursor < 1 then - self.cursor = #self.data - elseif self.cursor > #self.data then - self.cursor = 1 - end - end -end - -Opts():show() diff --git a/scripts/gui/create-item.lua b/scripts/gui/create-item.lua deleted file mode 100644 index a2c38247a..000000000 --- a/scripts/gui/create-item.lua +++ /dev/null @@ -1,260 +0,0 @@ --- create-item.lua --- A gui-based item creation script. --- author Putnam --- edited by expwnent - ---@module = true ---[[=begin - -gui/create-item -=============== -A graphical interface for creating items. - -=end]] -local function getGenderString(gender) - local genderStr - if gender==0 then - genderStr=string.char(12) - elseif gender==1 then - genderStr=string.char(11) - else - return "" - end - return string.char(40)..genderStr..string.char(41) -end - -local function getCreatureList() - local crList={} - for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do - for kk,ca in ipairs(cr.caste) do - local str=ca.caste_name[0] - str=str..' '..getGenderString(ca.gender) - table.insert(crList,{str,nil,ca}) - end - end - return crList -end - -local function getRestrictiveMatFilter(itemType) - if not args.restrictive then return nil end - local itemTypes={ - WEAPON=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED) - end, - AMMO=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_AMMO) - end, - ARMOR=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_ARMOR) - end, - INSTRUMENT=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_HARD) - end, - AMULET=function(mat,parent,typ,idx) - return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD) - end, - ROCK=function(mat,parent,typ,idx) - return (mat.flags.IS_STONE) - end, - BOULDER=ROCK, - BAR=function(mat,parent,typ,idx) - return (mat.flags.IS_METAL or mat.flags.SOAP or mat.id==COAL) - end - - } - for k,v in ipairs({'GOBLET','FLASK','TOY','RING','CROWN','SCEPTER','FIGURINE','TOOL'}) do - itemTypes[v]=itemTypes.INSTRUMENT - end - for k,v in ipairs({'SHOES','SHIELD','HELM','GLOVES'}) do - itemTypes[v]=itemTypes.ARMOR - end - for k,v in ipairs({'EARRING','BRACELET'}) do - itemTypes[v]=itemTypes.AMULET - end - itemTypes.BOULDER=itemTypes.ROCK - return itemTypes[df.item_type[itemType]] -end - -local function getMatFilter(itemtype) - local itemTypes={ - SEEDS=function(mat,parent,typ,idx) - return mat.flags.SEED_MAT - end, - PLANT=function(mat,parent,typ,idx) - return mat.flags.STRUCTURAL_PLANT_MAT - end, - LEAVES=function(mat,parent,typ,idx) - return mat.flags.LEAF_MAT - end, - MEAT=function(mat,parent,typ,idx) - return mat.flags.MEAT - end, - CHEESE=function(mat,parent,typ,idx) - return (mat.flags.CHEESE_PLANT or mat.flags.CHEESE_CREATURE) - end, - LIQUID_MISC=function(mat,parent,typ,idx) - return (mat.flags.LIQUID_MISC_PLANT or mat.flags.LIQUID_MISC_CREATURE or mat.flags.LIQUID_MISC_OTHER) - end, - POWDER_MISC=function(mat,parent,typ,idx) - return (mat.flags.POWDER_MISC_PLANT or mat.flags.POWDER_MISC_CREATURE) - end, - DRINK=function(mat,parent,typ,idx) - return (mat.flags.ALCOHOL_PLANT or mat.flags.ALCOHOL_CREATURE) - end, - GLOB=function(mat,parent,typ,idx) - return (mat.flags.STOCKPILE_GLOB) - end, - WOOD=function(mat,parent,typ,idx) - return (mat.flags.WOOD) - end, - THREAD=function(mat,parent,typ,idx) - return (mat.flags.THREAD_PLANT) - end, - LEATHER=function(mat,parent,typ,idx) - return (mat.flags.LEATHER) - end - } - return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype) -end - -local function createItem(mat,itemType,quality,creator,description,amount) - local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)) - assert(item, 'failed to create item') - quality = math.max(0, math.min(5, quality - 1)) - item:setQuality(quality) - if df.item_type[itemType[1]]=='SLAB' then - item.description=description - end - if tonumber(amount) > 1 then - item:setStackSize(amount) - end -end - -local function qualityTable() - return {{'None'}, - {'-Well-crafted-'}, - {'+Finely-crafted+'}, - {'*Superior*'}, - {string.char(240)..'Exceptional'..string.char(240)}, - {string.char(15)..'Masterwork'..string.char(15)} - } -end - -local script=require('gui.script') - -local function showItemPrompt(text,item_filter,hide_none) - require('gui.materials').ItemTypeDialog{ - prompt=text, - item_filter=item_filter, - hide_none=hide_none, - on_select=script.mkresume(true), - on_cancel=script.mkresume(false), - on_close=script.qresume(nil) - }:show() - - return script.wait() -end - -local function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) --the one included with DFHack doesn't have a filter or the inorganic, creature, plant things available - require('gui.materials').MaterialDialog{ - frame_title = title, - prompt = prompt, - mat_filter = filter, - use_inorganic = inorganic, - use_creature = creature, - use_plant = plant, - on_select = script.mkresume(true), - on_cancel = script.mkresume(false), - on_close = script.qresume(nil) - }:show() - - return script.wait() -end - -local function usesCreature(itemtype) - typesThatUseCreatures={REMAINS=true,FISH=true,FISH_RAW=true,VERMIN=true,PET=true,EGG=true,CORPSE=true,CORPSEPIECE=true} - return typesThatUseCreatures[df.item_type[itemtype]] -end - -local function getCreatureRaceAndCaste(caste) - return df.global.world.raws.creatures.list_creature[caste.index],df.global.world.raws.creatures.list_caste[caste.index] -end - -function hackWish(unit) - script.start(function() - local amountok, amount - local matok,mattype,matindex,matFilter - local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true) - if not itemok then return end - if not args.notRestrictive then - matFilter=getMatFilter(itemtype) - end - if not usesCreature(itemtype) then - matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter) - if not matok then return end - else - local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList()) - if not creatureok then return end - mattype,matindex=getCreatureRaceAndCaste(creatureTable[3]) - end - local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable()) - if not qualityok then return end - local description - if df.item_type[itemtype]=='SLAB' then - local descriptionok - descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE) - if not descriptionok then return end - end - if args.multi then - repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount) or not amountok - if not amountok then return end - if mattype and itemtype then - if df.item_type.attrs[itemtype].is_stackable then - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,amount) - else - for i=1,amount do - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1) - end - end - return true - end - return false - else - if mattype and itemtype then - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1) - return true - end - return false - end - end) -end - -scriptArgs={...} - -utils=require('utils') - -validArgs = validArgs or utils.invert({ - 'startup', - 'all', - 'restrictive', - 'unit', - 'multi' -}) - -args = utils.processArgs({...}, validArgs) - -eventful=require('plugins.eventful') - -if not args.startup then - local unit=args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) - if unit then - hackWish(unit) - else - qerror('A unit needs to be selected to use gui/create-item.') - end -else - eventful.onReactionComplete.hackWishP=function(reaction,unit,input_items,input_reagents,output_items,call_native) - if not reaction.code:find('DFHACK_WISH') then return nil end - hackWish(unit) - end -end diff --git a/scripts/gui/dfstatus.lua b/scripts/gui/dfstatus.lua deleted file mode 100644 index 9789affbc..000000000 --- a/scripts/gui/dfstatus.lua +++ /dev/null @@ -1,229 +0,0 @@ --- a quick access status screen --- originally written by enjia2000@gmail.com (stolencatkarma) - ---[[=begin - -gui/dfstatus -============ -Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars. -Sections can be enabled/disabled/configured by editing ``dfhack-config/dfstatus.lua``. - -=end]] -local gui = require 'gui' - -function warn(msg) - dfhack.color(COLOR_LIGHTRED) - print(msg) - dfhack.color(nil) -end - -config = { - flags = { - drink = true, - wood = true, - fuel = true, - prepared_meals = true, - tanned_hides = true, - cloth = true, - metals = true, - }, - metal_ids = {}, -} - -function parse_config() - local metal_map = {} - for id, raw in pairs(df.global.world.raws.inorganics) do - if raw.material.flags.IS_METAL then - metal_map[raw.id:upper()] = id - metal_map[id] = raw.id:upper() - end - end - - local function add_metal(...) - for _, m in pairs({...}) do - id = metal_map[tostring(m):upper()] - if id ~= nil then - table.insert(config.metal_ids, id) - elseif m == '-' then - table.insert(config.metal_ids, '-') - else - warn('Invalid metal: ' .. tostring(m)) - end - end - return add_metal - end - - local env = {} - setmetatable(env, { - __index = function(_, k) - if k == 'metal' or k == 'metals' then - return add_metal - elseif k == 'flags' then - return config.flags - else - error('unknown name: ' .. k, 2) - end - end, - __newindex = function(_, k, v) - if config.flags[k] ~= nil then - if v ~= nil then - config.flags[k] = v - else - config.flags[k] = false - end - else - error('unknown flag: ' .. k, 2) - end - end, - }) - local f, err = loadfile('dfhack-config/dfstatus.lua', 't', env) - if not f then - qerror('error loading config: ' .. err) - end - local ok, err = pcall(f) - if not ok then - qerror('error parsing config: ' .. err) - end -end - -function getInorganicName(id) - return (df.inorganic_raw.find(id).material.state_name.Solid:gsub('^[a-z]', string.upper)) -end - -dfstatus = defclass(dfstatus, gui.FramedScreen) -dfstatus.ATTRS = { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'dfstatus', - frame_width = 16, - frame_height = 17, - frame_inset = 1, - focus_path = 'dfstatus', -} - -function dfstatus:init() - self.text = {} - self.start = 1 - local function write(line) - table.insert(self.text, line) - -- ensure that the window is wide enough for this line plus a scroll arrow - if #line + 1 > self.frame_width then - self.frame_width = #line + 1 - end - end - local function newline() write('') end - local f = config.flags - - local drink = 0 - local wood = 0 - local fuel = 0 - - local prepared_meals = 0 - local tanned_hides = 0 - local cloth = 0 - - local metals = {} - for _, id in pairs(config.metal_ids) do - metals[id] = 0 - end - - for _, item in ipairs(df.global.world.items.all) do - if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then - if item:getType() == df.item_type.WOOD then - wood = wood + item:getStackSize() - elseif item:getType() == df.item_type.DRINK then - drink = drink + item:getStackSize() - elseif item:getType() == df.item_type.SKIN_TANNED then - tanned_hides = tanned_hides + item:getStackSize() - elseif item:getType() == df.item_type.CLOTH then - cloth = cloth + item:getStackSize() - elseif item:getType() == df.item_type.FOOD then - prepared_meals = prepared_meals + item:getStackSize() - elseif item:getType() == df.item_type.BAR then - if item:getMaterial() == df.builtin_mats.COAL then - fuel = fuel + item:getStackSize() - elseif item:getMaterial() == df.builtin_mats.INORGANIC then - local mat_idx = item:getMaterialIndex() - if metals[mat_idx] ~= nil then - metals[mat_idx] = metals[mat_idx] + item:getStackSize() - end - end - end - end - end - if f.drink then - write("Drinks: " .. drink) - end - if f.prepared_meals then - write("Meals: " .. prepared_meals) - end - if f.drink or f.prepared_meals then - newline() - end - if f.wood then - write("Wood: " .. wood) - end - if f.fuel then - write("Fuel: " .. fuel) - end - if f.wood or f.fuel then - newline() - end - if f.tanned_hides then - write("Hides: " .. tanned_hides) - end - if f.cloth then - write("Cloth: " .. cloth) - end - if f.tanned_hides or f.cloth then - newline() - end - if f.metals then - write("Metal bars:") - for _, id in pairs(config.metal_ids) do - if id == '-' then - newline() - else - write(' ' .. ('%-10s'):format(getInorganicName(id) .. ': ') .. metals[id]) - end - end - end - self.start_min = 1 - self.start_max = #self.text - self.frame_height + 1 -end - -function dfstatus:onRenderBody(dc) - dc:pen(COLOR_LIGHTGREEN) - for id, line in pairs(self.text) do - if id >= self.start then - dc:string(line):newline() - end - end - dc:pen(COLOR_LIGHTCYAN) - if self.start > self.start_min then - dc:seek(self.frame_width - 1, 0):char(24) - end - if self.start < self.start_max then - dc:seek(self.frame_width - 1, self.frame_height - 1):char(25) - end -end - -function dfstatus:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - scr = nil - elseif keys.STANDARDSCROLL_UP then - self.start = math.max(self.start - 1, self.start_min) - elseif keys.STANDARDSCROLL_DOWN then - self.start = math.min(self.start + 1, self.start_max) - end -end - -if not scr then - parse_config() - scr = dfstatus() - scr:show() -else - scr:dismiss() - scr = nil -end - diff --git a/scripts/gui/family-affairs.lua b/scripts/gui/family-affairs.lua deleted file mode 100644 index e3806e9f2..000000000 --- a/scripts/gui/family-affairs.lua +++ /dev/null @@ -1,292 +0,0 @@ --- gui/family-affairs --- derived from v1.2 @ http://www.bay12forums.com/smf/index.php?topic=147779 -local help = [[=begin - -gui/family-affairs -================== -A user-friendly interface to view romantic relationships, -with the ability to add, remove, or otherwise change them at -your whim - fantastic for depressed dwarves with a dead spouse -(or matchmaking players...). - -The target/s must be alive, sane, and in fortress mode. - -.. image:: /docs/images/family-affairs.png - :align: center - -``gui/family-affairs [unitID]`` - shows GUI for the selected unit, or the specified unit ID - -``gui/family-affairs divorce [unitID]`` - removes all spouse and lover information from the unit - and it's partner, bypassing almost all checks. - -``gui/family-affairs [unitID] [unitID]`` - divorces the two specificed units and their partners, - then arranges for the two units to marry, bypassing - almost all checks. Use with caution. - -=end]] - -helpstr = help:gsub('=begin', ''):gsub('=end', '') - -local dlg = require ('gui.dialogs') - -function ErrorPopup (msg,color) - if not tostring(msg) then msg = "Error" end - if not color then color = COLOR_LIGHTRED end - dlg.showMessage("Dwarven Family Affairs", msg, color, nil) -end - -function AnnounceAndGamelog(text) - dfhack.gui.showAnnouncement(text, COLOR_LIGHTMAGENTA) -end - -function ListPrompt (msg, choicelist, bool, yes_func) -dlg.showListPrompt( - "Dwarven Family Affairs", - msg, - COLOR_WHITE, - choicelist, - --called if choice is yes - yes_func, - --called on cancel - function() end, - 15, - bool - ) -end - -function GetMarriageSummary (source) - local familystate = "" - - if source.relations.spouse_id ~= -1 then - if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) then - familystate = dfhack.TranslateName(source.name).." has a spouse ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")" - end - if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) == false then - familystate = dfhack.TranslateName(source.name).."'s spouse is dead or not sane, would you like to choose a new one?" - end - end - - if source.relations.spouse_id == -1 and source.relations.lover_id ~= -1 then - if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) then - familystate = dfhack.TranslateName(source.name).." already has a lover ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")" - end - if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) == false then - familystate = dfhack.TranslateName(source.name).."'s lover is dead or not sane, would you like that love forgotten?" - end - end - - if source.relations.spouse_id == -1 and source.relations.lover_id == -1 then - familystate = dfhack.TranslateName(source.name).." is not involved in romantic relationships with anyone" - end - - if source.relations.pregnancy_timer > 0 then - familystate = familystate.."\nShe is pregnant." - local father = df.historical_figure.find(source.relations.pregnancy_spouse) - if father then - familystate = familystate.." The father is "..dfhack.TranslateName(father.name).."." - end - end - - return familystate -end - -function GetSpouseData (source) - local spouse = df.unit.find(source.relations.spouse_id) - local spouse_hf - if spouse then - spouse_hf = df.historical_figure.find (spouse.hist_figure_id) - end - return spouse,spouse_hf -end - -function GetLoverData (source) - local lover = df.unit.find(source.relations.spouse_id) - local lover_hf - if lover then - lover_hf = df.historical_figure.find (lover.hist_figure_id) - end - return lover,lover_hf -end - -function EraseHFLinksLoverSpouse (hf) - for i = #hf.histfig_links-1,0,-1 do - if hf.histfig_links[i]._type == df.histfig_hf_link_spousest or hf.histfig_links[i]._type == df.histfig_hf_link_loverst then - local todelete = hf.histfig_links[i] - hf.histfig_links:erase(i) - todelete:delete() - end - end -end - -function Divorce (source) - local source_hf = df.historical_figure.find(source.hist_figure_id) - local spouse,spouse_hf = GetSpouseData (source) - local lover,lover_hf = GetLoverData (source) - - source.relations.spouse_id = -1 - source.relations.lover_id = -1 - - if source_hf then - EraseHFLinksLoverSpouse (source_hf) - end - if spouse then - spouse.relations.spouse_id = -1 - spouse.relations.lover_id = -1 - end - if lover then - spouse.relations.spouse_id = -1 - spouse.relations.lover_id = -1 - end - if spouse_hf then - EraseHFLinksLoverSpouse (spouse_hf) - end - if lover_hf then - EraseHFLinksLoverSpouse (lover_hf) - end - - local partner = spouse or lover - if not partner then - AnnounceAndGamelog(dfhack.TranslateName(source.name).." is now single") - else - AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(partner.name).." are now single") - end -end - -function Marriage (source,target) - local source_hf = df.historical_figure.find(source.hist_figure_id) - local target_hf = df.historical_figure.find(target.hist_figure_id) - source.relations.spouse_id = target.id - target.relations.spouse_id = source.id - - local new_link = df.histfig_hf_link_spousest:new() -- adding hf link to source - new_link.target_hf = target_hf.id - new_link.link_strength = 100 - source_hf.histfig_links:insert('#',new_link) - - new_link = df.histfig_hf_link_spousest:new() -- adding hf link to target - new_link.target_hf = source_hf.id - new_link.link_strength = 100 - target_hf.histfig_links:insert('#',new_link) -end - -function ChooseNewSpouse (source) - - if not source then - qerror("no unit") return - end - if not dfhack.units.isAdult(source) then - ErrorPopup("target is too young") return - end - if not (source.relations.spouse_id == -1 and source.relations.lover_id == -1) then - ErrorPopup("target already has a spouse or a lover") - qerror("source already has a spouse or a lover") - return - end - - local choicelist = {} - targetlist = {} - - for k,v in pairs (df.global.world.units.active) do - if dfhack.units.isCitizen(v) - and v.race == source.race - and v.sex ~= source.sex - and v.relations.spouse_id == -1 - and v.relations.lover_id == -1 - and dfhack.units.isAdult(v) - then - table.insert(choicelist,dfhack.TranslateName(v.name)..', '..dfhack.units.getProfessionName(v)) - table.insert(targetlist,v) - end - end - - if #choicelist > 0 then - ListPrompt( - "Assign new spouse for "..dfhack.TranslateName(source.name), - choicelist, - true, - function(a,b) - local target = targetlist[a] - Marriage (source,target) - AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(target.name).." have married!") - end) - else - ErrorPopup("No suitable candidates") - end -end - -function MainDialog (source) - - local familystate = GetMarriageSummary(source) - - familystate = familystate.."\nSelect action:" - local choicelist = {} - local on_select = {} - - local adult = dfhack.units.isAdult(source) - local single = source.relations.spouse_id == -1 and source.relations.lover_id == -1 - local ready_for_marriage = single and adult - - if adult then - table.insert(choicelist,"Remove romantic relationships (if any)") - table.insert(on_select, Divorce) - if ready_for_marriage then - table.insert(choicelist,"Assign a new spouse") - table.insert(on_select,ChooseNewSpouse) - end - if not ready_for_marriage then - table.insert(choicelist,"[Assign a new spouse]") - table.insert(on_select,function () ErrorPopup ("Existing relationships must be removed if you wish to assign a new spouse.") end) - end - else - table.insert(choicelist,"Leave this child alone") - table.insert(on_select,nil) - end - - ListPrompt(familystate, choicelist, false, - function(a,b) if on_select[a] then on_select[a](source) end end) -end - - -local args = {...} - -if args[1] == "help" or args[1] == "?" then print(helpstr) return end - -if not dfhack.world.isFortressMode() then - print (helpstr) qerror ("invalid game mode") return -end - -if args[1] == "divorce" and tonumber(args[2]) then - local unit = df.unit.find(args[2]) - if unit then Divorce (unit) return end -end - -if tonumber(args[1]) and tonumber(args[2]) then - local unit1 = df.unit.find(args[1]) - local unit2 = df.unit.find(args[2]) - if unit1 and unit2 then - Divorce (unit1) - Divorce (unit2) - Marriage (unit1,unit2) - return - end -end - -local selected = dfhack.gui.getSelectedUnit(true) -if tonumber(args[1]) then - selected = df.unit.find(tonumber(args[1])) or selected -end - -if selected then - if dfhack.units.isCitizen(selected) and dfhack.units.isSane(selected) then - MainDialog(selected) - else - qerror("You must select a sane fortress citizen.") - return - end -else - print (helpstr) - qerror("Select a sane fortress dwarf") -end diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua deleted file mode 100644 index 576649013..000000000 --- a/scripts/gui/gm-editor.lua +++ /dev/null @@ -1,536 +0,0 @@ --- Interface powered item editor. - ---[[=begin - -gui/gm-editor -============= -This editor allows to change and modify almost anything in df. Press :kbd:`?` for -in-game help. There are three ways to open this editor: - -* Callling ``gui/gm-editor`` from a command or keybinding opens the editor - on whatever is selected or viewed (e.g. unit/item description screen) - -* using gui/gm-editor - executes lua command and opens editor on - its results (e.g. ``gui/gm-editor "df.global.world.items.all"`` shows all items) - -* using gui/gm-editor dialog - shows an in game dialog to input lua command. Works - the same as version above. - -.. image:: /docs/images/gm-editor.png - -=end]] -local gui = require 'gui' -local dialog = require 'gui.dialogs' -local widgets =require 'gui.widgets' -local guiScript = require 'gui.script' -local args={...} - -find_funcs = find_funcs or (function() - local t = {} - for k in pairs(df) do - pcall(function() - t[k] = df[k].find - end) - end - return t -end)() - -local keybindings={ - offset={key="CUSTOM_ALT_O",desc="Show current items offset"}, - find={key="CUSTOM_F",desc="Find a value by entering a predicate"}, - find_id={key="CUSTOM_I",desc="Find object with this ID"}, - lua_set={key="CUSTOM_ALT_S",desc="Set by using a lua function"}, - insert={key="CUSTOM_ALT_I",desc="Insert a new value to the vector"}, - delete={key="CUSTOM_ALT_D",desc="Delete selected entry"}, - reinterpret={key="CUSTOM_ALT_R",desc="Open selected entry as something else"}, - start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"}, - help={key="HELP",desc="Show this help"}, - displace={key="STRING_A093",desc="Open reference offseted by index"}, - NOT_USED={key="SEC_SELECT",desc="Edit selected entry as a number (for enums)"}, --not a binding... -} -function getTargetFromScreens() - local my_trg - if dfhack.gui.getCurFocus() == 'item' then - my_trg=dfhack.gui.getCurViewscreen().item - elseif dfhack.gui.getCurFocus() == 'joblist' then - local t_screen=dfhack.gui.getCurViewscreen() - my_trg=t_screen.jobs[t_screen.cursor_pos] - elseif dfhack.gui.getCurFocus() == 'createquota' then - local t_screen=dfhack.gui.getCurViewscreen() - my_trg=t_screen.orders[t_screen.sel_idx] - elseif dfhack.gui.getCurFocus() == 'dwarfmode/LookAround/Flow' then - local t_look=df.global.ui_look_list.items[df.global.ui_look_cursor] - my_trg=t_look.flow - - elseif dfhack.gui.getSelectedUnit(true) then - my_trg=dfhack.gui.getSelectedUnit(true) - elseif dfhack.gui.getSelectedItem(true) then - my_trg=dfhack.gui.getSelectedItem(true) - elseif dfhack.gui.getSelectedJob(true) then - my_trg=dfhack.gui.getSelectedJob(true) - else - qerror("No valid target found") - end - return my_trg -end -function search_relevance(search, candidate) - local function clean(str) - return ' ' .. str:lower():gsub('[^a-z0-9]','') .. ' ' - end - search = clean(search) - candidate = clean(candidate) - local ret = 0 - while #search > 0 do - local pos = candidate:find(search:sub(1, 1), 1, true) - if pos then - ret = ret + (#search - pos) - candidate = candidate:sub(pos + 1) - end - search = search:sub(2) - end - return ret -end - - -GmEditorUi = defclass(GmEditorUi, gui.FramedScreen) -GmEditorUi.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "GameMaster's editor", - } -function GmEditorUi:onHelp() - self.subviews.pages:setSelected(2) -end -function burning_red(input) -- todo does not work! bug angavrilov that so that he would add this, very important!! - local col=COLOR_LIGHTRED - return {text=input,pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}} -end -function Disclaimer(tlb) - local dsc={"Association Of ",{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}, - "Dwarves (AOPD) is not responsible for all the damage",NEWLINE,"that this tool can (and will) cause to you and your loved dwarves",NEWLINE,"and/or saves.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} - if tlb then - for _,v in ipairs(dsc) do - table.insert(tlb,v) - end - end - return dsc -end - -function GmEditorUi:init(args) - self.stack={} - self.item_count=0 - self.keys={} - local helptext={{text="Help"},NEWLINE,NEWLINE} - for k,v in pairs(keybindings) do - table.insert(helptext,{text=v.desc,key=v.key,key_sep=': '}) - table.insert(helptext,NEWLINE) - end - table.insert(helptext,NEWLINE) - Disclaimer(helptext) - - local helpPage=widgets.Panel{ - subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} - local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"), - on_submit2=self:callback("editSelectedRaw"), - text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}} - local mainPage=widgets.Panel{ - subviews={ - mainList, - widgets.Label{text={{text="",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, - widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2}, - on_click=function() self:enable_input(true) end}, - widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"}, - --widgets.Label{text="BLAH2"} - } - ,view_id='page_main'} - - local pages=widgets.Pages{subviews={mainPage,helpPage},view_id="pages"} - self:addviews{ - pages - } - self:pushTarget(args.target) -end -function GmEditorUi:text_input(new_text) - self:updateTarget(true,true) -end -function GmEditorUi:enable_input(enable) - self.subviews.filter_input.active=enable -end -function GmEditorUi:find(test) - local trg=self:currentTarget() - - if test== nil then - dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self)) - return - end - - local e,what=load("return function(k,v) return "..test.." end") - if e==nil then - dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_LIGHTRED) - end - - if trg.target and trg.target._kind and trg.target._kind=="container" then - - for k,v in pairs(trg.target) do - if e()(k,v)==true then - self:pushTarget(v) - return - end - end - else - local i=1 - for k,v in pairs(trg.target) do - if e()(k,v)==true then - self.subviews.list_main:setSelected(i) - return - end - i=i+1 - end - end -end -function GmEditorUi:find_id() - local key = self:getSelectedKey() - local id = tonumber(self:getSelectedValue()) - if not id then return end - local opts = {} - for name, func in pairs(find_funcs) do - table.insert(opts, {text=name, callback=func, weight=search_relevance(key, name)}) - end - table.sort(opts, function(a, b) - return a.weight > b.weight - end) - guiScript.start(function() - local ret,idx,choice=guiScript.showListPrompt("Choose type:",nil,3,opts,nil,true) - if ret then - local obj = choice.callback(id) - if obj then - self:pushTarget(obj) - else - dialog.showMessage("Error!", ('%s with ID %d not found'):format(choice.text, id), COLOR_LIGHTRED) - end - end - end) -end -function GmEditorUi:insertNew(typename) - local tp=typename - if typename == nil then - dialog.showInputPrompt("Class type","You can:\n * Enter type name (without 'df.')\n * Leave empty for default type and 'nil' value\n * Enter '*' for default type and 'new' constructed pointer value",COLOR_WHITE,"",self:callback("insertNew")) - return - end - - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="container" then - if tp == "" then - trg.target:resize(#trg.target+1) - elseif tp== "*" then - trg.target:insert("#",{new=true}) - else - local ntype=df[tp] - if ntype== nil then - dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED) - return - end - trg.target:insert("#",{new=ntype}) - end - self:updateTarget(true,true) - end -end -function GmEditorUi:deleteSelected(key) - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="container" then - trg.target:erase(key) - self:updateTarget(true,true) - end -end -function GmEditorUi:getSelectedKey() - return self:currentTarget().keys[self.subviews.list_main:getSelected()] -end -function GmEditorUi:getSelectedValue() - return self:currentTarget().target[self:getSelectedKey()] -end -function GmEditorUi:currentTarget() - return self.stack[#self.stack] -end -function GmEditorUi:getSelectedEnumType() - local trg=self:currentTarget() - local trg_key=trg.keys[self.subviews.list_main:getSelected()] - - local ok,ret=pcall(function () --super safe way to check if the field has enum - return trg.target._field==nil or trg.target:_field(trg_key)==nil - end) - if not ok or ret==true then - return nil - end - - local enum=trg.target:_field(trg_key)._type - if enum._kind=="enum-type" then - return enum - else - return nil - end -end -function GmEditorUi:editSelectedEnum(index,choice) - local enum=self:getSelectedEnumType() - if enum then - local trg=self:currentTarget() - local trg_key=self:getSelectedKey() - local list={} - for i=enum._first_item, enum._last_item do - table.insert(list,{text=('%s (%i)'):format(tostring(enum[i]), i),value=i}) - end - guiScript.start(function() - local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list,nil,true) - if ret then - trg.target[trg_key]=choice.value - self:updateTarget(true) - end - end) - - else - qerror("not an enum") - end -end -function GmEditorUi:openReinterpret(key) - local trg=self:currentTarget() - dialog.showInputPrompt(tostring(trg_key),"Enter new type:",COLOR_WHITE, - "",function(choice) - local ntype=df[tp] - self:pushTarget(df.reinterpret_cast(ntype,trg.target[key])) - end) -end -function GmEditorUi:openOffseted(index,choice) - local trg=self:currentTarget() - local trg_key=trg.keys[index] - - dialog.showInputPrompt(tostring(trg_key),"Enter offset:",COLOR_WHITE,"", - function(choice) - self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice))) - end) -end -function GmEditorUi:editSelectedRaw(index,choice) - self:editSelected(index, choice, {raw=true}) -end -function GmEditorUi:editSelected(index,choice,opts) - opts = opts or {} - local trg=self:currentTarget() - local trg_key=trg.keys[index] - if trg.target and trg.target._kind and trg.target._kind=="bitfield" then - trg.target[trg_key]= not trg.target[trg_key] - self:updateTarget(true) - else - --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") - local trg_type=type(trg.target[trg_key]) - if self:getSelectedEnumType() and not opts.raw then - self:editSelectedEnum() - elseif trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected - dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE, - tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key)) - - elseif trg_type == 'boolean' then - trg.target[trg_key] = not trg.target[trg_key] - self:updateTarget(true) - elseif trg_type == 'userdata' or trg_type == 'table' then - self:pushTarget(trg.target[trg_key]) - elseif trg_type == 'nil' or trg_type == 'function' then - -- ignore - else - print("Unknown type:"..trg_type) - pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end) - end - end -end - -function GmEditorUi:commitEdit(key,value) - local trg=self:currentTarget() - if type(trg.target[key])=='number' then - trg.target[key]=tonumber(value) - elseif type(trg.target[key])=='string' then - trg.target[key]=value - end - self:updateTarget(true) -end - -function GmEditorUi:set(key,input) - local trg=self:currentTarget() - - if input== nil then - dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",self:callback("set",key)) - return - end - local e,what=load("return function(v) return "..input.." end") - if e==nil then - dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_LIGHTRED) - return - end - trg.target[key]=e()(trg) - self:updateTarget(true) -end -function GmEditorUi:onInput(keys) - if keys.LEAVESCREEN then - if self.subviews.filter_input.active then - self:enable_input(false) - return - end - if self.subviews.pages:getSelected()==2 then - self.subviews.pages:setSelected(1) - else - self:popTarget() - end - end - - if self.subviews.filter_input.active then - self.super.onInput(self,keys) - return - end - - if keys[keybindings.offset.key] then - local trg=self:currentTarget() - local _,stoff=df.sizeof(trg.target) - local size,off=df.sizeof(trg.target:_field(self:getSelectedKey())) - dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE) - elseif keys[keybindings.displace.key] then - self:openOffseted(self.subviews.list_main:getSelected()) - elseif keys[keybindings.find.key] then - self:find() - elseif keys[keybindings.find_id.key] then - self:find_id() - elseif keys[keybindings.lua_set.key] then - self:set(self:getSelectedKey()) - elseif keys[keybindings.insert.key] then --insert - self:insertNew() - elseif keys[keybindings.delete.key] then --delete - self:deleteSelected(self:getSelectedKey()) - elseif keys[keybindings.reinterpret.key] then - self:openReinterpret(self:getSelectedKey()) - elseif keys[keybindings.start_filter.key] then - self:enable_input(true) - return - end - - self.super.onInput(self,keys) -end -function getStringValue(trg,field) - local obj=trg.target - - local text=tostring(obj[field]) - pcall(function() - if obj._field ~= nil then - local enum=obj:_field(field)._type - if enum._kind=="enum-type" then - text=text.." ("..tostring(enum[obj[field]])..")" - end - end - end) - return text -end -function GmEditorUi:updateTarget(preserve_pos,reindex) - local trg=self:currentTarget() - local filter=self.subviews.filter_input.text - - if reindex then - trg.keys={} - for k,v in pairs(trg.target) do - if filter~= "" then - local ok,ret=dfhack.pcall(string.match,tostring(k),filter) - if not ok then - table.insert(trg.keys,k) - elseif ret then - table.insert(trg.keys,k) - end - else - table.insert(trg.keys,k) - end - end - end - self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target) - local t={} - for k,v in pairs(trg.keys) do - table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=getStringValue(trg,v)}}}) - end - local last_pos - if preserve_pos then - last_pos=self.subviews.list_main:getSelected() - end - self.subviews.list_main:setChoices(t) - if last_pos then - self.subviews.list_main:setSelected(last_pos) - else - self.subviews.list_main:setSelected(trg.selected) - end -end -function GmEditorUi:pushTarget(target_to_push) - local new_tbl={} - new_tbl.target=target_to_push - new_tbl.keys={} - new_tbl.selected=1 - new_tbl.filter="" - if self:currentTarget()~=nil then - self:currentTarget().selected=self.subviews.list_main:getSelected() - self.stack[#self.stack].filter=self.subviews.filter_input.text - end - for k,v in pairs(target_to_push) do - table.insert(new_tbl.keys,k) - end - new_tbl.item_count=#new_tbl.keys - table.insert(self.stack,new_tbl) - self.subviews.filter_input.text="" - self:updateTarget() -end -function GmEditorUi:popTarget() - table.remove(self.stack) --removes last element - if #self.stack==0 then - self:dismiss() - return - end - self.subviews.filter_input.text=self.stack[#self.stack].filter --restore filter - self:updateTarget() -end -function show_editor(trg) - if not trg then - qerror('Target not found') - end - local screen = GmEditorUi{target=trg} - screen:show() -end -eval_env = {} -setmetatable(eval_env, {__index = function(_, k) - if k == 'scr' or k == 'screen' then - return dfhack.gui.getCurViewscreen() - elseif k == 'bld' or k == 'building' then - return dfhack.gui.getSelectedBuilding() - elseif k == 'item' then - return dfhack.gui.getSelectedItem() - elseif k == 'job' then - return dfhack.gui.getSelectedJob() - elseif k == 'wsjob' or k == 'workshop_job' then - return dfhack.gui.getSelectedWorkshopJob() - elseif k == 'unit' then - return dfhack.gui.getSelectedUnit() - else - for g in pairs(df.global) do - if g == k then - return df.global[k] - end - end - return _G[k] - end -end}) -function eval(s) - local f, err = load("return " .. s, "expression", "t", eval_env) - if err then qerror(err) end - return f() -end -if #args~=0 then - if args[1]=="dialog" then - function thunk(entry) - show_editor(eval(entry)) - end - dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) - elseif args[1]=="free" then - show_editor(df.reinterpret_cast(df[args[2]],args[3])) - else - show_editor(eval(args[1])) - end -else - show_editor(getTargetFromScreens()) -end - diff --git a/scripts/gui/gm-unit.lua b/scripts/gui/gm-unit.lua deleted file mode 100644 index 3432882cb..000000000 --- a/scripts/gui/gm-unit.lua +++ /dev/null @@ -1,604 +0,0 @@ --- Interface powered, user friendly, unit editor - ---[[=begin - -gui/gm-unit -=========== -An editor for various unit attributes. - -=end]] -local gui = require 'gui' -local dialog = require 'gui.dialogs' -local widgets =require 'gui.widgets' -local guiScript = require 'gui.script' -local utils = require 'utils' -local args={...} - - -local target ---TODO: add more ways to guess what unit you want to edit -if args[1]~= nil then - target=df.units.find(args[1]) -else - target=dfhack.gui.getSelectedUnit(true) -end - -if target==nil then - qerror("No unit to edit") --TODO: better error message -end -local editors={} -function add_editor(editor_class) - table.insert(editors,{text=editor_class.ATTRS.frame_title,on_submit=function ( unit ) - editor_class{target_unit=unit}:show() - end}) -end --------------------------------various subeditors--------- ---TODO set local sould or better yet skills vector to reduce long skill list access typing -editor_skills=defclass(editor_skills,gui.FramedScreen) -editor_skills.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Skill editor", - target_unit = DEFAULT_NIL, - learned_only= false, -} -function list_skills(unit,learned_only) - local s_=df.job_skill - local u_skills=unit.status.current_soul.skills - local ret={} - for i,v in ipairs(s_) do - if i>0 then - local u_skill=utils.binsearch(u_skills,i,"id") - if u_skill or not learned_only then - if not u_skill then - u_skill={rating=-1,experience=0} - end - - local rating - if u_skill.rating >=0 then - rating=df.skill_rating.attrs[u_skill.rating] - else - rating={caption="",xp_threshold=0} - end - - local text=string.format("%s: %s %d %d/%d",df.job_skill.attrs[i].caption,rating.caption,u_skill.rating,u_skill.experience,rating.xp_threshold) - table.insert(ret,{text=text,id=i}) - end - end - end - return ret -end -function editor_skills:update_list(no_save_place) - local skill_list=list_skills(self.target_unit,self.learned_only) - if no_save_place then - self.subviews.skills:setChoices(skill_list) - else - self.subviews.skills:setChoices(skill_list,self.subviews.skills:getSelected()) - end -end -function editor_skills:init( args ) - if self.target_unit.status.current_soul==nil then - qerror("Unit does not have soul, can't edit skills") - end - - local skill_list=list_skills(self.target_unit,self.learned_only) - - self:addviews{ - widgets.FilteredList{ - choices=skill_list, - frame = {t=0, b=1,l=1}, - view_id="skills", - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - {text=": remove level ", - key = "SECONDSCROLL_UP", - on_activate=self:callback("level_skill",-1)}, - {text=": add level ", - key = "SECONDSCROLL_DOWN", - on_activate=self:callback("level_skill",1)} - , - {text=": show learned only ", - key = "CHANGETAB", - on_activate=function () - self.learned_only=not self.learned_only - self:update_list(true) - end} - } - }, - } -end -function editor_skills:get_cur_skill() - local list_wid=self.subviews.skills - local _,choice=list_wid:getSelected() - if choice==nil then - qerror("Nothing selected") - end - local u_skill=utils.binsearch(self.target_unit.status.current_soul.skills,choice.id,"id") - return choice,u_skill -end -function editor_skills:level_skill(lvl) - local sk_en,sk=self:get_cur_skill() - if lvl >0 then - local rating - - if sk then - rating=sk.rating+lvl - else - rating=lvl-1 - end - - utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=rating}, 'id') --TODO set exp? - elseif sk and sk.rating==0 and lvl<0 then - utils.erase_sorted_key(self.target_unit.status.current_soul.skills,sk_en.id,"id") - elseif sk and lvl<0 then - utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=sk.rating+lvl}, 'id') --TODO set exp? - end - self:update_list() -end -function editor_skills:remove_rust(skill) - --TODO -end -add_editor(editor_skills) -------- civ editor -RaceBox = defclass(RaceBox, dialog.ListBox) -RaceBox.focus_path = 'RaceBox' - -RaceBox.ATTRS{ - format_name="$NAME ($TOKEN)", - with_filter=true, - allow_none=false, -} -function RaceBox:format_creature(creature_raw) - local t = {NAME=creature_raw.name[0],TOKEN=creature_raw.creature_id} - return string.gsub(self.format_name, "%$(%w+)", t) -end -function RaceBox:preinit(info) - self.format_name=RaceBox.ATTRS.format_name or info.format_name -- preinit does not have ATTRS set yet - local choices={} - if RaceBox.ATTRS.allow_none or info.allow_none then - table.insert(choices,{text="",num=-1}) - end - for i,v in ipairs(df.global.world.raws.creatures.all) do - local text=self:format_creature(v) - table.insert(choices,{text=text,raw=v,num=i,search_key=text:lower()}) - end - info.choices=choices -end -function showRacePrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_none) - RaceBox{ - frame_title = title, - text = text, - text_pen = tcolor, - on_select = on_select, - on_cancel = on_cancel, - frame_width = min_width, - allow_none = allow_none, - }:show() -end -CivBox = defclass(CivBox,dialog.ListBox) -CivBox.focus_path = "CivBox" - -CivBox.ATTRS={ - format_name="$NAME ($ENGLISH):$ID", - format_no_name=":$ID", - name_other="", - with_filter=true, - allow_other=false, -} - -function civ_name(id,format_name,format_no_name,name_other,name_invalid) - if id==-1 then - return name_other or "" - end - local civ - if type(id)=='userdata' then - civ=id - else - civ=df.historical_entity.find(id) - if civ==nil then - return name_invalid or "" - end - end - local t={NAME=dfhack.TranslateName(civ.name),ENGLISH=dfhack.TranslateName(civ.name,true),ID=civ.id} --TODO race?, maybe something from raws? - if t.NAME=="" then - return string.gsub(format_no_name or ":$ID", "%$(%w+)", t) - end - return string.gsub(format_name or "$NAME ($ENGLISH):$ID", "%$(%w+)", t) -end -function CivBox:update_choices() - local choices={} - if self.allow_other then - table.insert(choices,{text=self.name_other,num=-1}) - end - - for i,v in ipairs(df.global.world.entities.all) do - if not self.race_filter or (v.race==self.race_filter) then --TODO filter type - local text=civ_name(v,self.format_name,self.format_no_name,self.name_other,self.name_invalid) - table.insert(choices,{text=text,raw=v,num=i}) - end - end - self.choices=choices - if self.subviews.list then - self.subviews.list:setChoices(self.choices) - end -end -function CivBox:update_race_filter(id) - local raw=df.creature_raw.find(id) - if raw then - self.subviews.race_label:setText(": "..raw.name[0]) - self.race_filter=id - else - self.subviews.race_label:setText(": ") - self.race_filter=nil - end - - self:update_choices() -end -function CivBox:choose_race() - showRacePrompt("Choose race","Select new race:",nil,function (id,choice) - self:update_race_filter(choice.num) - end,nil,nil,true) -end -function CivBox:init(info) - self.subviews.list.frame={t=3,r=0,l=0} - self:addviews{ - widgets.Label{frame={t=1,l=0},text={ - {text="Filter race ",key="CUSTOM_CTRL_A",key_sep="()",on_activate=self:callback("choose_race")}, - }}, - widgets.Label{frame={t=1,l=21},view_id="race_label", - text=": ", - } - } - self:update_choices() -end -function showCivPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other) - CivBox{ - frame_title = title, - text = text, - text_pen = tcolor, - on_select = on_select, - on_cancel = on_cancel, - frame_width = min_width, - allow_other = allow_other, - }:show() -end - -editor_civ=defclass(editor_civ,gui.FramedScreen) -editor_civ.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Civilization editor", - target_unit = DEFAULT_NIL, - } - -function editor_civ:update_curren_civ() - self.subviews.civ_name:setText("Currently: "..civ_name(self.target_unit.civ_id)) -end -function editor_civ:init( args ) - if self.target_unit==nil then - qerror("invalid unit") - end - - self:addviews{ - widgets.Label{view_id="civ_name",frame = { t=1,l=1}, text="Currently: "..civ_name(self.target_unit.civ_id)}, - widgets.Label{frame = { t=2,l=1}, text={{text=": set to other (-1, usually enemy)",key="CUSTOM_N", - on_activate= function() self.target_unit.civ_id=-1;self:update_curren_civ() end}}}, - widgets.Label{frame = { t=3,l=1}, text={{text=": set to current civ("..df.global.ui.civ_id..")",key="CUSTOM_C", - on_activate= function() self.target_unit.civ_id=df.global.ui.civ_id;self:update_curren_civ() end}}}, - widgets.Label{frame = { t=4,l=1}, text={{text=": manually enter",key="CUSTOM_E", - on_activate=function () - dialog.showInputPrompt("Civ id","Enter new civ id:",COLOR_WHITE, - tostring(self.target_unit.civ_id),function(new_value) - self.target_unit.civ_id=new_value - self:update_curren_civ() - end) - end}} - }, - widgets.Label{frame= {t=5,l=1}, text={{text=": select from list",key="CUSTOM_L", - on_activate=function ( ) - showCivPrompt("Choose civilization", "Select units civilization",nil,function ( id,choice ) - self.target_unit.civ_id=choice.num - self:update_curren_civ() - end,nil,nil,true) - end - }}}, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - } - }, - } -end -add_editor(editor_civ) -------- counters editor -editor_counters=defclass(editor_counters,gui.FramedScreen) -editor_counters.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Counters editor", - target_unit = DEFAULT_NIL, - counters1={ - "think_counter", - "job_counter", - "swap_counter", - "winded", - "stunned", - "unconscious", - "suffocation", - "webbed", - "soldier_mood_countdown", - "soldier_mood", --todo enum, - "pain", - "nausea", - "dizziness", - }, - counters2={ - "paralysis", - "numbness", - "fever", - "exhaustion", - "hunger_timer", - "thirst_timer", - "sleepiness_timer", - "stomach_content", - "stomach_food", - "vomit_timeout", - "stored_fat" --TODO what to reset to? - } -} -function editor_counters:fill_counters() - local ret={} - local u=self.target_unit - for i,v in ipairs(self.counters1) do - table.insert(ret,{f=u.counters:_field(v),name=v}) - end - for i,v in ipairs(self.counters2) do - table.insert(ret,{f=u.counters2:_field(v),name=v}) - end - return ret -end -function editor_counters:update_counters() - for i,v in ipairs(self.counter_list) do - v.text=string.format("%s:%d",v.name,v.f.value) - end - self.subviews.counters:setChoices(self.counter_list) -end -function editor_counters:set_cur_counter(value,index,choice) - choice.f.value=value - self:update_counters() -end -function editor_counters:choose_cur_counter(index,choice) - dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE, - tostring(choice.f.value),function(new_value) - self:set_cur_counter(new_value,index,choice) - end) -end -function editor_counters:init( args ) - if self.target_unit==nil then - qerror("invalid unit") - end - - self.counter_list=self:fill_counters() - - - self:addviews{ - widgets.FilteredList{ - choices=self.counter_list, - frame = {t=0, b=1,l=1}, - view_id="counters", - on_submit=self:callback("choose_cur_counter"), - on_submit2=self:callback("set_cur_counter",0),--TODO some things need to be set to different defaults - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - {text=": reset counter ", - key = "SEC_SELECT", - }, - {text=": set counter ", - key = "SELECT", - } - - } - }, - } - self:update_counters() -end -add_editor(editor_counters) - -wound_creator=defclass(wound_creator,gui.FramedScreen) -wound_creator.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Wound creator", - target_wound = DEFAULT_NIL, - --filter -} -function wound_creator:init( args ) - if self.target_wound==nil then - qerror("invalid wound") - end - - - self:addviews{ - widgets.List{ - - frame = {t=0, b=1,l=1}, - view_id="fields", - on_submit=self:callback("edit_cur_wound"), - on_submit2=self:callback("delete_current_wound") - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss")}, - - {text=": edit wound ", - key = "SELECT"}, - - {text=": delete wound ", - key = "SEC_SELECT"}, - {text=": create wound ", - key = "CUSTOM_CTRL_I", - on_activate= self:callback("create_new_wound")}, - - } - }, - } - self:update_wounds() -end -------------------- -editor_wounds=defclass(editor_wounds,gui.FramedScreen) -editor_wounds.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "Wound editor", - target_unit = DEFAULT_NIL, - --filter -} -function is_scar( wound_part ) - return wound_part.flags1.scar_cut or wound_part.flags1.scar_smashed or - wound_part.flags1.scar_edged_shake1 or wound_part.flags1.scar_blunt_shake1 -end -function format_flag_name( fname ) - return fname:sub(1,1):upper()..fname:sub(2):gsub("_"," ") -end -function name_from_flags( wp ) - for i,v in ipairs(wp.flags1) do - if v then - return format_flag_name(df.wound_damage_flags1[i]) - end - end - for i,v in ipairs(wp.flags2) do - if v then - return format_flag_name(df.wound_damage_flags2[i]) - end - end - return "" -end -function format_wound( list_id,wound, unit) - - local name="" - if #wound.parts>0 and #wound.parts[0].effect_type>0 then --try to make wound name by effect... - name=tostring(df.wound_effect_type[wound.parts[0].effect_type[0]]) - if #wound.parts>1 then --cheap and probably incorrect... - name=name.."s" - end - elseif #wound.parts>0 and is_scar(wound.parts[0]) then - name="Scar" - elseif #wound.parts>0 then - local wp=wound.parts[0] - name=name_from_flags(wp) - end - - return string.format("%d. %s id=%d",list_id,name,wound.id) -end -function editor_wounds:update_wounds() - local ret={} - for i,v in ipairs(self.trg_wounds) do - table.insert(ret,{text=format_wound(i,v,self.target_unit),wound=v}) - end - self.subviews.wounds:setChoices(ret) - self.wound_list=ret -end -function editor_wounds:dirty_unit() - print("todo: implement unit status recalculation") -end -function editor_wounds:get_cur_wound() - local list_wid=self.subviews.wounds - local _,choice=list_wid:getSelected() - if choice==nil then - qerror("Nothing selected") - end - local ret_wound=utils.binsearch(self.trg_wounds,choice.id,"id") - return choice,ret_wound -end -function editor_wounds:delete_current_wound(index,choice) - - utils.erase_sorted(self.trg_wounds,choice.wound,"id") - choice.wound:delete() - self:dirty_unit() - self:update_wounds() -end -function editor_wounds:create_new_wound() - print("Creating") -end -function editor_wounds:edit_cur_wound(index,choice) - -end -function editor_wounds:init( args ) - if self.target_unit==nil then - qerror("invalid unit") - end - self.trg_wounds=self.target_unit.body.wounds - - self:addviews{ - widgets.List{ - - frame = {t=0, b=1,l=1}, - view_id="wounds", - on_submit=self:callback("edit_cur_wound"), - on_submit2=self:callback("delete_current_wound") - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor ", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss")}, - - {text=": edit wound ", - key = "SELECT"}, - - {text=": delete wound ", - key = "SEC_SELECT"}, - {text=": create wound ", - key = "CUSTOM_CTRL_I", - on_activate= self:callback("create_new_wound")}, - - } - }, - } - self:update_wounds() -end -add_editor(editor_wounds) - --------------------------------main window---------------- -unit_editor = defclass(unit_editor, gui.FramedScreen) -unit_editor.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_title = "GameMaster's unit editor", - target_unit = DEFAULT_NIL, - } - - -function unit_editor:init(args) - - self:addviews{ - widgets.FilteredList{ - choices=editors, - on_submit=function (idx,choice) - if choice.on_submit then - choice.on_submit(self.target_unit) - end - end - }, - widgets.Label{ - frame = { b=0,l=1}, - text ={{text= ": exit editor", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - } - }, - } -end - - -unit_editor{target_unit=target}:show() diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua deleted file mode 100644 index be90ffb1c..000000000 --- a/scripts/gui/guide-path.lua +++ /dev/null @@ -1,205 +0,0 @@ --- Show/change the path used by Guide Cart orders ---[[=begin - -gui/guide-path -============== -Bind to a key (the example config uses :kbd:`Alt`:kbd:`P`), and activate in the Hauling menu with -the cursor over a Guide order. - -.. image:: /docs/images/guide-path.png - -The script displays the cached path that will be used by the order; the game -computes it when the order is executed for the first time. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local tile_attrs = df.tiletype.attrs - -GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) - -GuidePathUI.focus_path = 'guide-path' - -GuidePathUI.ATTRS { - route = DEFAULT_NIL, - stop = DEFAULT_NIL, - order = DEFAULT_NIL, -} - -function GuidePathUI:init() - self.saved_mode = df.global.ui.main.mode - - for i=0,#self.route.stops-1 do - if self.route.stops[i] == self.stop then - self.stop_idx = i - break - end - end - - if not self.stop_idx then - error('Could not find stop index') - end - - self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops] -end - -function GuidePathUI:onShow() - -- with cursor, but without those ugly lines from native hauling mode - df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles -end - -function GuidePathUI:onDestroy() - df.global.ui.main.mode = self.saved_mode -end - -local function getTileType(cursor) - local block = dfhack.maps.getTileBlock(cursor) - if block then - return block.tiletype[cursor.x%16][cursor.y%16] - else - return 0 - end -end - -local function isTrackTile(tile) - return tile_attrs[tile].special == df.tiletype_special.TRACK -end - -local function paintMapTile(dc, vp, cursor, pos, ...) - if not same_xyz(cursor, pos) then - local stile = vp:tileToScreen(pos) - if stile.z == 0 then - dc:seek(stile.x,stile.y):char(...) - end - end -end - -local function get_path_point(gpath,i) - return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) -end - -function GuidePathUI:renderPath(cursor) - local gpath = self.order.guide_path - local startp = self.stop.pos - local endp = self.next_stop.pos - local vp = self:getViewport() - local dc = gui.Painter.new(self.df_layout.map) - local visible = gui.blink_visible(500) - - if visible then - paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) - end - - local ok = nil - local pcnt = #gpath.x - if pcnt > 0 then - ok = true - - for i = 0,pcnt-1 do - local pt = get_path_point(gpath, i) - if i == 0 and not same_xyz(startp,pt) then - ok = false - end - if i == pcnt-1 and not same_xyz(endp,pt) then - ok = false - end - local tile = getTileType(pt) - if not isTrackTile(tile) then - ok = false - end - if visible then - local char = '+' - if i < pcnt-1 then - local npt = get_path_point(gpath, i+1) - if npt.x == pt.x+1 then - char = 26 - elseif npt.x == pt.x-1 then - char = 27 - elseif npt.y == pt.y+1 then - char = 25 - elseif npt.y == pt.y-1 then - char = 24 - end - end - local color = COLOR_LIGHTGREEN - if not ok then color = COLOR_LIGHTRED end - paintMapTile(dc, vp, cursor, pt, char, color) - end - end - end - - if gui.blink_visible(120) then - paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) - end - - return ok -end - -function GuidePathUI:onRenderBody(dc) - dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path") - - dc:seek(2,3) - - local cursor = guidm.getCursorPos() - local path_ok = self:renderPath(cursor) - - if path_ok == nil then - dc:string('No saved path', COLOR_DARKGREY) - elseif path_ok then - dc:string('Valid path', COLOR_GREEN) - else - dc:string('Invalid path', COLOR_RED) - end - - dc:newline():newline(1) - dc:key('CUSTOM_Z'):string(": Reset path",COLOR_GREY,nil,path_ok~=nil) - --dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) - dc:newline(1) - dc:key('CUSTOM_C'):string(": Zoom cur, ") - dc:key('CUSTOM_N'):string(": Zoom next") - - dc:newline():newline(1):string('At cursor:') - dc:newline():newline(2) - - local tile = getTileType(cursor) - if isTrackTile(tile) then - dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN) - else - dc:string('No track', COLOR_DARKGREY) - end - - dc:newline():newline(1) - dc:key('LEAVESCREEN'):string(": Back") -end - -function GuidePathUI:onInput(keys) - if keys.CUSTOM_C then - self:moveCursorTo(copyall(self.stop.pos)) - elseif keys.CUSTOM_N then - self:moveCursorTo(copyall(self.next_stop.pos)) - elseif keys.CUSTOM_Z then - local gpath = self.order.guide_path - gpath.x:resize(0) - gpath.y:resize(0) - gpath.z:resize(0) - elseif keys.LEAVESCREEN then - self:dismiss() - elseif self:propagateMoveKeys(keys) then - return - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then - qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order") -end - -local hauling = df.global.ui.hauling -local route = hauling.view_routes[hauling.cursor_top] -local stop = hauling.view_stops[hauling.cursor_top] -local order = hauling.stop_conditions[hauling.cursor_stop] - -local list = GuidePathUI{ route = route, stop = stop, order = order } -list:show() diff --git a/scripts/gui/hack-wish.lua b/scripts/gui/hack-wish.lua deleted file mode 100644 index de423abc2..000000000 --- a/scripts/gui/hack-wish.lua +++ /dev/null @@ -1,8 +0,0 @@ ---@ alias = 'gui/create-item' ---[[=begin - -gui/hack-wish -============= -An alias for `gui/create-item`. Deprecated. - -=end]] \ No newline at end of file diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua deleted file mode 100644 index 324aa11ad..000000000 --- a/scripts/gui/hello-world.lua +++ /dev/null @@ -1,31 +0,0 @@ --- Test lua viewscreens. ---[[=begin - -gui/hello-world -=============== -A basic example for testing, or to start your own script from. - -=end]] -local gui = require 'gui' - -local text = 'Woohoo, lua viewscreen :)' - -local screen = gui.FramedScreen{ - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'Hello World', - frame_width = #text, - frame_height = 1, - frame_inset = 1, -} - -function screen:onRenderBody(dc) - dc:string(text, COLOR_LIGHTGREEN) -end - -function screen:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - end -end - -screen:show() diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua deleted file mode 100644 index 51ebca4bf..000000000 --- a/scripts/gui/liquids.lua +++ /dev/null @@ -1,326 +0,0 @@ --- Interface front-end for liquids plugin. ---[[=begin - -gui/liquids -=========== -To use, bind to a key (the example config uses Alt-L) and activate in the :kbd:`k` mode. - -.. image:: /docs/images/liquids.png - -This script is a gui front-end to `liquids` and works similarly, -allowing you to add or remove water & magma, and create obsidian walls & floors. - -.. warning:: - - There is **no undo support**. Bugs in this plugin have been - known to create pathfinding problems and heat traps. - -The :kbd:`b` key changes how the affected area is selected. The default :guilabel:`Rectangle` -mode works by selecting two corners like any ordinary designation. The :kbd:`p` -key chooses between adding water, magma, obsidian walls & floors, or just -tweaking flags. - -When painting liquids, it is possible to select the desired level with :kbd:`+`:kbd:`-`, -and choose between setting it exactly, only increasing or only decreasing -with :kbd:`s`. - -In addition, :kbd:`f` allows disabling or enabling the flowing water computations -for an area, and :kbd:`r` operates on the "permanent flow" property that makes -rivers power water wheels even when full and technically not flowing. - -After setting up the desired operations using the described keys, use :kbd:`Enter` to apply them. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local liquids = require('plugins.liquids') - -local sel_rect = df.global.selection_rect - -local brushes = { - { tag = 'range', caption = 'Rectangle', range = true }, - { tag = 'block', caption = '16x16 block' }, - { tag = 'column', caption = 'Column' }, - { tag = 'flood', caption = 'Flood' }, -} - -local paints = { - { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'D_LOOK_ARENA_WATER' }, - { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'D_LOOK_ARENA_MAGMA' }, - { tag = 'obsidian', caption = 'Obsidian Wall' }, - { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, - { tag = 'riversource', caption = 'River Source' }, - { tag = 'flowbits', caption = 'Flow Updates', flow = true }, - { tag = 'wclean', caption = 'Clean Salt/Stagnant' }, -} - -local flowbits = { - { tag = '+', caption = 'Enable Updates' }, - { tag = '-', caption = 'Disable Updates' }, - { tag = '.', caption = 'Keep Updates' }, -} - -local setmode = { - { tag = '.', caption = 'Set Exactly' }, - { tag = '+', caption = 'Only Increase' }, - { tag = '-', caption = 'Only Decrease' }, -} - -local permaflows = { - { tag = '.', caption = "Keep Permaflow" }, - { tag = '-', caption = 'Remove Permaflow' }, - { tag = 'N', caption = 'Set Permaflow N' }, - { tag = 'S', caption = 'Set Permaflow S' }, - { tag = 'E', caption = 'Set Permaflow E' }, - { tag = 'W', caption = 'Set Permaflow W' }, - { tag = 'NE', caption = 'Set Permaflow NE' }, - { tag = 'NW', caption = 'Set Permaflow NW' }, - { tag = 'SE', caption = 'Set Permaflow SE' }, - { tag = 'SW', caption = 'Set Permaflow SW' }, -} - -Toggle = defclass(Toggle) - -Toggle.ATTRS{ items = {}, selected = 1 } - -function Toggle:get() - return self.items[self.selected] -end - -function Toggle:render(dc) - local item = self:get() - if item then - dc:string(item.caption) - if item.key then - dc:string(" ("):key(item.key):string(")") - end - else - dc:string('NONE', COLOR_RED) - end -end - -function Toggle:step(delta) - if #self.items > 1 then - delta = delta or 1 - self.selected = 1 + (self.selected + delta - 1) % #self.items - end -end - -LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay) - -LiquidsUI.focus_path = 'liquids' - -function LiquidsUI:init() - self:assign{ - brush = Toggle{ items = brushes }, - paint = Toggle{ items = paints }, - flow = Toggle{ items = flowbits }, - set = Toggle{ items = setmode }, - permaflow = Toggle{ items = permaflows }, - amount = 7, - } -end - -function LiquidsUI:onDestroy() - guidm.clearSelection() -end - -function render_liquid(dc, block, x, y) - local dsgn = block.designation[x%16][y%16] - - if dsgn.flow_size > 0 then - if dsgn.liquid_type == df.tile_liquid.Magma then - dc:pen(COLOR_RED):string("Magma") - else - dc:pen(COLOR_BLUE) - if dsgn.water_stagnant then dc:string("Stagnant ") end - if dsgn.water_salt then dc:string("Salty ") end - dc:string("Water") - end - dc:string(" ["..dsgn.flow_size.."/7]") - else - dc:string('No Liquid') - end -end - -local permaflow_abbr = { - north = 'N', south = 'S', east = 'E', west = 'W', - northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW' -} - -function render_flow_state(dc, block, x, y) - local flow = block.liquid_flow[x%16][y%16] - - if block.flags.update_liquid then - dc:string("Updating", COLOR_GREEN) - else - dc:string("Static") - end - dc:string(", ") - if flow.perm_flow_dir ~= 0 then - local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir] - dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN) - elseif flow.temp_flow_timer > 0 then - dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN) - else - dc:string("No Flow") - end -end - -function LiquidsUI:onRenderBody(dc) - dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) - - local cursor = guidm.getCursorPos() - local block = dfhack.maps.getTileBlock(cursor) - - if block then - local x, y = pos2xyz(cursor) - local tile = block.tiletype[x%16][y%16] - - dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN) - dc:newline(2):pen(COLOR_DARKGREY) - render_liquid(dc, block, x, y) - dc:newline(2):pen(COLOR_DARKGREY) - render_flow_state(dc, block, x, y) - else - dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2) - end - - dc:newline():pen(COLOR_GREY) - - dc:newline(1):key('CUSTOM_B'):string(": ") - self.brush:render(dc) - dc:newline(1):key('CUSTOM_P'):string(": ") - self.paint:render(dc) - - local paint = self.paint:get() - - dc:newline() - if paint.liquid then - dc:newline(1):string("Amount: "..self.amount) - dc:advance(1):string("("):key('SECONDSCROLL_UP'):key('SECONDSCROLL_DOWN'):string(")") - dc:newline(3):key('CUSTOM_S'):string(": ") - self.set:render(dc) - else - dc:advance(0,2) - end - - dc:newline() - if paint.flow then - dc:newline(1):key('CUSTOM_F'):string(": ") - self.flow:render(dc) - dc:newline(1):key('CUSTOM_R'):string(": ") - self.permaflow:render(dc) - else - dc:advance(0,2) - end - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back, ") - dc:key('SELECT'):string(": Paint") -end - -function ensure_blocks(cursor, size, cb) - size = size or xyz2pos(1,1,1) - local cx,cy,cz = pos2xyz(cursor) - local all = true - for x=1,size.x or 1,16 do - for y=1,size.y or 1,16 do - for z=1,size.z do - if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then - all = false - end - end - end - end - if all then - cb() - return - end - dlg.showYesNoPrompt( - 'Instantiate Blocks', - 'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.', - COLOR_YELLOW, - function() - for x=1,size.x or 1,16 do - for y=1,size.y or 1,16 do - for z=1,size.z do - dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1) - end - end - end - cb() - end, - function() - cb() - end - ) -end - -function LiquidsUI:onInput(keys) - local paint = self.paint:get() - local liquid = paint.liquid - if keys.CUSTOM_B then - self.brush:step() - elseif keys.CUSTOM_P then - self.paint:step() - elseif liquid and keys.SECONDSCROLL_UP then - self.amount = math.max(0, self.amount-1) - elseif liquid and keys.SECONDSCROLL_DOWN then - self.amount = math.min(7, self.amount+1) - elseif liquid and keys.CUSTOM_S then - self.set:step() - elseif paint.flow and keys.CUSTOM_F then - self.flow:step() - elseif paint.flow and keys.CUSTOM_R then - self.permaflow:step() - elseif keys.LEAVESCREEN then - if guidm.getSelection() then - guidm.clearSelection() - return - end - self:dismiss() - self:sendInputToParent('CURSOR_DOWN_Z') - self:sendInputToParent('CURSOR_UP_Z') - elseif keys.SELECT then - local cursor = guidm.getCursorPos() - local sp = guidm.getSelection() - local size = nil - if self.brush:get().range then - if not sp then - guidm.setSelectionStart(cursor) - return - else - guidm.clearSelection() - cursor, size = guidm.getSelectionRange(cursor, sp) - end - else - guidm.clearSelection() - end - local cb = curry( - liquids.paint, - cursor, - self.brush:get().tag, self.paint:get().tag, - self.amount, size, - self.set:get().tag, self.flow:get().tag, - self.permaflow:get().tag - ) - ensure_blocks(cursor, size, cb) - elseif self:propagateMoveKeys(keys) then - return - elseif keys.D_LOOK_ARENA_WATER then - self.paint.selected = 1 - elseif keys.D_LOOK_ARENA_MAGMA then - self.paint.selected = 2 - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then - qerror("This script requires the main dwarfmode view in 'k' mode") -end - -local list = LiquidsUI() -list:show() diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua deleted file mode 100644 index d65782469..000000000 --- a/scripts/gui/mechanisms.lua +++ /dev/null @@ -1,146 +0,0 @@ --- Shows mechanisms linked to the current building. ---[[=begin - -gui/mechanisms -============== -To use, bind to a key (the example config uses :kbd:`Ctrl`:kbd:`M`) -and activate in :kbd:`q` mode. - -.. image:: /docs/images/mechanisms.png - -Lists mechanisms connected to the building, and their links. Navigating -the list centers the view on the relevant linked buildings. - -To exit, press :kbd:`Esc` or :kbd:`Enter`; :kbd:`Esc` recenters on -the original building, while :kbd:`Enter` leaves focus on the current -one. :kbd:`Shift`:kbd:`Enter` has an effect equivalent to pressing -:kbd:`Enter`, and then re-entering the mechanisms UI. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' - -function listMechanismLinks(building) - local lst = {} - local function push(item, mode) - if item then - lst[#lst+1] = { - obj = item, mode = mode, - name = utils.getBuildingName(item) - } - end - end - - push(building, 'self') - - if not df.building_actual:is_instance(building) then - return lst - end - - local item, tref, tgt - for _,v in ipairs(building.contained_items) do - item = v.item - if df.item_trappartsst:is_instance(item) then - tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGER) - if tref then - push(tref:getBuilding(), 'trigger') - end - tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGERTARGET) - if tref then - push(tref:getBuilding(), 'target') - end - end - end - - return lst -end - -MechanismList = defclass(MechanismList, guidm.MenuOverlay) - -MechanismList.focus_path = 'mechanisms' - -function MechanismList:init(info) - self:assign{ - links = {}, selected = 1 - } - self:fillList(info.building) -end - -function MechanismList:fillList(building) - local links = listMechanismLinks(building) - - self.old_viewport = self:getViewport() - self.old_cursor = guidm.getCursorPos() - - if #links <= 1 then - links[1].mode = 'none' - end - - self.links = links - self.selected = 1 -end - -local colors = { - self = COLOR_CYAN, none = COLOR_CYAN, - trigger = COLOR_GREEN, target = COLOR_GREEN -} -local icons = { - self = 128, none = 63, trigger = 27, target = 26 -} - -function MechanismList:onRenderBody(dc) - dc:clear() - dc:seek(1,1):string("Mechanism Links", COLOR_WHITE):newline() - - for i,v in ipairs(self.links) do - local pen = { fg=colors[v.mode], bold = (i == self.selected) } - dc:newline(1):pen(pen):char(icons[v.mode]) - dc:advance(1):string(v.name) - end - - local nlinks = #self.links - - if nlinks <= 1 then - dc:newline():newline(1):string("This building has no links", COLOR_LIGHTRED) - end - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back, ") - dc:key('SELECT'):string(": Switch") -end - -function MechanismList:changeSelected(delta) - if #self.links <= 1 then return end - self.selected = 1 + (self.selected + delta - 1) % #self.links - self:selectBuilding(self.links[self.selected].obj) -end - -function MechanismList:onInput(keys) - if keys.SECONDSCROLL_UP then - self:changeSelected(-1) - elseif keys.SECONDSCROLL_DOWN then - self:changeSelected(1) - elseif keys.LEAVESCREEN then - self:dismiss() - if self.selected ~= 1 then - self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view) - end - elseif keys.SELECT_ALL then - if self.selected > 1 then - self:fillList(self.links[self.selected].obj) - end - elseif keys.SELECT then - self:dismiss() - elseif self:simulateViewScroll(keys) then - return - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then - qerror("This script requires the main dwarfmode view in 'q' mode") -end - -local list = MechanismList{ building = df.global.world.selected_building } -list:show() -list:changeSelected(1) diff --git a/scripts/gui/mod-manager.lua b/scripts/gui/mod-manager.lua deleted file mode 100644 index 5c3b39375..000000000 --- a/scripts/gui/mod-manager.lua +++ /dev/null @@ -1,361 +0,0 @@ --- a graphical mod manager for df -local gui=require 'gui' -local widgets=require 'gui.widgets' ---[[=begin - -gui/mod-manager -=============== -A simple way to install and remove small mods. - -It looks for specially formatted mods in :file:`{}/mods/`. Mods are not -included, but some examples are `available here`_. - -.. _`available here`: https://github.com/warmist/df-mini-mods - -.. image:: /docs/images/mod-manager.png - -=end]] -local entity_file=dfhack.getDFPath().."/raw/objects/entity_default.txt" -local init_file=dfhack.getDFPath().."/raw/init.lua" -local mod_dir=dfhack.getDFPath().."/hack/mods" ---[[ mod format: lua script that defines: - name - a name that is displayed in list - author - mod author, also displayed - description - mod description - OPTIONAL: - raws_list - a list (table) of file names that need to be copied over to df raws - patch_entity - a chunk of text to patch entity TODO: add settings to which entities to add - patch_init - a chunk of lua to add to lua init - patch_dofile - a list (table) of files to add to lua init as "dofile" - patch_files - a table of files to patch: - filename - a filename (in raws folder) to patch - patch - what to add - after - a string after which to insert - MORE OPTIONAL: - guard - a token that is used in raw files to find editions and remove them on uninstall - guard_init - a token for lua file - [pre|post]_(un)install - callback functions. Can trigger more complicated behavior -]] - -function fileExists(filename) - local file=io.open(filename,"rb") - if file==nil then - return - else - file:close() - return true - end -end -if not fileExists(init_file) then - local initFile=io.open(init_file,"a") - initFile:close() -end -function copyFile(from,to) --oh so primitive - local filefrom=io.open(from,"rb") - local fileto=io.open(to,"w+b") - local buf=filefrom:read("*a") - printall(buf) - fileto:write(buf) - filefrom:close() - fileto:close() -end -function patchInit(initFileName,patch_guard,code) - local initFile=io.open(initFileName,"a") - initFile:write(string.format("\n%s\n%s\n%s",patch_guard[1], - code,patch_guard[2])) - initFile:close() -end -function patchDofile( luaFileName,patch_guard,dofile_list,mod_path ) - local luaFile=io.open(luaFileName,"a") - luaFile:write(patch_guard[1].."\n") - for _,v in ipairs(dofile_list) do - local fixed_path=mod_path:gsub("\\","/") - luaFile:write(string.format("dofile('%s/%s')\n",fixed_path,v)) - end - luaFile:write(patch_guard[2].."\n") - luaFile:close() -end -function patchFile(file_name,patch_guard,after_string,code) - local input_lines=patch_guard[1].."\n"..code.."\n"..patch_guard[2] - - local badchars="[%:%[%]]" - local find_string=after_string:gsub(badchars,"%%%1") --escape some bad chars - - local entityFile=io.open(file_name,"r") - local buf=entityFile:read("*all") - entityFile:close() - local entityFile=io.open(file_name,"w+") - buf=string.gsub(buf,find_string,after_string.."\n"..input_lines) - entityFile:write(buf) - entityFile:close() -end -function findGuards(str,start,patch_guard) - local pStart=string.find(str,patch_guard[1],start) - if pStart==nil then return nil end - local pEnd=string.find(str,patch_guard[2],pStart) - if pEnd==nil then error("Start guard token found, but end was not found") end - return pStart-1,pEnd+#patch_guard[2]+1 -end -function findGuardsFile(filename,patch_guard) - local file=io.open(filename,"r") - local buf=file:read("*all") - return findGuards(buf,1,patch_guard) -end -function unPatchFile(filename,patch_guard) - local file=io.open(filename,"r") - local buf=file:read("*all") - file:close() - - local newBuf="" - local pos=1 - local lastPos=1 - repeat - local endPos - pos,endPos=findGuards(buf,lastPos,patch_guard) - newBuf=newBuf..string.sub(buf,lastPos,pos) - if endPos~=nil then - lastPos=endPos - end - until pos==nil - - local file=io.open(filename,"w+") - file:write(newBuf) - file:close() -end -function checkInstalled(dfMod) --try to figure out if installed - if dfMod.checkInstalled then - return dfMod.checkInstalled() - else - if dfMod.raws_list then - for k,v in pairs(dfMod.raws_list) do - if fileExists(dfhack.getDFPath().."/raw/objects/"..v) then - return true,v - end - end - end - if dfMod.patch_entity then - if findGuardsFile(entity_file,dfMod.guard)~=nil then - return true,"entity_default.txt" - end - end - if dfMod.patch_files then - for k,v in pairs(dfMod.patch_files) do - if findGuardsFile(dfhack.getDFPath().."/raw/objects/"..v.filename,dfMod.guard)~=nil then - return true,"v.filename" - end - end - end - if dfMod.patch_init then - if findGuardsFile(init_file,dfMod.guard_init)~=nil then - return true,"init.lua" - end - end - end -end -manager=defclass(manager,gui.FramedScreen) - -function manager:init(args) - self.mods={} - local mods=self.mods - local mlist=dfhack.internal.getDir(mod_dir) - - if mlist==nil or #mlist==0 then - qerror("Mod directory not found! Are you sure it is in:"..mod_dir) - end - for k,v in ipairs(mlist) do - if v~="." and v~=".." then - local f,modData=pcall(dofile,mod_dir.."/".. v .. "/init.lua") - if f then - mods[modData.name]=modData - modData.guard=modData.guard or {">>"..modData.name.." patch","<" - end -end -function manager:formAuthor() - return self.selected.author or "" -end -function manager:selectMod(idx,choice) - self.selected=choice.data - if self.subviews.info then - self.subviews.info:setText(self:formDescription()) - self:updateLayout() - end -end -function manager:updateState() - for k,v in pairs(self.mods) do - v.installed=checkInstalled(v) - end -end -function manager:installCurrent() - self:install(self.selected) -end -function manager:uninstallCurrent() - self:uninstall(self.selected) -end -function manager:install(trgMod,force) - - if trgMod==nil then - qerror 'Mod does not exist' - end - if not force then - local isInstalled,file=checkInstalled(trgMod) -- maybe load from .installed? - if isInstalled then - qerror("Mod already installed. File:"..file) - end - end - print("installing:"..trgMod.name) - if trgMod.pre_install then - trgMod.pre_install(args) - end - if trgMod.raws_list then - for k,v in pairs(trgMod.raws_list) do - copyFile(trgMod.path..v,dfhack.getDFPath().."/raw/objects/"..v) - end - end - if trgMod.patch_entity then - local entity_target="[ENTITY:MOUNTAIN]" --TODO configure - patchFile(entity_file,trgMod.guard,entity_target,trgMod.patch_entity) - end - if trgMod.patch_files then - for k,v in pairs(trgMod.patch_files) do - patchFile(dfhack.getDFPath().."/raw/objects/"..v.filename,trgMod.guard,v.after,v.patch) - end - end - if trgMod.patch_init then - patchInit(init_file,trgMod.guard_init,trgMod.patch_init) - end - if trgMod.patch_dofile then - patchDofile(init_file,trgMod.guard_init,trgMod.patch_dofile,trgMod.path) - end - trgMod.installed=true - - if trgMod.post_install then - trgMod.post_install(self) - end - print("done") -end -function manager:uninstall(trgMod) - print("Uninstalling:"..trgMod.name) - if trgMod.pre_uninstall then - trgMod.pre_uninstall(args) - end - - if trgMod.raws_list then - for k,v in pairs(trgMod.raws_list) do - os.remove(dfhack.getDFPath().."/raw/objects/"..v) - end - end - if trgMod.patch_entity then - unPatchFile(entity_file,trgMod.guard) - end - if trgMod.patch_files then - for k,v in pairs(trgMod.patch_files) do - unPatchFile(dfhack.getDFPath().."/raw/objects/"..v.filename,trgMod.guard) - end - end - if trgMod.patch_init or trgMod.patch_dofile then - unPatchFile(init_file,trgMod.guard_init) - end - - trgMod.installed=false - if trgMod.post_uninstall then - trgMod.post_uninstall(args) - end - print("done") -end -function manager:onInput(keys) - - if keys.LEAVESCREEN then - self:dismiss() - else - self:inputToSubviews(keys) - end - -end -if dfhack.gui.getCurFocus()~='title' then - qerror("Can only be used in title screen") -end -local m=manager{} -m:show() diff --git a/scripts/gui/no-dfhack-init.lua b/scripts/gui/no-dfhack-init.lua deleted file mode 100644 index c4352211d..000000000 --- a/scripts/gui/no-dfhack-init.lua +++ /dev/null @@ -1,37 +0,0 @@ --- Shows the warning about missing configuration file. ---[[=begin - -gui/no-dfhack-init -================== -Shows a warning at startup if no valid :file:`dfhack.init` file is found. - -=end]] -local gui = require 'gui' -local dlg = require 'gui.dialogs' - -local dfhack_init = { text = 'dfhack.init', pen = COLOR_LIGHTCYAN } -local dfhack_init_example = { text = 'dfhack.init-example', pen = COLOR_LIGHTCYAN } - -local message = { - 'The ', dfhack_init, ' configuration file is missing. To customize', NEWLINE, - 'your DFHack installation, rename the ', dfhack_init_example, ' file', NEWLINE, - 'to ', dfhack_init, ' and edit it to suit your needs.', NEWLINE, NEWLINE, - 'For now, ', dfhack_init_example, ' will be used instead.' -} - -dfhack.print('\n') - -for k,v in ipairs(message) do - if type(v) == 'table' then - dfhack.color(v.pen) - dfhack.print(v.text) - else - dfhack.color(COLOR_YELLOW) - dfhack.print(v) - end -end - -dfhack.color(COLOR_RESET) -dfhack.print('\n\n') - -dlg.showMessage('DFHack is not configured', message, COLOR_YELLOW) diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua deleted file mode 100644 index ac10e5dc7..000000000 --- a/scripts/gui/power-meter.lua +++ /dev/null @@ -1,135 +0,0 @@ --- Interface front-end for power-meter plugin. ---[[=begin - -gui/power-meter -=============== -An in-game interface for `power-meter`. - -Bind it to a key (default :kbd:`Ctrl`:kbd:`Shift`:kbd:`M`) and activate -after selecting Pressure Plate in the build menu. - -.. image:: /docs/images/power-meter.png - -The script follows the general look and feel of the regular pressure -plate build configuration page, but configures parameters relevant to -the modded power meter building. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local plugin = require('plugins.power-meter') -local bselector = df.global.ui_build_selector - -PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) - -PowerMeter.focus_path = 'power-meter' - -PowerMeter.ATTRS { - frame_background = false -} - -function PowerMeter:init() - self:assign{ - min_power = 0, max_power = -1, invert = false, - } -end - -function PowerMeter:onShow() - PowerMeter.super.onShow(self) - - -- Send an event to update the errors - bselector.plate_info.flags.whole = 0 - self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER') -end - -function PowerMeter:onRenderBody(dc) - dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN) - dc:seek(1,1):pen(COLOR_WHITE) - dc:string("Power Meter"):newline():newline(1) - dc:string("Placement"):newline():newline(1) - - dc:string("Excess power range:") - - dc:newline(3):key('BUILDING_TRIGGER_MIN_WATER_DOWN') - dc:key('BUILDING_TRIGGER_MIN_WATER_UP') - dc:string(": Min ") - if self.min_power <= 0 then - dc:string("(any)") - else - dc:string(''..self.min_power) - end - - dc:newline(3):key('BUILDING_TRIGGER_MAX_WATER_DOWN') - dc:key('BUILDING_TRIGGER_MAX_WATER_UP') - dc:string(": Max ") - if self.max_power < 0 then - dc:string("(any)") - else - dc:string(''..self.max_power) - end - dc:newline():newline(1) - - dc:key('CUSTOM_I'):string(": ") - if self.invert then - dc:string("Inverted") - else - dc:string("Not inverted") - end -end - -function PowerMeter:onInput(keys) - if keys.CUSTOM_I then - self.invert = not self.invert - elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then - self.min_power = self.min_power + 10 - elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then - self.min_power = math.max(0, self.min_power - 10) - elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then - if self.max_power < 0 then - self.max_power = 0 - else - self.max_power = self.max_power + 10 - end - elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then - self.max_power = math.max(-1, self.max_power - 10) - elseif keys.LEAVESCREEN then - self:dismiss() - self:sendInputToParent('LEAVESCREEN') - elseif keys.SELECT then - if #bselector.errors == 0 then - if not plugin.makePowerMeter( - bselector.plate_info, - self.min_power, self.max_power, self.invert - ) - then - dlg.showMessage( - 'Power Meter', - 'Could not initialize.', COLOR_LIGHTRED - ) - - self:dismiss() - self:sendInputToParent('LEAVESCREEN') - return - end - - self:sendInputToParent('SELECT') - if bselector.stage ~= 1 then - self:dismiss() - end - end - elseif self:propagateMoveKeys(keys) then - return - end -end - -if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap' -or bselector.building_subtype ~= df.trap_type.PressurePlate -then - qerror("This script requires the main dwarfmode view in build pressure plate mode") -end - -local list = PowerMeter() -list:show() diff --git a/scripts/gui/prerelease-warning.lua b/scripts/gui/prerelease-warning.lua deleted file mode 100644 index 113375fd1..000000000 --- a/scripts/gui/prerelease-warning.lua +++ /dev/null @@ -1,141 +0,0 @@ --- Shows the warning about missing configuration file. ---[[=begin - -gui/prerelease-warning -====================== -Shows a warning on world load for pre-release builds. - -With no arguments passed, the warning is shown unless the "do not show again" -option has been selected. With the ``force`` argument, the warning is always -shown. - -=end]] - -local gui = require 'gui' -local dlg = require 'gui.dialogs' -local json = require 'json' -local utils = require 'utils' - -local force = ({...})[1] == 'force' -local config = json.open('dfhack-config/prerelease-warning.json') - -if config.data.hide and not force then - return -end -if not dfhack.isPrerelease() and not force then - qerror('not a prerelease build') -end --- Don't fire during worldgen -if dfhack.internal.getAddress('gametype') and df.global.gametype == df.game_type.NONE and not force then - return -end - -local state = dfhack.getDFHackRelease():lower():match('[a-z]+') -if not utils.invert{'alpha', 'beta', 'rc', 'r'}[state] then - dfhack.printerr('warning: unknown release state: ' .. state) - state = 'unknown' -end - - -message = ({ - alpha = { - 'Warning', - COLOR_YELLOW, - 'This is an alpha build of DFHack. Some structures are likely', NEWLINE, - 'to be incorrect, resulting in crashes or save corruption', NEWLINE, - {pen=COLOR_LIGHTRED, text='Make backups of your saves often!'} - }, - beta = { - 'Warning', - COLOR_YELLOW, - 'This is a beta release of DFHack. It is more stable than an', NEWLINE, - 'alpha release, but bugs are still possible, possibly including', NEWLINE, - 'crashes and save corruption.', NEWLINE, - 'Make backups of your saves beforehand to be safe.' - }, - rc = { - 'Notice', - COLOR_YELLOW, - 'This is a DFHack release candidate. It is fairly stable but', NEWLINE, - 'likely contains newer features that are not fully-tested.', NEWLINE, - 'Crashes are unlikely, but always make backups of your saves', NEWLINE, - 'to be safe.' - }, - r = { - 'Error', - COLOR_LIGHTRED, - 'This release is flagged as a prerelease but named as a', NEWLINE, - 'stable release.', NEWLINE, - {pen=COLOR_LIGHTMAGENTA, text='Please report this to the DFHack team or a pack maintainer.'} - }, - unknown = { - 'Error', - COLOR_LIGHTMAGENTA, - 'Unknown prerelease DFHack build. This should never happen!', NEWLINE, - 'Please report this to the DFHack team or a pack maintainer.' - } -})[state] - -title = table.remove(message, 1) -color = table.remove(message, 1) - -pack_message = [[ - -This should not be enabled by default in a pack. -If you are seeing this message and did not enable/install DFHack -yourself, please report this to your pack's maintainer.]] - -path = dfhack.getHackPath():lower() -if #pack_message > 0 and (path:find('lnp') or path:find('starter') or path:find('newb') or path:find('lazy') or path:find('pack')) then - for _, v in pairs(utils.split_string(pack_message, '\n')) do - table.insert(message, NEWLINE) - table.insert(message, {text=v, pen=COLOR_LIGHTMAGENTA}) - end -end - -dfhack.print('\n') - -for k,v in ipairs(message) do - if type(v) == 'table' then - dfhack.color(v.pen) - dfhack.print(v.text) - else - dfhack.color(color) - dfhack.print(v) - end -end - -dfhack.color(COLOR_RESET) -dfhack.print('\n\n') - -WarningBox = defclass(nil, dlg.MessageBox) - -function WarningBox:getWantedFrameSize() - local w, h = WarningBox.super.getWantedFrameSize(self) - return w, h + 2 -end - -function WarningBox:onRenderFrame(dc,rect) - WarningBox.super.onRenderFrame(self,dc,rect) - dc:pen(COLOR_WHITE):key_pen(COLOR_LIGHTRED) - :seek(rect.x1 + 2, rect.y2 - 2) - :key('CUSTOM_D'):string(': Do not show again') - :advance(10) - :key('LEAVESCREEN'):string('/') - :key('SELECT'):string(': Dismiss') -end - -function WarningBox:onInput(keys) - if keys.CUSTOM_D then - config.data.hide = true - config:write() - keys.LEAVESCREEN = true - end - WarningBox.super.onInput(self, keys) -end - -WarningBox{ - frame_title = title, - text = message, - text_pen = color -}:show() diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua deleted file mode 100644 index 3433f4881..000000000 --- a/scripts/gui/rename.lua +++ /dev/null @@ -1,93 +0,0 @@ --- Rename various objects via gui. ---[[=begin - -gui/rename -========== -Backed by `rename`, this script allows entering the desired name -via a simple dialog in the game ui. - -* ``gui/rename [building]`` in :kbd:`q` mode changes the name of a building. - - .. image:: /docs/images/rename-bld.png - - The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. - It is also possible to rename zones from the :kbd:`i` menu. - -* ``gui/rename [unit]`` with a unit selected changes the nickname. - - Unlike the built-in interface, this works even on enemies and animals. - -* ``gui/rename unit-profession`` changes the selected unit's custom profession name. - - .. image:: /docs/images/rename-prof.png - - Likewise, this can be applied to any unit, and when used on animals it overrides - their species string. - -The ``building`` or ``unit`` options are automatically assumed when in relevant UI state. - -The example config binds building/unit rename to :kbd:`Ctrl`:kbd:`Shift`:kbd:`N`, and -unit profession change to :kbd:`Ctrl`:kbd:`Shift`:kbd:`T`. - -=end]] -local gui = require 'gui' -local dlg = require 'gui.dialogs' -local plugin = require 'plugins.rename' - -local mode = ... -local focus = dfhack.gui.getCurFocus() - -local function verify_mode(expected) - if mode ~= nil and mode ~= expected then - qerror('Invalid UI state for mode '..mode) - end -end - -local unit = dfhack.gui.getSelectedUnit(true) -local building = dfhack.gui.getSelectedBuilding(true) - -if building and (not unit or mode == 'building') then - verify_mode('building') - - if plugin.canRenameBuilding(building) then - dlg.showInputPrompt( - 'Rename Building', - 'Enter a new name for the building:', COLOR_GREEN, - building.name, - curry(plugin.renameBuilding, building) - ) - else - dlg.showMessage( - 'Rename Building', - 'Cannot rename this type of building.', COLOR_LIGHTRED - ) - end -elseif unit then - if mode == 'unit-profession' then - dlg.showInputPrompt( - 'Rename Unit', - 'Enter a new profession for the unit:', COLOR_GREEN, - unit.custom_profession, - function(newval) - unit.custom_profession = newval - end - ) - else - verify_mode('unit') - - local vname = dfhack.units.getVisibleName(unit) - local vnick = '' - if vname and vname.has_name then - vnick = vname.nickname - end - - dlg.showInputPrompt( - 'Rename Unit', - 'Enter a new nickname for the unit:', COLOR_GREEN, - vnick, - curry(dfhack.units.setNickname, unit) - ) - end -elseif mode then - verify_mode(nil) -end diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua deleted file mode 100644 index ac6890317..000000000 --- a/scripts/gui/room-list.lua +++ /dev/null @@ -1,259 +0,0 @@ --- Browses rooms owned by a unit. ---[[=begin - -gui/room-list -============= -To use, bind to a key (the example config uses :kbd:`Alt`:kbd:`R`) and activate in :kbd:`q` mode, -either immediately or after opening the assign owner page. - -.. image:: /docs/images/room-list.png - -The script lists other rooms owned by the same owner, or by the unit selected in the assign -list, and allows unassigning them. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' - -local room_type_table = { - [df.building_bedst] = { token = 'bed', qidx = 2, tile = 233 }, - [df.building_tablest] = { token = 'table', qidx = 3, tile = 209 }, - [df.building_chairst] = { token = 'chair', qidx = 4, tile = 210 }, - [df.building_coffinst] = { token = 'coffin', qidx = 5, tile = 48 }, -} - -local room_quality_table = { - { 1, 'Meager Quarters', 'Meager Dining Room', 'Meager Office', 'Grave' }, - { 100, 'Modest Quarters', 'Modest Dining Room', 'Modest Office', "Servant's Burial Chamber" }, - { 250, 'Quarters', 'Dining Room', 'Office', 'Burial Chamber' }, - { 500, 'Decent Quarters', 'Decent Dining Room', 'Decent Office', 'Tomb' }, - { 1000, 'Fine Quarters', 'Fine Dining Room', 'Splendid Office', 'Fine Tomb' }, - { 1500, 'Great Bedroom', 'Great Dining Room', 'Throne Room', 'Mausoleum' }, - { 2500, 'Grand Bedroom', 'Grand Dining Room', 'Opulent Throne Room', 'Grand Mausoleum' }, - { 10000, 'Royal Bedroom', 'Royal Dining Room', 'Royal Throne Room', 'Royal Mausoleum' } -} - -function getRoomName(building, unit) - local info = room_type_table[building._type] - if not info or not building.is_room then - return utils.getBuildingName(building) - end - - local quality = building:getRoomValue(unit) - local row = room_quality_table[1] - for _,v in ipairs(room_quality_table) do - if v[1] <= quality then - row = v - else - break - end - end - return row[info.qidx] -end - -function makeRoomEntry(bld, unit, is_spouse) - local info = room_type_table[bld._type] or {} - - return { - obj = bld, - token = info.token or '?', - tile = info.tile or '?', - caption = getRoomName(bld, unit), - can_use = (not is_spouse or bld:canUseSpouseRoom()), - owner = unit - } -end - -function listRooms(unit, spouse) - local rv = {} - for _,v in pairs(unit.owned_buildings) do - if v.owner == unit then - rv[#rv+1] = makeRoomEntry(v, unit, spouse) - end - end - return rv -end - -function concat_lists(...) - local rv = {} - for i = 1,select('#',...) do - local v = select(i,...) - if v then - for _,x in ipairs(v) do rv[#rv+1] = x end - end - end - return rv -end - -RoomList = defclass(RoomList, guidm.MenuOverlay) - -RoomList.focus_path = 'room-list' - -RoomList.ATTRS{ unit = DEFAULT_NIL } - -function RoomList:init(info) - local unit = info.unit - local base_bld = df.global.world.selected_building - - self:assign{ - base_building = base_bld, - items = {}, selected = 1, - own_rooms = {}, spouse_rooms = {} - } - - self.old_viewport = self:getViewport() - self.old_cursor = guidm.getCursorPos() - - if unit then - self.own_rooms = listRooms(unit) - self.spouse = df.unit.find(unit.relations.spouse_id) - if self.spouse then - self.spouse_rooms = listRooms(self.spouse, unit) - end - self.items = concat_lists(self.own_rooms, self.spouse_rooms) - end - - if base_bld then - for i,v in ipairs(self.items) do - if v.obj == base_bld then - self.selected = i - v.tile = 26 - goto found - end - end - self.base_item = makeRoomEntry(base_bld, unit) - self.base_item.owner = unit - self.base_item.old_owner = base_bld.owner - self.base_item.tile = 26 - self.items = concat_lists({self.base_item}, self.items) - ::found:: - end -end - -local sex_char = { [0] = 12, [1] = 11 } - -function drawUnitName(dc, unit) - dc:pen(COLOR_GREY) - if unit then - local color = dfhack.units.getProfessionColor(unit) - dc:char(sex_char[unit.sex] or '?'):advance(1):pen(color) - - local vname = dfhack.units.getVisibleName(unit) - if vname and vname.has_name then - dc:string(dfhack.TranslateName(vname)..', ') - end - dc:string(dfhack.units.getProfessionName(unit)) - else - dc:string("No Owner Assigned") - end -end - -function drawRoomEntry(dc, entry, selected) - local color = COLOR_GREEN - if not entry.can_use then - color = COLOR_RED - elseif entry.obj.owner ~= entry.owner or not entry.owner then - color = COLOR_CYAN - end - dc:pen{fg = color, bold = (selected == entry)} - dc:char(entry.tile):advance(1):string(entry.caption) -end - -function can_modify(sel_item) - return sel_item and sel_item.owner - and sel_item.can_use and not sel_item.owner.flags1.dead -end - -function RoomList:onRenderBody(dc) - local sel_item = self.items[self.selected] - - dc:clear():seek(1,1) - drawUnitName(dc, self.unit) - - if self.base_item then - dc:newline():newline(2) - drawRoomEntry(dc, self.base_item, sel_item) - end - if #self.own_rooms > 0 then - dc:newline() - for _,v in ipairs(self.own_rooms) do - dc:newline(2) - drawRoomEntry(dc, v, sel_item) - end - end - if #self.spouse_rooms > 0 then - dc:newline():newline(1) - drawUnitName(dc, self.spouse) - - dc:newline() - for _,v in ipairs(self.spouse_rooms) do - dc:newline(2) - drawRoomEntry(dc, v, sel_item) - end - end - if self.unit and #self.own_rooms == 0 and #self.spouse_rooms == 0 then - dc:newline():newline(2):string("No already assigned rooms.", COLOR_LIGHTRED) - end - - dc:newline():newline(1):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back") - - if can_modify(sel_item) then - dc:string(", "):key('SELECT') - if sel_item.obj.owner == sel_item.owner then - dc:string(": Unassign") - else - dc:string(": Assign") - end - end -end - -function RoomList:changeSelected(delta) - if #self.items <= 1 then return end - self.selected = 1 + (self.selected + delta - 1) % #self.items - self:selectBuilding(self.items[self.selected].obj) -end - -function RoomList:onInput(keys) - local sel_item = self.items[self.selected] - - if keys.SECONDSCROLL_UP then - self:changeSelected(-1) - elseif keys.SECONDSCROLL_DOWN then - self:changeSelected(1) - elseif keys.LEAVESCREEN then - self:dismiss() - - if self.base_building then - if not sel_item or self.base_building ~= sel_item.obj then - self:selectBuilding(self.base_building, self.old_cursor, self.old_view) - end - if self.unit and self.base_building.owner == self.unit then - df.global.ui_building_in_assign = false - end - end - elseif keys.SELECT then - if can_modify(sel_item) then - local owner = sel_item.owner - if sel_item.obj.owner == owner then - owner = sel_item.old_owner - end - dfhack.buildings.setOwner(sel_item.obj, owner) - end - elseif self:simulateViewScroll(keys) then - return - end -end - -local focus = dfhack.gui.getCurFocus() - -if focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then - local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] - RoomList{ unit = unit }:show() -elseif string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then - local base = df.global.world.selected_building - RoomList{ unit = base.owner }:show() -else - qerror("This script requires the main dwarfmode view in 'q' mode") -end diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua deleted file mode 100644 index 8ebfe0dc4..000000000 --- a/scripts/gui/siege-engine.lua +++ /dev/null @@ -1,536 +0,0 @@ --- Front-end for the siege engine plugin. ---[[=begin - -gui/siege-engine -================ -An in-game interface for `siege-engine`. - -Bind it to a key (the example config uses :kbd:`Alt`:kbd:`a`) and -activate after selecting a siege engine in :kbd:`q` mode. - -.. image:: /docs/images/siege-engine.png - -The main mode displays the current target, selected ammo item -type, linked stockpiles and the allowed operator skill range. The -map tile color is changed to signify if it can be hit by the -selected engine: green for fully reachable, blue for out of -range, red for blocked, yellow for partially blocked. - -Pressing :kbd:`r` changes into the target selection mode, which -works by highlighting two points with :kbd:`Enter` like all -designations. When a target area is set, the engine projectiles -are aimed at that area, or units within it (this doesn't actually -change the original aiming code, instead the projectile -trajectory parameters are rewritten as soon as it appears). - -After setting the target in this way for one engine, you can -'paste' the same area into others just by pressing :kbd:`p` in -the main page of this script. The area to paste is kept until you -quit DF, or select another area manually. - -Pressing :kbd:`t` switches to a mode for selecting a stockpile to -take ammo from. - -Exiting from the siege engine script via :kbd:`Esc` reverts the -view to the state prior to starting the script. -:kbd:`Shift`:kbd:`Esc` retains the current viewport, and also -exits from the :kbd:`q` mode to main menu. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local dlg = require 'gui.dialogs' - -local plugin = require 'plugins.siege-engine' -local wmap = df.global.world.map - -local LEGENDARY = df.skill_rating.Legendary - --- Globals kept between script calls -last_target_min = last_target_min or nil -last_target_max = last_target_max or nil - -local item_choices = { - { caption = 'boulders (default)', item_type = df.item_type.BOULDER }, - { caption = 'blocks', item_type = df.item_type.BLOCKS }, - { caption = 'weapons', item_type = df.item_type.WEAPON }, - { caption = 'trap components', item_type = df.item_type.TRAPCOMP }, - { caption = 'bins', item_type = df.item_type.BIN }, - { caption = 'barrels', item_type = df.item_type.BARREL }, - { caption = 'cages', item_type = df.item_type.CAGE }, - { caption = 'anything', item_type = -1 }, -} - -local item_choice_idx = {} -for i,v in ipairs(item_choices) do - item_choice_idx[v.item_type] = i -end - -SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) - -SiegeEngine.focus_path = 'siege-engine' - -SiegeEngine.ATTRS{ building = DEFAULT_NIL } - -function SiegeEngine:init() - self:assign{ - center = utils.getBuildingCenter(self.building), - selected_pile = 1, - mode_main = { - render = self:callback 'onRenderBody_main', - input = self:callback 'onInput_main', - }, - mode_aim = { - render = self:callback 'onRenderBody_aim', - input = self:callback 'onInput_aim', - }, - mode_pile = { - render = self:callback 'onRenderBody_pile', - input = self:callback 'onInput_pile', - } - } -end - -function SiegeEngine:onShow() - SiegeEngine.super.onShow(self) - - self.old_cursor = guidm.getCursorPos() - self.old_viewport = self:getViewport() - - self.mode = self.mode_main - self:showCursor(false) -end - -function SiegeEngine:onDestroy() - if self.save_profile then - plugin.saveWorkshopProfile(self.building) - end - if not self.no_select_building then - self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) - end -end - -function SiegeEngine:onGetSelectedBuilding() - return df.global.world.selected_building -end - -function SiegeEngine:showCursor(enable) - local cursor = guidm.getCursorPos() - if cursor and not enable then - self.cursor = cursor - self.target_select_first = nil - guidm.clearCursorPos() - elseif not cursor and enable then - local view = self:getViewport() - cursor = self.cursor - if not cursor or not view:isVisible(cursor) then - cursor = view:getCenter() - end - self.cursor = nil - guidm.setCursorPos(cursor) - end -end - -function SiegeEngine:centerViewOn(pos) - local cursor = guidm.getCursorPos() - if cursor then - guidm.setCursorPos(pos) - else - self.cursor = pos - end - self:getViewport():centerOn(pos):set() -end - -function SiegeEngine:zoomToTarget() - local target_min, target_max = plugin.getTargetArea(self.building) - if target_min then - local cx = math.floor((target_min.x + target_max.x)/2) - local cy = math.floor((target_min.y + target_max.y)/2) - local cz = math.floor((target_min.z + target_max.z)/2) - local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz)) - self:centerViewOn(pos) - end -end - -function paint_target_grid(dc, view, origin, p1, p2) - local r1, sz, r2 = guidm.getSelectionRange(p1, p2) - - if view.z < r1.z or view.z > r2.z then - return - end - - local p1 = view:tileToScreen(r1) - local p2 = view:tileToScreen(r2) - local org = view:tileToScreen(origin) - dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true } - - -- Frame - dc:fill(p1.x,p1.y,p1.x,p2.y) - dc:fill(p1.x,p1.y,p2.x,p1.y) - dc:fill(p2.x,p1.y,p2.x,p2.y) - dc:fill(p1.x,p2.y,p2.x,p2.y) - - -- Grid - local gxmin = org.x+10*math.ceil((p1.x-org.x)/10) - local gxmax = org.x+10*math.floor((p2.x-org.x)/10) - local gymin = org.y+10*math.ceil((p1.y-org.y)/10) - local gymax = org.y+10*math.floor((p2.y-org.y)/10) - for x = gxmin,gxmax,10 do - for y = gymin,gymax,10 do - dc:fill(p1.x,y,p2.x,y) - dc:fill(x,p1.y,x,p2.y) - end - end -end - -function SiegeEngine:renderTargetView(target_min, target_max) - local view = self:getViewport() - local map = self.df_layout.map - local map_dc = gui.Painter.new(map) - - plugin.paintAimScreen( - self.building, view:getPos(), - xy2pos(map.x1, map.y1), view:getSize() - ) - - if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then - paint_target_grid(map_dc, view, self.center, target_min, target_max) - end - - local cursor = guidm.getCursorPos() - if cursor then - local cx, cy, cz = pos2xyz(view:tileToScreen(cursor)) - if cz == 0 then - map_dc:seek(cx,cy):char('X', COLOR_YELLOW) - end - end -end - -function SiegeEngine:scrollPiles(delta) - local links = plugin.getStockpileLinks(self.building) - if links then - self.selected_pile = 1+(self.selected_pile+delta-1) % #links - return links[self.selected_pile] - end -end - -function SiegeEngine:renderStockpiles(dc, links, nlines) - local idx = (self.selected_pile-1) % #links - local page = math.floor(idx/nlines) - for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do - local color = COLOR_BROWN - if i == idx then - color = COLOR_YELLOW - end - dc:newline(2):string(utils.getBuildingName(links[i+1]), color) - end -end - -function SiegeEngine:onRenderBody_main(dc) - dc:newline(1):pen(COLOR_WHITE):string("Target: ") - - local target_min, target_max = plugin.getTargetArea(self.building) - if target_min then - dc:string( - (target_max.x-target_min.x+1).."x".. - (target_max.y-target_min.y+1).."x".. - (target_max.z-target_min.z+1).." Rect" - ) - else - dc:string("None (default)") - end - - dc:newline(3):key('CUSTOM_R'):string(": Rectangle") - if last_target_min then - dc:string(", "):key('CUSTOM_P'):string(": Paste") - end - dc:newline(3) - if target_min then - dc:key('CUSTOM_X'):string(": Clear, ") - dc:key('CUSTOM_Z'):string(": Zoom") - end - - dc:newline():newline(1) - if self.building.type == df.siegeengine_type.Ballista then - dc:string("Uses ballista arrows") - else - local item = plugin.getAmmoItem(self.building) - dc:key('CUSTOM_U'):string(": Use ") - if item_choice_idx[item] then - dc:string(item_choices[item_choice_idx[item]].caption) - else - dc:string(df.item_type[item]) - end - end - - dc:newline():newline(1) - dc:key('CUSTOM_T'):string(": Take from stockpile"):newline(3) - local links = plugin.getStockpileLinks(self.building) - local bottom = dc.height - 5 - if links then - dc:key('CUSTOM_D'):string(": Delete, ") - dc:key('CUSTOM_O'):string(": Zoom"):newline() - self:renderStockpiles(dc, links, bottom-2-dc:cursorY()) - dc:newline():newline() - end - - local prof = self.building:getWorkshopProfile() or {} - dc:seek(1,math.max(dc:cursorY(),19)) - dc:key('CUSTOM_G'):key('CUSTOM_H'):key('CUSTOM_J'):key('CUSTOM_K') - dc:string(': ') - dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-') - dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption) - dc:newline():newline() - - if self.target_select_first then - self:renderTargetView(self.target_select_first, guidm.getCursorPos()) - else - self:renderTargetView(target_min, target_max) - end -end - -function SiegeEngine:setTargetArea(p1, p2) - self.target_select_first = nil - - if not plugin.setTargetArea(self.building, p1, p2) then - dlg.showMessage( - 'Set Target Area', - 'Could not set the target area', COLOR_LIGHTRED - ) - else - last_target_min = p1 - last_target_max = p2 - end -end - -function SiegeEngine:setAmmoItem(choice) - if self.building.type == df.siegeengine_type.Ballista then - return - end - - if not plugin.setAmmoItem(self.building, choice.item_type) then - dlg.showMessage( - 'Set Ammo Item', - 'Could not set the ammo item', COLOR_LIGHTRED - ) - end -end - -function SiegeEngine:onInput_main(keys) - if keys.CUSTOM_R then - self:showCursor(true) - self.target_select_first = nil - self.mode = self.mode_aim - elseif keys.CUSTOM_P and last_target_min then - self:setTargetArea(last_target_min, last_target_max) - elseif keys.CUSTOM_U then - local item = plugin.getAmmoItem(self.building) - local idx = 1 + (item_choice_idx[item] or 0) % #item_choices - self:setAmmoItem(item_choices[idx]) - elseif keys.CUSTOM_Z then - self:zoomToTarget() - elseif keys.CUSTOM_X then - plugin.clearTargetArea(self.building) - elseif keys.SECONDSCROLL_UP then - self:scrollPiles(-1) - elseif keys.SECONDSCROLL_DOWN then - self:scrollPiles(1) - elseif keys.CUSTOM_D then - local pile = self:scrollPiles(0) - if pile then - plugin.removeStockpileLink(self.building, pile) - end - elseif keys.CUSTOM_O then - local pile = self:scrollPiles(0) - if pile then - self:centerViewOn(utils.getBuildingCenter(pile)) - end - elseif keys.CUSTOM_T then - self:showCursor(true) - self.mode = self.mode_pile - self:sendInputToParent('CURSOR_DOWN_Z') - self:sendInputToParent('CURSOR_UP_Z') - elseif keys.CUSTOM_G then - local prof = plugin.saveWorkshopProfile(self.building) - prof.min_level = math.max(0, prof.min_level-1) - plugin.saveWorkshopProfile(self.building) - elseif keys.CUSTOM_H then - local prof = plugin.saveWorkshopProfile(self.building) - prof.min_level = math.min(LEGENDARY, prof.min_level+1) - plugin.saveWorkshopProfile(self.building) - elseif keys.CUSTOM_J then - local prof = plugin.saveWorkshopProfile(self.building) - prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1) - plugin.saveWorkshopProfile(self.building) - elseif keys.CUSTOM_K then - local prof = plugin.saveWorkshopProfile(self.building) - prof.max_level = math.min(LEGENDARY, prof.max_level+1) - if prof.max_level >= LEGENDARY then prof.max_level = 3000 end - plugin.saveWorkshopProfile(self.building) - elseif self:simulateViewScroll(keys) then - self.cursor = nil - else - return false - end - return true -end - -local status_table = { - ok = { pen = COLOR_GREEN, msg = "Target accessible" }, - out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, - blocked = { pen = COLOR_RED, msg = "Target obstructed" }, - semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" }, -} - -function SiegeEngine:onRenderBody_aim(dc) - local cursor = guidm.getCursorPos() - local first = self.target_select_first - - dc:newline(1):string('Select target rectangle'):newline() - - local info = status_table[plugin.getTileStatus(self.building, cursor)] - if info then - dc:newline(2):string(info.msg, info.pen) - else - dc:newline(2):string('ERROR', COLOR_RED) - end - - dc:newline():newline(1):key('SELECT') - if first then - dc:string(": Finish rectangle") - else - dc:string(": Start rectangle") - end - dc:newline() - - local target_min, target_max = plugin.getTargetArea(self.building) - if target_min then - dc:newline(1):key('CUSTOM_Z'):string(": Zoom to current target") - end - - if first then - self:renderTargetView(first, cursor) - else - local target_min, target_max = plugin.getTargetArea(self.building) - self:renderTargetView(target_min, target_max) - end -end - -function SiegeEngine:onInput_aim(keys) - if keys.SELECT then - local cursor = guidm.getCursorPos() - if self.target_select_first then - self:setTargetArea(self.target_select_first, cursor) - - self.mode = self.mode_main - self:showCursor(false) - else - self.target_select_first = cursor - end - elseif keys.CUSTOM_Z then - self:zoomToTarget() - elseif keys.LEAVESCREEN then - self.mode = self.mode_main - self:showCursor(false) - elseif self:simulateCursorMovement(keys) then - self.cursor = nil - else - return false - end - return true -end - -function SiegeEngine:onRenderBody_pile(dc) - dc:newline(1):string('Select pile to take from'):newline():newline(2) - - local sel = df.global.world.selected_building - - if df.building_stockpilest:is_instance(sel) then - dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1) - - if plugin.isLinkedToPile(self.building, sel) then - dc:string("Already taking from here"):newline():newline(2) - dc:key('CUSTOM_D'):string(": Delete link") - else - dc:key('SELECT'):string(": Take from this pile") - end - elseif sel then - dc:string(utils.getBuildingName(sel), COLOR_DARKGREY) - dc:newline():newline(1) - dc:string("Not a stockpile",COLOR_LIGHTRED) - else - dc:string("No building selected", COLOR_DARKGREY) - end -end - -function SiegeEngine:onInput_pile(keys) - if keys.SELECT then - local sel = df.global.world.selected_building - if df.building_stockpilest:is_instance(sel) - and not plugin.isLinkedToPile(self.building, sel) then - plugin.addStockpileLink(self.building, sel) - - df.global.world.selected_building = self.building - self.mode = self.mode_main - self:showCursor(false) - end - elseif keys.CUSTOM_D then - local sel = df.global.world.selected_building - if df.building_stockpilest:is_instance(sel) then - plugin.removeStockpileLink(self.building, sel) - end - elseif keys.LEAVESCREEN then - df.global.world.selected_building = self.building - self.mode = self.mode_main - self:showCursor(false) - elseif self:propagateMoveKeys(keys) then - -- - else - return false - end - return true -end - -function SiegeEngine:onRenderBody(dc) - dc:clear() - dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline() - - self.mode.render(dc) - - dc:seek(1, math.max(dc:cursorY(), 21)):pen(COLOR_WHITE) - dc:key('LEAVESCREEN'):string(": Back, ") - dc:key('CUSTOM_C'):string(": Recenter") -end - -function SiegeEngine:onInput(keys) - if self.mode.input(keys) then - -- - elseif keys.CUSTOM_C then - self:centerViewOn(self.center) - elseif keys.LEAVESCREEN then - self:dismiss() - elseif keys.LEAVESCREEN_ALL then - self:dismiss() - self.no_select_building = true - guidm.clearCursorPos() - df.global.ui.main.mode = df.ui_sidebar_mode.Default - df.global.world.selected_building = nil - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then - qerror("This script requires a siege engine selected in 'q' mode") -end - -local building = df.global.world.selected_building - -if not df.building_siegeenginest:is_instance(building) then - qerror("A siege engine must be selected") -end -if building:getBuildStage() < building:getMaxBuildStage() then - qerror("This engine is not completely built yet") -end - -local list = SiegeEngine{ building = building } -list:show() diff --git a/scripts/gui/stockpiles.lua b/scripts/gui/stockpiles.lua deleted file mode 100644 index 4e2f28281..000000000 --- a/scripts/gui/stockpiles.lua +++ /dev/null @@ -1,78 +0,0 @@ --- lave/load stockpile settings with a GUI ---[[=begin - -gui/stockpiles -============== -An in-game interface for `stocksettings`, to -load and save stockpile settings from the :kbd:`q` menu. - -Usage: - -:gui/stockpiles -save: to save the current stockpile -:gui/stockpiles -load: to load settings into the current stockpile -:gui/stockpiles -dir : set the default directory to save settings into -:gui/stockpiles -help: to see this message - -Don't forget to ``enable stockpiles`` and create the ``stocksettings`` directory in -the DF folder before trying to use the GUI. - -=end]] -local stock = require 'plugins.stockpiles' - -function check_enabled() - return stock.isEnabled() -end - -function world_guard() - if not dfhack.isMapLoaded() then - qerror("World is not loaded") - return false - end - return true -end - -function guard() - if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then - qerror("This script requires a stockpile selected in the 'q' mode") - return false - end - return true -end - -utils = require('utils') -validArgs = validArgs or utils.invert({ - 'help', - 'load', - 'save', - 'dir', -}) - -args = utils.processArgs({...}, validArgs) - -function usage() - print("") - print("Stockpile Settings. Arguments: ") - print("-save to save the current stockpile") - print("-load to load settings into the current stockpile") - print("-dir set the default directory to save settings into") - if dfhack.isMapLoaded() then - print(" Current directory is: " .. stock.get_path()) - end - print("") -end - -if not check_enabled() then - qerror("Stockpiles plugin not enabled. Enable it with: enable stockpiles") -elseif args.load then - if not guard() then return end - stock.load_settings() -elseif args.save then - if not guard() then return end - local sp = dfhack.gui.getSelectedBuilding(true) - stock.save_settings(sp) -elseif args.dir then - if not world_guard() then return end - stock.set_path(args.dir) -else - usage() -end diff --git a/scripts/gui/unit-info-viewer.lua b/scripts/gui/unit-info-viewer.lua deleted file mode 100644 index 1d7cb763d..000000000 --- a/scripts/gui/unit-info-viewer.lua +++ /dev/null @@ -1,800 +0,0 @@ --- unit-info-viewer.lua --- Displays age, birth, maxage, shearing, milking, grazing, egg laying, body size, and death info about a unit. Recommended keybinding Alt-I --- version 1.04 --- original author: Kurik Amudnil --- edited by expwnent ---[[=begin - -gui/unit-info-viewer -==================== -Displays age, birth, maxage, shearing, milking, grazing, egg laying, body size, -and death info about a unit. Recommended keybinding :kbd:`Alt`:kbd:`I`. - -=end]] -local gui = require 'gui' -local widgets =require 'gui.widgets' -local utils = require 'utils' - -local DEBUG = false -if DEBUG then print('-----') end - -local pens = { - BLACK = dfhack.pen.parse{fg=COLOR_BLACK,bg=0}, - BLUE = dfhack.pen.parse{fg=COLOR_BLUE,bg=0}, - GREEN = dfhack.pen.parse{fg=COLOR_GREEN,bg=0}, - CYAN = dfhack.pen.parse{fg=COLOR_CYAN,bg=0}, - RED = dfhack.pen.parse{fg=COLOR_RED,bg=0}, - MAGENTA = dfhack.pen.parse{fg=COLOR_MAGENTA,bg=0}, - BROWN = dfhack.pen.parse{fg=COLOR_BROWN,bg=0}, - GREY = dfhack.pen.parse{fg=COLOR_GREY,bg=0}, - DARKGREY = dfhack.pen.parse{fg=COLOR_DARKGREY,bg=0}, - LIGHTBLUE = dfhack.pen.parse{fg=COLOR_LIGHTBLUE,bg=0}, - LIGHTGREEN = dfhack.pen.parse{fg=COLOR_LIGHTGREEN,bg=0}, - LIGHTCYAN = dfhack.pen.parse{fg=COLOR_LIGHTCYAN,bg=0}, - LIGHTRED = dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}, - LIGHTMAGENTA = dfhack.pen.parse{fg=COLOR_LIGHTMAGENTA,bg=0}, - YELLOW = dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}, - WHITE = dfhack.pen.parse{fg=COLOR_WHITE,bg=0}, -} - -function getUnit_byID(id) -- get unit by id from units.all via binsearch - if type(id) == 'number' then - -- (vector,key,field,cmpfun,min,max) { item/nil , found true/false , idx/insert at } - return utils.binsearch(df.global.world.units.all,id,'id') - end -end - -function getUnit_byVS(silent) -- by view screen mode - silent = silent or false - -- if not world loaded, return nil ? - local u,tmp -- u: the unit to return ; tmp: temporary for intermediate tests/return values - local v = dfhack.gui.getCurViewscreen() - u = dfhack.gui.getSelectedUnit(true) -- supports gui scripts/plugin that provide a hook for getSelectedUnit() - if u then - return u - -- else: contexts not currently supported by dfhack.gui.getSelectedUnit() - elseif df.viewscreen_dwarfmodest:is_instance(v) then - tmp = df.global.ui.main.mode - if tmp == 17 or tmp == 42 or tmp == 43 then - -- context: @dwarfmode/QueryBuiding/Some/Cage -- (q)uery cage - -- context: @dwarfmode/ZonesPenInfo/AssignUnit -- i (zone) -> pe(N) - -- context: @dwarfmode/ZonesPitInfo -- i (zone) -> (P)it - u = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] - elseif tmp == 49 and df.global.ui.burrows.in_add_units_mode then - -- @dwarfmode/Burrows/AddUnits - u = df.global.ui.burrows.list_units[ df.global.ui.burrows.unit_cursor_pos ] - - elseif df.global.ui.follow_unit ~= -1 then - -- context: follow unit mode - u = getUnit_byID(df.global.ui.follow_unit) - end -- end viewscreen_dwarfmodest - elseif df.viewscreen_petst:is_instance(v) then - -- context: @pet/List/Unit -- z (status) -> animals - if v.mode == 0 then -- List - if not v.is_vermin[v.cursor] then - u = v.animal[v.cursor].unit - end - --elseif v.mode = 1 then -- training knowledge (no unit reference) - elseif v.mode == 2 then -- select trainer - u = v.trainer_unit[v.trainer_cursor] - end - elseif df.viewscreen_layer_workshop_profilest:is_instance(v) then - -- context: @layer_workshop_profile/Unit -- (q)uery workshop -> (P)rofile -- df.global.ui.main.mode == 17 - u = v.workers[v.layer_objects[0].cursor] - elseif df.viewscreen_layer_overall_healthst:is_instance(v) then - -- context @layer_overall_health/Units -- z -> health - u = v.unit[v.layer_objects[0].cursor] - elseif df.viewscreen_layer_militaryst:is_instance(v) then - local PG_ASSIGNMENTS = 0 - local PG_EQUIPMENT = 2 - local TB_POSITIONS = 1 - local TB_CANDIDATES = 2 - -- layer_objects[0: squads list; 1: positions list; 2: candidates list] - -- page 0:positions/assignments 1:alerts 2:equipment 3:uniforms 4:supplies 5:ammunition - if v.page == PG_ASSIGNMENTS and v.layer_objects[TB_CANDIDATES].enabled and v.layer_objects[TB_CANDIDATES].active then - -- context: @layer_military/Positions/Position/Candidates -- m -> Candidates - u = v.positions.candidates[v.layer_objects[TB_CANDIDATES].cursor] - elseif v.page == PG_ASSIGNMENTS and v.layer_objects[TB_POSITIONS].enabled and v.layer_objects[TB_POSITIONS].active then - -- context: @layer_military/Positions/Position -- m -> Positions - u = v.positions.assigned[v.layer_objects[TB_POSITIONS].cursor] - elseif v.page == PG_EQUIPMENT and v.layer_objects[TB_POSITIONS].enabled and v.layer_objects[TB_POSITIONS].active then - -- context: @layer_military/Equip/Customize/View/Position -- m -> (e)quip -> Positions - -- context: @layer_military/Equip/Uniform/Positions -- m -> (e)quip -> assign (U)niforms -> Positions - u = v.equip.units[v.layer_objects[TB_POSITIONS].cursor] - end - elseif df.viewscreen_layer_noblelistst:is_instance(v) then - if v.mode == 0 then - -- context: @layer_noblelist/List -- (n)obles - u = v.info[v.layer_objects[v.mode].cursor].unit - elseif v.mode == 1 then - -- context: @layer_noblelist/Appoint -- (n)obles -> (r)eplace - u = v.candidates[v.layer_objects[v.mode].cursor].unit - end - elseif df.viewscreen_unitst:is_instance(v) then - -- @unit -- (v)unit -> z ; loo(k) -> enter ; (n)obles -> enter ; others - u = v.unit - elseif df.viewscreen_customize_unitst:is_instance(v) then - -- @customize_unit -- @unit -> y - u = v.unit - elseif df.viewscreen_layer_unit_healthst:is_instance(v) then - -- @layer_unit_health -- @unit -> h ; @layer_overall_health/Units -> enter - if df.viewscreen_layer_overall_healthst:is_instance(v.parent) then - -- context @layer_overall_health/Units -- z (status)-> health - u = v.parent.unit[v.parent.layer_objects[0].cursor] - elseif df.viewscreen_unitst:is_instance(v.parent) then - -- @unit -- (v)unit -> z ; loo(k) -> enter ; (n)obles -> enter ; others - u = v.parent.unit - end - elseif df.viewscreen_textviewerst:is_instance(v) then - -- @textviewer -- @unit -> enter (thoughts and preferences) - if df.viewscreen_unitst:is_instance(v.parent) then - -- @unit -- @unit -> enter (thoughts and preferences) - u = v.parent.unit - elseif df.viewscreen_itemst:is_instance(v.parent) then - tmp = v.parent.entry_ref[v.parent.cursor_pos] - if df.general_ref_unit:is_instance(tmp) then -- general_ref_unit and derived ; general_ref_contains_unitst ; others? - u = getUnit_byID(tmp.unit_id) - end - elseif df.viewscreen_dwarfmodest:is_instance(v.parent) then - tmp = df.global.ui.main.mode - if tmp == 24 then -- (v)iew units {g,i,p,w} -> z (thoughts and preferences) - -- context: @dwarfmode/ViewUnits/... - --if df.global.ui_selected_unit > -1 then -- -1 = 'no units nearby' - u = df.global.world.units.active[df.global.ui_selected_unit] - --end - elseif tmp == 25 then -- loo(k) unit -> enter (thoughs and preferences) - -- context: @dwarfmode/LookAround/Unit - tmp = df.global.ui_look_list.items[df.global.ui_look_cursor] - if tmp.type == 2 then -- 0:item 1:terrain >>2: unit<< 3:building 4:colony/vermin 7:spatter - u = tmp.unit - end - end - elseif df.viewscreen_unitlistst:is_instance(v.parent) then -- (u)nit list -> (v)iew unit (not citizen) - -- context: @unitlist/Citizens ; @unitlist/Livestock ; @unitlist/Others ; @unitlist/Dead - u = v.parent.units[v.parent.page][ v.parent.cursor_pos[v.parent.page] ] - end - end -- switch viewscreen - if not u and not silent then - dfhack.printerr('No unit is selected in the UI or context not supported.') - end - return u -end -- getUnit_byVS() - ---http://lua-users.org/wiki/StringRecipes ---------- -function str2FirstUpper(str) - return str:gsub("^%l", string.upper) -end - --------------------------------------------------- ---http://lua-users.org/wiki/StringRecipes ---------- -local function tchelper(first, rest) - return first:upper()..rest:lower() -end - --- Add extra characters to the pattern if you need to. _ and ' are --- found in the middle of identifiers and English words. --- We must also put %w_' into [%w_'] to make it handle normal stuff --- and extra stuff the same. --- This also turns hex numbers into, eg. 0Xa7d4 -function str2TitleCase(str) - return str:gsub("(%a)([%w_']*)", tchelper) -end - --------------------------------------------------- ---isBlank suggestion by http://stackoverflow.com/a/10330861 -function isBlank(x) - x = tostring(x) or "" - -- returns (not not match_begin), _ = match_end => not not true , _ => true - -- returns not not nil => false (no match) - return not not x:find("^%s*$") -end - ---http://lua-users.org/wiki/StringRecipes (removed indents since I am not using them) -function wrap(str, limit)--, indent, indent1) - --indent = indent or "" - --indent1 = indent1 or indent - local limit = limit or 72 - local here = 1 ---#indent1 - return str:gsub("(%s+)()(%S+)()", --indent1..str:gsub( - function(sp, st, word, fi) - if fi-here > limit then - here = st -- - #indent - return "\n"..word --..indent..word - end - end) -end - - --------------------------------------------------- ----------------------- Time ---------------------- --------------------------------------------------- -local TU_PER_DAY = 1200 ---[[ -if advmode then TU_PER_DAY = 86400 ? or only for cur_year_tick? -advmod_TU / 72 = ticks ---]] -local TU_PER_MONTH = TU_PER_DAY * 28 -local TU_PER_YEAR = TU_PER_MONTH * 12 - -local MONTHS = { - 'Granite', - 'Slate', - 'Felsite', - 'Hematite', - 'Malachite', - 'Galena', - 'Limestone', - 'Sandstone', - 'Timber', - 'Moonstone', - 'Opal', - 'Obsidian', -} -Time = defclass(Time) -function Time:init(args) - self.year = args.year or 0 - self.ticks = args.ticks or 0 -end -function Time:getDays() -- >>float<< Days as age (including years) - return self.year * 336 + (self.ticks / TU_PER_DAY) -end -function Time:getMonths() -- >>int<< Months as age (not including years) - return math.floor (self.ticks / TU_PER_MONTH) -end -function Time:getMonthStr() -- Month as date - return MONTHS[self:getMonths()+1] or 'error' -end -function Time:getDayStr() -- Day as date - local d = math.floor ( (self.ticks % TU_PER_MONTH) / TU_PER_DAY ) + 1 - if d == 11 or d == 12 or d == 13 then - d = tostring(d)..'th' - elseif d % 10 == 1 then - d = tostring(d)..'st' - elseif d % 10 == 2 then - d = tostring(d)..'nd' - elseif d % 10 == 3 then - d = tostring(d)..'rd' - else - d = tostring(d)..'th' - end - return d -end ---function Time:__add() ---end -function Time:__sub(other) - if DEBUG then print(self.year,self.ticks) end - if DEBUG then print(other.year,other.ticks) end - if self.ticks < other.ticks then - return Time{ year = (self.year - other.year - 1) , ticks = (TU_PER_YEAR + self.ticks - other.ticks) } - else - return Time{ year = (self.year - other.year) , ticks = (self.ticks - other.ticks) } - end -end --------------------------------------------------- --------------------------------------------------- - --------------------------------------------------- --------------------- Identity -------------------- --------------------------------------------------- -local SINGULAR = 0 -local PLURAL = 1 ---local POSSESSIVE = 2 - -local PRONOUNS = { - [0]='She', - [1]='He', - [2]='It', -} -local BABY = 0 -local CHILD = 1 -local ADULT = 2 - -local TRAINING_LEVELS = { - [0] = ' (Semi-Wild)', -- Semi-wild - ' (Trained)', -- Trained - ' (-Trained-)', -- Well-trained - ' (+Trained+)', -- Skillfully trained - ' (*Trained*)', -- Expertly trained - ' ('..string.char(240)..'Trained'..string.char(240)..')', -- Exceptionally trained - ' ('..string.char(15)..'Trained'..string.char(15)..')', -- Masterully Trained - ' (Tame)', -- Domesticated - '', -- undefined - '', -- wild/untameable -} - -local DEATH_TYPES = { - [0] = ' died of old age', -- OLD_AGE - ' starved to death', -- HUNGER - ' died of dehydration', -- THIRST - ' was shot and killed', -- SHOT - ' bled to death', -- BLEED - ' drowned', -- DROWN - ' suffocated', -- SUFFOCATE - ' was struck down', -- STRUCK_DOWN - ' was scuttled', -- SCUTTLE - " didn't survive a collision", -- COLLISION - ' took a magma bath', -- MAGMA - ' took a magma shower', -- MAGMA_MIST - ' was incinerated by dragon fire', -- DRAGONFIRE - ' was killed by fire', -- FIRE - ' experienced death by SCALD', -- SCALD - ' was crushed by cavein', -- CAVEIN - ' was smashed by a drawbridge', -- DRAWBRIDGE - ' was killed by falling rocks', -- FALLING_ROCKS - ' experienced death by CHASM', -- CHASM - ' experienced death by CAGE', -- CAGE - ' was murdered', -- MURDER - ' was killed by a trap', -- TRAP - ' vanished', -- VANISH - ' experienced death by QUIT', -- QUIT - ' experienced death by ABANDON', -- ABANDON - ' suffered heat stroke', -- HEAT - ' died of hypothermia', -- COLD - ' experienced death by SPIKE', -- SPIKE - ' experienced death by ENCASE_LAVA', -- ENCASE_LAVA - ' experienced death by ENCASE_MAGMA', -- ENCASE_MAGMA - ' was preserved in ice', -- ENCASE_ICE - ' became headless', -- BEHEAD - ' was crucified', -- CRUCIFY - ' experienced death by BURY_ALIVE', -- BURY_ALIVE - ' experienced death by DROWN_ALT', -- DROWN_ALT - ' experienced death by BURN_ALIVE', -- BURN_ALIVE - ' experienced death by FEED_TO_BEASTS', -- FEED_TO_BEASTS - ' experienced death by HACK_TO_PIECES', -- HACK_TO_PIECES - ' choked on air', -- LEAVE_OUT_IN_AIR - ' experienced death by BOIL', -- BOIL - ' melted', -- MELT - ' experienced death by CONDENSE', -- CONDENSE - ' experienced death by SOLIDIFY', -- SOLIDIFY - ' succumbed to infection', -- INFECTION - "'s ghost was put to rest with a memorial", -- MEMORIALIZE - ' scared to death', -- SCARE - ' experienced death by DARKNESS', -- DARKNESS - ' experienced death by COLLAPSE', -- COLLAPSE - ' was drained of blood', -- DRAIN_BLOOD - ' was slaughtered', -- SLAUGHTER - ' became roadkill', -- VEHICLE - ' killed by a falling object', -- FALLING_OBJECT -} - ---GHOST_TYPES[unit.relations.ghost_info.type].." This spirit has not been properly memorialized or buried." -local GHOST_TYPES = { - [0]="A murderous ghost.", - "A sadistic ghost.", - "A secretive ghost.", - "An energetic poltergeist.", - "An angry ghost.", - "A violent ghost.", - "A moaning spirit returned from the dead. It will generally trouble one unfortunate at a time.", - "A howling spirit. The ceaseless noise is making sleep difficult.", - "A troublesome poltergeist.", - "A restless haunt, generally troubling past acquaintances and relatives.", - "A forlorn haunt, seeking out known locations or drifting around the place of death.", -} - - -Identity = defclass(Identity) -function Identity:init(args) - local u = args.unit - self.ident = dfhack.units.getIdentity(u) - - self.unit = u - self.name = dfhack.TranslateName( dfhack.units.getVisibleName(u) ) - self.name_en = dfhack.TranslateName( dfhack.units.getVisibleName(u) , true) - self.raw_prof = dfhack.units.getProfessionName(u) - self.pronoun = PRONOUNS[u.sex] or 'It' - - if self.ident then - self.birth_date = Time{year = self.ident.birth_year, ticks = self.ident.birth_second} - self.race_id = self.ident.race - self.caste_id = self.ident.caste - if self.ident.histfig_id > -1 then - self.hf_id = self.ident.histfig_id - end - else - self.birth_date = Time{year = self.unit.relations.birth_year, ticks = self.unit.relations.birth_time} - self.race_id = u.race - self.caste_id = u.caste - if u.hist_figure_id > -1 then - self.hf_id = u.hist_figure_id - end - end - self.race = df.global.world.raws.creatures.all[self.race_id] - self.caste = self.race.caste[self.caste_id] - - self.isCivCitizen = (df.global.ui.civ_id == u.civ_id) - self.isStray = u.flags1.tame --self.isCivCitizen and not u.flags1.merchant - self.cur_date = Time{year = df.global.cur_year, ticks = df.global.cur_year_tick} - - - ------------ death ------------ - self.dead = u.flags1.dead - self.ghostly = u.flags3.ghostly - self.undead = u.enemy.undead - - if self.dead and self.hf_id then -- dead-dead not undead-dead - local events = df.global.world.history.events2 - local e - for idx = #events - 1,0,-1 do - e = events[idx] - if df.history_event_hist_figure_diedst:is_instance(e) and e.victim_hf == self.hf_id then - self.death_event = e - break - end - end - end - if u.counters.death_id > -1 then -- if undead/ghostly dead or dead-dead - self.incident = df.global.world.incidents.all[u.counters.death_id] - if not self.incident.flags.discovered then - self.missing = true - end - end - -- slaughtered? - if self.death_event then - self.death_date = Time{year = self.death_event.year, ticks = self.death_event.seconds} - elseif self.incident then - self.death_date = Time{year = self.incident.event_year, ticks = self.incident.event_time} - end - -- age now or age death? - if self.dead and self.death_date then -- if cursed with no age? -- if hacked a ressurection, such that they aren't dead anymore, don't use the death date - self.age_time = self.death_date - self.birth_date - else - self.age_time = self.cur_date - self.birth_date - end - if DEBUG then print( self.age_time.year,self.age_time.ticks,self.age_time:getMonths() ) end - ---------- ---------- ---------- - - - ---------- caste_name ---------- - self.caste_name = {} - if isBlank(self.caste.caste_name[SINGULAR]) then - self.caste_name[SINGULAR] = self.race.name[SINGULAR] - else - self.caste_name[SINGULAR] = self.caste.caste_name[SINGULAR] - end - if isBlank(self.caste.caste_name[PLURAL]) then - self.caste_name[PLURAL] = self.race.name[PLURAL] - else - self.caste_name[PLURAL] = self.caste.caste_name[PLURAL] - end - ---------- ---------- ---------- - - --------- growth_status --------- - -- 'baby_age' is the age the baby becomes a child - -- 'child_age' is the age the child becomes an adult - if self.age_time.year >= self.caste.misc.child_age then -- has child come of age becoming adult? - self.growth_status = ADULT - elseif self.age_time.year >= self.caste.misc.baby_age then -- has baby come of age becoming child? - self.growth_status = CHILD - else - self.growth_status = BABY - end - ---------- ---------- ---------- - - -------- aged_caste_name -------- - local caste_name, race_name - if self.growth_status == ADULT then - caste_name = self.caste.caste_name[SINGULAR] - race_name = self.race.name[SINGULAR] - elseif self.growth_status == CHILD then - caste_name = self.caste.child_name[SINGULAR] - race_name = self.race.general_child_name[SINGULAR] - else --if self.growth_status == BABY then - caste_name = self.caste.baby_name[SINGULAR] - race_name = self.race.general_baby_name[SINGULAR] - end - self.aged_caste_name = {} - if isBlank(caste_name[SINGULAR]) then - self.aged_caste_name[SINGULAR] = race_name[SINGULAR] - else - self.aged_caste_name[SINGULAR] = caste_name[SINGULAR] - end - if isBlank(caste_name[PLURAL]) then - self.aged_caste_name[PLURAL] = race_name[PLURAL] - else - self.aged_caste_name[PLURAL] = caste_name[PLURAL] - end - ---------- ---------- ---------- - - ----- Profession adjustment ----- - local prof = self.raw_prof - if self.undead then - prof = str2TitleCase( self.caste_name[SINGULAR] ) - if isBlank(u.enemy.undead.anon_7) then - prof = prof..' Corpse' - else - prof = u.enemy.undead.anon_7 -- a reanimated body part will use this string instead - end - end - --[[ - if self.ghostly then - prof = 'Ghostly '..prof - end - --]] - if u.curse.name_visible and not isBlank(u.curse.name) then - prof = prof..' '..u.curse.name - end - if isBlank(self.name) then - if self.isStray then - prof = 'Stray '..prof --..TRAINING_LEVELS[u.training_level] - end - end - self.prof = prof - ---------- ---------- ---------- -end --------------------------------------------------- --------------------------------------------------- ---[[ - prof_id ? - group_id ? - fort_race_id - fort_civ_id - --fort_group_id? ---]] - - -UnitInfoViewer = defclass(UnitInfoViewer, gui.FramedScreen) -UnitInfoViewer.focus_path = 'unitinfoviewer' -- -> dfhack/lua/unitinfoviewer -UnitInfoViewer.ATTRS={ - frame_style = gui.GREY_LINE_FRAME, - frame_inset = 2, -- used by init - frame_outset = 1,--3, -- new, used by init; 0 = full screen, suggest 0, 1, or 3 or maybe 5 - --frame_title , -- not used - --frame_width,frame_height calculated by frame inset and outset in init -} -function UnitInfoViewer:init(args) -- requires args.unit - --if DEBUG then print('-----') end - local x,y = dfhack.screen.getWindowSize() - -- what if inset or outset are defined as {l,r,t,b}? - x = x - 2*(self.frame_inset + 1 + self.frame_outset) -- 1=frame border thickness - y = y - 2*(self.frame_inset + 1 + self.frame_outset) -- 1=frame border thickness - self.frame_width = args.frame_width or x - self.frame_height = args.frame_height or y - self.text = {} - if df.unit:is_instance(args.unit) then - self.ident = Identity{ unit = args.unit } - if not isBlank(self.ident.name_en) then - self.frame_title = 'Unit: '..self.ident.name_en - elseif not isBlank(self.ident.prof) then - self.frame_title = 'Unit: '..self.ident.prof - if self.ident.isStray then - self.frame_title = self.frame_title..TRAINING_LEVELS[self.ident.unit.training_level] - end - end - self:chunk_Name() - self:chunk_Description() - if not (self.ident.dead or self.ident.undead or self.ident.ghostly) then --not self.dead - if self.ident.isCivCitizen then - self:chunk_Age() - self:chunk_MaxAge() - end - if self.ident.isStray then - if self.ident.growth_status == ADULT then - self:chunk_Milkable() - end - self:chunk_Grazer() - if self.ident.growth_status == ADULT then - self:chunk_Shearable() - end - if self.ident.growth_status == ADULT then - self:chunk_EggLayer() - end - end - self:chunk_BodySize() - elseif self.ident.ghostly then - self:chunk_Dead() - self:chunk_Ghostly() - elseif self.ident.undead then - self:chunk_BodySize() - self:chunk_Dead() - else - self:chunk_Dead() - end - else - self:insert_chunk("No unit is selected in the UI or context not supported.",pens.LIGHTRED) - end - self:addviews{ widgets.Label{ frame={yalign=0}, text=self.text } } -end -function UnitInfoViewer:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - end -end -function UnitInfoViewer:onGetSelectedUnit() - return self.ident.unit -end -function UnitInfoViewer:insert_chunk(str,pen) - local lines = utils.split_string( wrap(str,self.frame_width) , NEWLINE ) - for i = 1,#lines do - table.insert(self.text,{text=lines[i],pen=pen}) - table.insert(self.text,NEWLINE) - end - table.insert(self.text,NEWLINE) -end -function UnitInfoViewer:chunk_Name() - local i = self.ident - local u = i.unit - local prof = i.prof - local color = dfhack.units.getProfessionColor(u) - local blurb - if i.ghostly then - prof = 'Ghostly '..prof - end - if i.isStray then - prof = prof..TRAINING_LEVELS[u.training_level] - end - if isBlank(i.name) then - if isBlank(prof) then - blurb = 'I am a mystery' - else - blurb = prof - end - else - if isBlank(prof) then - blurb=i.name - else - blurb=i.name..', '..prof - end - end - self:insert_chunk(blurb,dfhack.pen.parse{fg=color,bg=0}) -end -function UnitInfoViewer:chunk_Description() - local dsc = self.ident.caste.description - if not isBlank(dsc) then - self:insert_chunk(dsc,pens.WHITE) - end -end - -function UnitInfoViewer:chunk_Age() - local i = self.ident - local age_str -- = '' - if i.age_time.year > 1 then - age_str = tostring(i.age_time.year)..' years old' - elseif i.age_time.year > 0 then -- == 1 - age_str = '1 year old' - else --if age_time.year == 0 then - local age_m = i.age_time:getMonths() -- math.floor - if age_m > 1 then - age_str = tostring(age_m)..' months old' - elseif age_m > 0 then -- age_m == 1 - age_str = '1 month old' - else -- if age_m == 0 then -- and age_m < 0 which would be an error - age_str = 'a newborn' - end - end - local blurb = i.pronoun..' is '..age_str - if i.race_id == df.global.ui.race_id then - blurb = blurb..', born on the '..i.birth_date:getDayStr()..' of '..i.birth_date:getMonthStr()..' in the year '..tostring(i.birth_date.year)..PERIOD - else - blurb = blurb..PERIOD - end - self:insert_chunk(blurb,pens.YELLOW) -end - -function UnitInfoViewer:chunk_MaxAge() - local i = self.ident - local maxage = math.floor( (i.caste.misc.maxage_max + i.caste.misc.maxage_min)/2 ) - --or i.unit.curse.add_tags1.NO_AGING hidden ident? - if i.caste.misc.maxage_min == -1 then - maxage = ' die of unnatural causes.' - elseif maxage == 0 then - maxage = ' die at a very young age.' - elseif maxage == 1 then - maxage = ' live about '..tostring(maxage)..' year.' - else - maxage = ' live about '..tostring(maxage)..' years.' - end - --' is expected to '.. - local blurb = str2FirstUpper(i.caste_name[PLURAL])..maxage - self:insert_chunk(blurb,pens.DARKGREY) -end -function UnitInfoViewer:chunk_Grazer() - if self.ident.caste.flags.GRAZER then - local blurb = 'Grazing satisfies '..tostring(self.ident.caste.misc.grazer)..' units of hunger.' - self:insert_chunk(blurb,pens.LIGHTGREEN) - end -end -function UnitInfoViewer:chunk_EggLayer() - local caste = self.ident.caste - if caste.flags.LAYS_EGGS then - local clutch = math.floor( (caste.misc.clutch_size_max + caste.misc.clutch_size_min)/2 ) - local blurb = 'Lays clutches of about '..tostring(clutch) - if clutch > 1 then - blurb = blurb..' eggs.' - else - blurb = blurb..' egg.' - end - self:insert_chunk(blurb,pens.GREEN) - end -end -function UnitInfoViewer:chunk_Milkable() - local i = self.ident - if i.caste.flags.MILKABLE then - local milk = dfhack.matinfo.decode( i.caste.extracts.milkable_mat , i.caste.extracts.milkable_matidx ) - if milk then - local days,seconds = math.modf ( i.caste.misc.milkable / TU_PER_DAY ) - days = (seconds > 0) and (tostring(days)..' to '..tostring(days + 1)) or tostring(days) - --local blurb = pronoun..' produces '..milk:toString()..' every '..days..' days.' - local blurb = (i.growth_status == ADULT) and (i.pronoun..' secretes ') or str2FirstUpper(i.caste_name[PLURAL])..' secrete ' - blurb = blurb..milk:toString()..' every '..days..' days.' - self:insert_chunk(blurb,pens.LIGHTCYAN) - end - end -end -function UnitInfoViewer:chunk_Shearable() - local i = self.ident - local mat_types = i.caste.body_info.materials.mat_type - local mat_idxs = i.caste.body_info.materials.mat_index - local mat_info, blurb - for idx,mat_type in ipairs(mat_types) do - mat_info = dfhack.matinfo.decode(mat_type,mat_idxs[idx]) - if mat_info and mat_info.material.flags.YARN then - blurb = (i.growth_status == ADULT) and (i.pronoun..' produces ') or str2FirstUpper(i.caste_name[PLURAL])..' produce ' - blurb = blurb..mat_info:toString()..PERIOD - self:insert_chunk(blurb,pens.BROWN) - end - end -end -function UnitInfoViewer:chunk_BodySize() - local i = self.ident - local pat = i.unit.body.physical_attrs - local blurb = i.pronoun..' appears to be about '..pat.STRENGTH.value..':'..pat.AGILITY.value..' cubic decimeters in size.' - self:insert_chunk(blurb,pens.LIGHTBLUE) -end -function UnitInfoViewer:chunk_Ghostly() - local blurb = GHOST_TYPES[self.ident.unit.relations.ghost_info.type].." This spirit has not been properly memorialized or buried." - self:insert_chunk(blurb,pens.LIGHTMAGENTA) - -- Arose in relations.curse_year curse_time -end -function UnitInfoViewer:chunk_Dead() - local i = self.ident - local blurb, str, pen - if i.missing then --dfhack.units.isDead(unit) - str = ' is missing.' - pen = pens.WHITE - elseif i.death_event then - --str = "The Caste_name Unit_Name died in year #{e.year}" - --str << " (cause: #{e.death_cause.to_s.downcase})," - --str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 - --str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON - --str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON - str = DEATH_TYPES[i.death_event.death_cause]..PERIOD - pen = pens.MAGENTA - elseif i.incident then - --str = "The #{u.race_tg.name[0]}" - --str << " #{u.name}" if u.name.has_name - --str << " died" - --str << " in year #{incident.event_year}" if incident - --str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1 - --str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer - str = DEATH_TYPES[i.incident.death_cause]..PERIOD - pen = pens.MAGENTA - elseif i.unit.flags2.slaughter and i.unit.flags2.killed then - str = ' was slaughtered.' - pen = pens.MAGENTA - else - str = ' is dead.' - pen = pens.MAGENTA - end - if i.undead or i.ghostly then - str = ' is undead.' - pen = pens.GREY - end - blurb = 'The '..i.prof -- assume prof is not blank - if not isBlank(i.name) then - blurb = blurb..', '..i.name - end - blurb = blurb..str - self:insert_chunk(blurb,pen) -end - --- only show if UnitInfoViewer isn't the current focus -if dfhack.gui.getCurFocus() ~= 'dfhack/lua/'..UnitInfoViewer.focus_path then - local gui_no_unit = false -- show if not found? - local unit = getUnit_byVS(gui_no_unit) -- silent? or let the gui display - if unit or gui_no_unit then - local kan_viewscreen = UnitInfoViewer{unit = unit} - kan_viewscreen:show() - end -end - diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua deleted file mode 100644 index 6dda9e764..000000000 --- a/scripts/gui/workflow.lua +++ /dev/null @@ -1,1099 +0,0 @@ --- A GUI front-end for the workflow plugin. ---[[=begin - -gui/workflow -============ -Bind to a key (the example config uses Alt-W), and activate with a job selected -in a workshop in :kbd:`q` mode. - -.. image:: /docs/images/workflow.png - -This script provides a simple interface to constraints managed by `workflow`. -When active, it displays a list of all constraints applicable to the -current job, and their current status. - -A constraint specifies a certain range to be compared against either individual -*item* or whole *stack* count, an item type and optionally a material. When the -current count is below the lower bound of the range, the job is resumed; if it -is above or equal to the top bound, it will be suspended. Within the range, the -specific constraint has no effect on the job; others may still affect it. - -Pressing :kbd:`i` switches the current constraint between counting stacks or items. -Pressing :kbd:`r` lets you input the range directly; -:kbd:`e`, :kbd:`r`, :kbd:`d`, :kbd:`f` adjust the -bounds by 5, 10, or 20 depending on the direction and the :kbd:`i` setting (counting -items and expanding the range each gives a 2x bonus). - -Pressing :kbd:`a` produces a list of possible outputs of this job as guessed by -workflow, and lets you create a new constraint by choosing one as template. If you -don't see the choice you want in the list, it likely means you have to adjust -the job material first using `job` ``item-material`` or `gui/workshop-job`, -as described in the `workflow` documentation. In this manner, this feature -can be used for troubleshooting jobs that don't match the right constraints. - -.. image:: /docs/images/workflow-new1.png - -If you select one of the outputs with :kbd:`Enter`, the matching constraint is simply -added to the list. If you use :kbd:`Shift`:kbd:`Enter`, the interface proceeds to the -next dialog, which allows you to edit the suggested constraint parameters to -suit your need, and set the item count range. - -.. image:: /docs/images/workflow-new2.png - -Pressing :kbd:`s` (or, with the example config, Alt-W in the :kbd:`z` stocks screen) -opens the overall status screen: - -.. image:: /docs/images/workflow-status.png - -This screen shows all currently existing workflow constraints, and allows -monitoring and/or changing them from one screen. The constraint list can -be filtered by typing text in the field below. - -The color of the stock level number indicates how "healthy" the stock level -is, based on current count and trend. Bright green is very good, green is good, -red is bad, bright red is very bad. - -The limit number is also color-coded. Red means that there are currently no -workshops producing that item (i.e. no jobs). If it's yellow, that means the -production has been delayed, possibly due to lack of input materials. - -The chart on the right is a plot of the last 14 days (28 half day plots) worth -of stock history for the selected item, with the rightmost point representing -the current stock value. The bright green dashed line is the target -limit (maximum) and the dark green line is that minus the gap (minimum). - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local guimat = require 'gui.materials' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -local workflow = require 'plugins.workflow' - -function check_enabled(cb) - if workflow.isEnabled() then - return cb() - else - dlg.showYesNoPrompt( - 'Enable Plugin', - { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE, - 'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' }, - COLOR_YELLOW, - function() - workflow.setEnabled(true) - return cb() - end - ) - end -end - -function check_repeat(job, cb) - if job.flags['repeat'] then - return cb() - else - dlg.showYesNoPrompt( - 'Not Repeat Job', - { 'Workflow only tracks repeating jobs.', NEWLINE, NEWLINE, - 'Press ', { key = 'MENU_CONFIRM' }, ' to make this one repeat.' }, - COLOR_YELLOW, - function() - job.flags['repeat'] = true - return cb() - end - ) - end -end - -function describe_item_type(iobj) - local itemline = 'any item' - if iobj.is_craft then - itemline = 'any craft' - elseif iobj.item_type >= 0 then - itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local subtype = iobj.item_subtype or -1 - local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype) - if def then - itemline = def.name - elseif count >= 0 then - itemline = 'any '..itemline - end - end - return itemline -end - -function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type or -1) -end - -function describe_material(iobj) - local matflags = utils.list_bitfield_flags(iobj.mat_mask) - if #matflags > 0 then - matflags = 'any '..table.concat(matflags, '/') - else - matflags = nil - end - - if is_caste_mat(iobj) then - return 'no material' - elseif (iobj.mat_type or -1) >= 0 then - local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) - local matline - if info then - matline = info:toString() - else - matline = iobj.mat_type..':'..iobj.mat_index - end - return matline, matflags - else - return matflags or 'any material' - end -end - -function current_stock(iobj) - if iobj.goal_by_count then - return iobj.cur_count - else - return iobj.cur_amount - end -end - -function if_by_count(iobj,bc,ba) - if iobj.goal_by_count then - return bc - else - return ba - end -end - -function compute_trend(history,field) - local count = #history - if count == 0 then - return 0 - end - local sumX,sumY,sumXY,sumXX = 0,0,0,0 - for i,v in ipairs(history) do - sumX = sumX + i - sumY = sumY + v[field] - sumXY = sumXY + i*v[field] - sumXX = sumXX + i*i - end - return (count * sumXY - sumX * sumY) / (count * sumXX - sumX * sumX) -end - ------------------------- --- RANGE EDITOR GROUP -- ------------------------- - -local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } - -RangeEditor = defclass(RangeEditor, widgets.Label) - -RangeEditor.ATTRS { - get_cb = DEFAULT_NIL, - save_cb = DEFAULT_NIL, - keys = { - count = 'CUSTOM_SHIFT_I', - modify = 'CUSTOM_SHIFT_R', - min_dec = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', - min_inc = 'BUILDING_TRIGGER_MIN_SIZE_UP', - max_dec = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', - max_inc = 'BUILDING_TRIGGER_MAX_SIZE_UP', - } -} - -function RangeEditor:init(args) - self:setText{ - { key = self.keys.count, - text = function() - local cons = self.get_cb() or null_cons - if cons.goal_by_count then - return ': Count stacks ' - else - return ': Count items ' - end - end, - on_activate = self:callback('onChangeUnit') }, - { key = self.keys.modify, text = ': Range', - on_activate = self:callback('onEditRange') }, - NEWLINE, ' ', - { key = self.keys.min_dec, - on_activate = self:callback('onIncRange', 'goal_gap', 2) }, - { key = self.keys.min_inc, - on_activate = self:callback('onIncRange', 'goal_gap', -1) }, - { text = function() - local cons = self.get_cb() or null_cons - return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) - end }, - { key = self.keys.max_dec, - on_activate = self:callback('onIncRange', 'goal_value', -1) }, - { key = self.keys.max_inc, - on_activate = self:callback('onIncRange', 'goal_value', 2) }, - { text = function() - local cons = self.get_cb() or null_cons - return string.format(': Max %-4d', cons.goal_value) - end }, - } -end - -function RangeEditor:onChangeUnit() - local cons = self.get_cb() - cons.goal_by_count = not cons.goal_by_count - self.save_cb(cons) -end - -function RangeEditor:onEditRange() - local cons = self.get_cb() - dlg.showInputPrompt( - 'Input Range', - 'Enter the new constraint range:', - COLOR_WHITE, - (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value, - function(text) - local maxv = string.match(text, '^%s*(%d+)%s*$') - if maxv then - cons.goal_value = maxv - return self.save_cb(cons) - end - local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$') - if minv and maxv and minv ~= maxv then - cons.goal_value = math.max(minv,maxv) - cons.goal_gap = math.abs(maxv-minv) - return self.save_cb(cons) - end - dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED) - end - ) -end - -function RangeEditor:onIncRange(field, delta) - local cons = self.get_cb() - if not cons.goal_by_count then - delta = delta * 2 - end - cons[field] = math.max(1, cons[field] + delta*5) - self.save_cb(cons) -end - ---------------------------- --- NEW CONSTRAINT DIALOG -- ---------------------------- - -NewConstraint = defclass(NewConstraint, gui.FramedScreen) - -NewConstraint.focus_path = 'workflow/new' - -NewConstraint.ATTRS { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'New workflow constraint', - frame_width = 39, - frame_height = 20, - frame_inset = 1, - constraint = DEFAULT_NIL, - on_submit = DEFAULT_NIL, -} - -function NewConstraint:init(args) - self.constraint = args.constraint or { item_type = -1 } - rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false }) - - local matlist = {} - local matsel = 1 - local matmask = self.constraint.mat_mask - - for i,v in ipairs(df.dfhack_material_category) do - if v and v ~= 'wood2' then - table.insert(matlist, { icon = self:callback('isMatSelected', v), text = v }) - if matmask and matmask[v] and matsel == 1 then - matsel = #matlist - end - end - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = 'Items matching:' - }, - widgets.Label{ - frame = { l = 1, t = 2, w = 26 }, - text = { - 'Type: ', - { pen = function() - if self:isValid() then return COLOR_LIGHTCYAN else return COLOR_LIGHTRED end - end, - text = function() - if self:isValid() then - return describe_item_type(self.constraint) - else - return 'item not set' - end - end }, - NEWLINE, ' ', - { key = 'CUSTOM_T', text = ': Select, ', - on_activate = self:callback('chooseType') }, - { key = 'CUSTOM_SHIFT_C', text = ': Crafts', - on_activate = self:callback('chooseCrafts') }, - NEWLINE, NEWLINE, - 'Material: ', - { pen = COLOR_LIGHTCYAN, - text = function() return describe_material(self.constraint) end }, - NEWLINE, ' ', - { key = 'CUSTOM_P', text = ': Specific', - on_activate = self:callback('chooseMaterial') }, - NEWLINE, NEWLINE, - 'Other:', - NEWLINE, ' ', - { key = 'D_MILITARY_SUPPLIES_WATER_DOWN', - on_activate = self:callback('incQuality', -1) }, - { key = 'D_MILITARY_SUPPLIES_WATER_UP', key_sep = ': ', - text = function() - return df.item_quality[self.constraint.min_quality or 0]..' quality' - end, - on_activate = self:callback('incQuality', 1) }, - NEWLINE, ' ', - { key = 'CUSTOM_L', key_sep = ': ', - text = function() - if self.constraint.is_local then - return 'Locally made only' - else - return 'Include foreign' - end - end, - on_activate = self:callback('toggleLocal') }, - } - }, - widgets.Label{ - frame = { l = 0, t = 14 }, - text = { - 'Desired range: ', - { pen = COLOR_LIGHTCYAN, - text = function() - local cons = self.constraint - local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value - if cons.goal_by_count then - return goal .. ' stacks' - else - return goal .. ' items' - end - end }, - } - }, - RangeEditor{ - frame = { l = 1, t = 16 }, - get_cb = self:cb_getfield('constraint'), - save_cb = self:callback('onRangeChange'), - }, - widgets.Label{ - frame = { l = 30, t = 0 }, - text = 'Mat class' - }, - widgets.List{ - view_id = 'matlist', - frame = { l = 30, t = 2, w = 9, h = 18 }, - scroll_keys = widgets.STANDARDSCROLL, - choices = matlist, - selected = matsel, - on_submit = self:callback('onToggleMatclass') - }, - widgets.Label{ - frame = { l = 0, b = 0, w = 29 }, - text = { - { key = 'LEAVESCREEN', text = ': Cancel, ', - on_activate = self:callback('dismiss') }, - { key = 'MENU_CONFIRM', key_sep = ': ', - enabled = self:callback('isValid'), - text = function() - if self.is_existing then return 'Update' else return 'Create new' end - end, - on_activate = function() - self:dismiss() - if self.on_submit then - self.on_submit(self.constraint) - end - end }, - } - }, - } -end - -function NewConstraint:postinit() - self:onChange() -end - -function NewConstraint:isValid() - return self.constraint.item_type >= 0 or self.constraint.is_craft -end - -function NewConstraint:onChange() - local token = workflow.constraintToToken(self.constraint) - local out - - if self:isValid() then - out = workflow.findConstraint(token) - end - - if out then - self.constraint = out - self.is_existing = true - else - self.constraint.token = token - self.is_existing = false - end -end - -function NewConstraint:chooseType() - guimat.ItemTypeDialog{ - prompt = 'Please select a new item type', - hide_none = true, - on_select = function(itype,isub) - local cons = self.constraint - cons.item_type = itype - cons.item_subtype = isub - cons.is_craft = nil - self:onChange() - end - }:show() -end - -function NewConstraint:chooseCrafts() - local cons = self.constraint - cons.item_type = -1 - cons.item_subtype = -1 - cons.is_craft = true - self:onChange() -end - -function NewConstraint:chooseMaterial() - local cons = self.constraint - guimat.MaterialDialog{ - prompt = 'Please select a new material', - none_caption = 'any material', - frame_width = 37, - on_select = function(mat_type, mat_index) - local cons = self.constraint - cons.mat_type = mat_type - cons.mat_index = mat_index - cons.mat_mask = nil - self:onChange() - end - }:show() -end - -function NewConstraint:incQuality(diff) - local cons = self.constraint - local nq = (cons.min_quality or 0) + diff - if nq < 0 then - nq = df.item_quality.Masterful - elseif nq > df.item_quality.Masterful then - nq = 0 - end - cons.min_quality = nq - self:onChange() -end - -function NewConstraint:toggleLocal() - local cons = self.constraint - cons.is_local = not cons.is_local - self:onChange() -end - -function NewConstraint:isMatSelected(token) - if self.constraint.mat_mask and self.constraint.mat_mask[token] then - return { ch = '\xfb', fg = COLOR_LIGHTGREEN } - else - return nil - end -end - -function NewConstraint:onToggleMatclass(idx,obj) - local cons = self.constraint - if cons.mat_mask and cons.mat_mask[obj.text] then - cons.mat_mask[obj.text] = false - else - cons.mat_mask = cons.mat_mask or {} - cons.mat_mask[obj.text] = true - cons.mat_type = -1 - cons.mat_index = -1 - end - self:onChange() -end - -function NewConstraint:onRangeChange() - local cons = self.constraint - cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) -end - ------------------------------- --- CONSTRAINT HISTORY GRAPH -- ------------------------------- - -HistoryGraph = defclass(HistoryGraph, widgets.Widget) - -HistoryGraph.ATTRS { - frame_inset = 1, - history_pen = COLOR_CYAN, -} - -function HistoryGraph:init(info) -end - -function HistoryGraph:setData(history, bars) - self.history = history or {} - self.bars = bars or {} - - local maxval = 1 - for i,v in ipairs(self.history) do - maxval = math.max(maxval, v) - end - for i,v in ipairs(self.bars) do - maxval = math.max(maxval, v.value) - end - self.max_value = maxval -end - -function HistoryGraph:onRenderFrame(dc,rect) - dc:fill(rect.x1,rect.y1,rect.x1,rect.y2,{ch='\xb3', fg=COLOR_BROWN}) - dc:fill(rect.x1,rect.y2,rect.x2,rect.y2,{ch='\xc4', fg=COLOR_BROWN}) - dc:seek(rect.x1,rect.y1):char('\x1e', COLOR_BROWN) - dc:seek(rect.x1,rect.y2):char('\xc5', COLOR_BROWN) - dc:seek(rect.x2,rect.y2):char('\x10', COLOR_BROWN) - dc:seek(rect.x1,rect.y2-1):char('0', COLOR_BROWN) -end - -function HistoryGraph:onRenderBody(dc) - local coeff = (dc.height-1)/self.max_value - - for i,v in ipairs(self.bars) do - local y = dc.height-1-math.floor(0.5 + coeff*v.value) - dc:fill(0,y,dc.width-1,y,v.pen or {ch='-', fg=COLOR_GREEN}) - end - - local xbase = dc.width-1-#self.history - for i,v in ipairs(self.history) do - local x = xbase + i - local y = dc.height-1-math.floor(0.5 + coeff*v) - dc:seek(x,y):char('*', self.history_pen) - end -end - ------------------------------- --- GLOBAL CONSTRAINT SCREEN -- ------------------------------- - -ConstraintList = defclass(ConstraintList, gui.FramedScreen) - -ConstraintList.focus_path = 'workflow/list' - -ConstraintList.ATTRS { - frame_title = 'Workflow Status', - frame_inset = 0, - frame_background = COLOR_BLACK, - frame_style = gui.BOUNDARY_FRAME, -} - -function ConstraintList:init(args) - local fwidth_cb = self:cb_getfield('fwidth') - - self.fwidth = 20 - self.sort_by_severity = false - - self:addviews{ - widgets.Panel{ - frame = { l = 0, r = 31 }, - frame_inset = 1, - on_layout = function(body) - self.fwidth = body.width - (12+1+1+7+1+1+1+7) - end, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - text_pen = COLOR_CYAN, - text = { - { text = 'Item', width = 12 }, ' ', - { text = 'Material etc', width = fwidth_cb }, ' ', - { text = 'Stock / Limit' }, - } - }, - widgets.FilteredList{ - view_id = 'list', - frame = { t = 2, b = 2 }, - edit_below = true, - not_found_label = 'No matching constraints', - edit_pen = COLOR_LIGHTCYAN, - text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, - cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, - on_select = self:callback('onSelectConstraint'), - }, - widgets.Label{ - frame = { b = 0, h = 1 }, - text = { - { key = 'CUSTOM_SHIFT_A', text = ': Add', - on_activate = self:callback('onNewConstraint') }, ', ', - { key = 'CUSTOM_SHIFT_X', text = ': Delete', - on_activate = self:callback('onDeleteConstraint') }, ', ', - { key = 'CUSTOM_SHIFT_O', text = ': Severity Order', - on_activate = self:callback('onSwitchSort'), - pen = function() - if self.sort_by_severity then - return COLOR_LIGHTCYAN - else - return COLOR_WHITE - end - end }, - } - } - } - }, - widgets.Panel{ - frame = { w = 30, r = 0, h = 6, t = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - enabled = self:callback('isAnySelected'), - text = { - { text = function() - local cur = self:getCurConstraint() - if cur then - return string.format( - 'Currently %d (%d in use)', - current_stock(cur), - if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) - ) - else - return 'No constraint selected' - end - end } - } - }, - RangeEditor{ - frame = { l = 0, t = 2 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - keys = { - count = 'CUSTOM_SHIFT_I', - modify = 'CUSTOM_SHIFT_R', - min_dec = 'SECONDSCROLL_PAGEUP', - min_inc = 'SECONDSCROLL_PAGEDOWN', - max_dec = 'SECONDSCROLL_UP', - max_inc = 'SECONDSCROLL_DOWN', - } - }, - } - }, - widgets.Widget{ - active = false, - frame = { w = 1, r = 30 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Widget{ - active = false, - frame = { w = 30, r = 0, h = 1, t = 6 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - HistoryGraph{ - view_id = 'graph', - frame = { w = 30, r = 0, t = 7, b = 0 }, - } - } - - self:initListChoices(nil, args.select_token) -end - -function stock_trend_color(cons) - local stock = current_stock(cons) - if stock >= cons.goal_value - cons.goal_gap then - return COLOR_LIGHTGREEN, 0 - elseif stock <= cons.goal_gap then - return COLOR_LIGHTRED, 4 - elseif stock >= cons.goal_value - 2*cons.goal_gap then - return COLOR_GREEN, 1 - elseif stock <= 2*cons.goal_gap then - return COLOR_RED, 3 - else - local trend = if_by_count(cons, cons.trend_count, cons.trend_amount) - if trend > 0.3 then - return COLOR_GREEN, 1 - elseif trend < -0.3 then - return COLOR_RED, 3 - else - return COLOR_GREY, 2 - end - end -end - -function ConstraintList:initListChoices(clist, sel_token) - clist = clist or workflow.listConstraints(nil, true) - - local fwidth_cb = self:cb_getfield('fwidth') - local choices = {} - - for i,cons in ipairs(clist) do - cons.trend_count = compute_trend(cons.history, 'cur_count') - cons.trend_amount = compute_trend(cons.history, 'cur_amount') - - local itemstr = describe_item_type(cons) - local matstr,matflagstr = describe_material(cons) - if matflagstr then - matstr = matflagstr .. ' ' .. matstr - end - - if cons.min_quality > 0 or cons.is_local then - local lst = {} - if cons.is_local then - table.insert(lst, 'local') - end - if cons.min_quality > 0 then - table.insert(lst, string.lower(df.item_quality[cons.min_quality])) - end - matstr = matstr .. ' ('..table.concat(lst,',')..')' - end - - local goal_color = COLOR_GREY - if #cons.jobs == 0 then - goal_color = COLOR_RED - elseif cons.is_delayed then - goal_color = COLOR_YELLOW - end - - table.insert(choices, { - text = { - { text = itemstr, width = 12, pad_char = ' ' }, ' ', - { text = matstr, width = fwidth_cb, pad_char = ' ' }, ' ', - { text = curry(current_stock,cons), width = 7, rjustify = true, - pen = function() return { fg = stock_trend_color(cons) } end }, - { text = curry(if_by_count,cons,'S','I'), gap = 1, - pen = { fg = COLOR_GREY } }, - { text = function() return cons.goal_value end, gap = 1, - pen = { fg = goal_color } } - }, - severity = select(2, stock_trend_color(cons)), - search_key = itemstr .. ' | ' .. matstr, - token = cons.token, - obj = cons - }) - end - - self:setChoices(choices, sel_token) -end - -function ConstraintList:isAnySelected() - return self.subviews.list:getSelected() ~= nil -end - -function ConstraintList:getCurConstraint() - local selidx,selobj = self.subviews.list:getSelected() - if selobj then return selobj.obj end -end - -function ConstraintList:onSwitchSort() - self.sort_by_severity = not self.sort_by_severity - self:setChoices(self.subviews.list:getChoices()) -end - -function ConstraintList:setChoices(choices, sel_token) - if self.sort_by_severity then - table.sort(choices, function(a,b) - return a.severity > b.severity - or (a.severity == b.severity and - current_stock(a.obj)/a.obj.goal_value < current_stock(b.obj)/b.obj.goal_value) - end) - else - table.sort(choices, function(a,b) return a.search_key < b.search_key end) - end - - local selidx = nil - if sel_token then - selidx = utils.linear_index(choices, sel_token, 'token') - end - - local list = self.subviews.list - local filter = list:getFilter() - - list:setChoices(choices, selidx) - - if filter ~= '' then - list:setFilter(filter, selidx) - - if selidx and list:getSelected() ~= selidx then - list:setFilter('', selidx) - end - end -end - -function ConstraintList:onInput(keys) - if keys.LEAVESCREEN then - self:dismiss() - else - ConstraintList.super.onInput(self, keys) - end -end - -function ConstraintList:onNewConstraint() - NewConstraint{ - on_submit = self:callback('saveConstraint') - }:show() -end - -function ConstraintList:saveConstraint(cons) - local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) - self:initListChoices(nil, out.token) -end - -function ConstraintList:onDeleteConstraint() - local cons = self:getCurConstraint() - dlg.showYesNoPrompt( - 'Delete Constraint', - 'Really delete the current constraint?', - COLOR_YELLOW, - function() - workflow.deleteConstraint(cons.token) - self:initListChoices() - end - ) -end - -function ConstraintList:onSelectConstraint(idx,item) - local history, bars - - if item then - local cons = item.obj - local vfield = if_by_count(cons, 'cur_count', 'cur_amount') - - bars = { - { value = cons.goal_value - cons.goal_gap, pen = {ch='-', fg=COLOR_GREEN} }, - { value = cons.goal_value, pen = {ch='-', fg=COLOR_LIGHTGREEN} }, - } - - history = {} - for i,v in ipairs(cons.history or {}) do - table.insert(history, v[vfield]) - end - - table.insert(history, cons[vfield]) - end - - self.subviews.graph:setData(history, bars) -end - -------------------------------- --- WORKSHOP JOB INFO OVERLAY -- -------------------------------- - -JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) - -JobConstraints.focus_path = 'workflow/job' - -JobConstraints.ATTRS { - job = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function JobConstraints:init(args) - self.building = dfhack.job.getHolder(self.job) - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - 'Workflow Constraints' - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 2, b = 6 }, - row_height = 4, - scroll_keys = widgets.SECONDSCROLL, - }, - RangeEditor{ - frame = { l = 0, b = 3 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'CUSTOM_SHIFT_A', text = ': Add limit, ', - on_activate = self:callback('onNewConstraint') }, - { key = 'CUSTOM_SHIFT_X', text = ': Delete', - enabled = self:callback('isAnySelected'), - on_activate = self:callback('onDeleteConstraint') }, - NEWLINE, NEWLINE, - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') }, - ' ', - { key = 'CUSTOM_SHIFT_S', text = ': Status', - on_activate = function() - local sel = self:getCurConstraint() - ConstraintList{ select_token = (sel or {}).token }:show() - end } - } - }, - } - - self:initListChoices(args.clist) -end - -function JobConstraints:onGetSelectedBuilding() - return self.building -end - -function JobConstraints:onGetSelectedJob() - return self.job -end - -function JobConstraints:initListChoices(clist, sel_token) - clist = clist or workflow.listConstraints(self.job) - - local choices = {} - - for i,cons in ipairs(clist) do - local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value - local curval - if cons.goal_by_count then - goal = goal .. ' stacks' - curval = cons.cur_count - else - goal = goal .. ' items' - curval = cons.cur_amount - end - local order_pen = COLOR_GREY - if cons.request == 'resume' then - order_pen = COLOR_GREEN - elseif cons.request == 'suspend' then - order_pen = COLOR_BLUE - end - local itemstr = describe_item_type(cons) - if cons.min_quality > 0 or cons.is_local then - local lst = {} - if cons.is_local then - table.insert(lst, 'local') - end - if cons.min_quality > 0 then - table.insert(lst, string.lower(df.item_quality[cons.min_quality])) - end - itemstr = itemstr .. ' ('..table.concat(lst,',')..')' - end - local matstr,matflagstr = describe_material(cons) - - table.insert(choices, { - text = { - goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, - ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', (matflagstr or '') - }, - token = cons.token, - obj = cons - }) - end - - local selidx = nil - if sel_token then - selidx = utils.linear_index(choices, sel_token, 'token') - end - - self.subviews.list:setChoices(choices, selidx) -end - -function JobConstraints:isAnySelected() - return self.subviews.list:getSelected() ~= nil -end - -function JobConstraints:getCurConstraint() - local i,v = self.subviews.list:getSelected() - if v then return v.obj end -end - -function JobConstraints:saveConstraint(cons) - local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) - self:initListChoices(nil, out.token) -end - -function JobConstraints:onNewConstraint() - local outputs = workflow.listJobOutputs(self.job) - if #outputs == 0 then - dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED) - return - end - - local variants = workflow.listWeakenedConstraints(outputs) - - local choices = {} - for i,cons in ipairs(variants) do - local itemstr = describe_item_type(cons) - local matstr,matflags = describe_material(cons) - if matflags then - matstr = matflags..' '..matstr - end - - table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) - end - - dlg.ListBox{ - frame_title = 'Add limit', - text = 'Select one of the possible outputs:', - text_pen = COLOR_WHITE, - choices = choices, - on_select = function(idx,item) - self:saveConstraint(item.obj) - end, - select2_hint = 'Advanced', - on_select2 = function(idx,item) - NewConstraint{ - constraint = item.obj, - on_submit = self:callback('saveConstraint') - }:show() - end, - }:show() -end - -function JobConstraints:onDeleteConstraint() - local cons = self:getCurConstraint() - dlg.showYesNoPrompt( - 'Delete Constraint', - 'Really delete the current constraint?', - COLOR_YELLOW, - function() - workflow.deleteConstraint(cons.token) - self:initListChoices() - end - ) -end - -function JobConstraints:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - JobConstraints.super.onInput(self, keys) - end -end - -local args = {...} - -if args[1] == 'status' then - check_enabled(function() ConstraintList{}:show() end) -else - if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") - end - - local job = dfhack.gui.getSelectedJob() - - check_enabled(function() - check_repeat(job, function() - local clist = workflow.listConstraints(job) - if not clist then - dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) - return - end - JobConstraints{ job = job, clist = clist }:show() - end) - end) -end diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua deleted file mode 100644 index f3af15d10..000000000 --- a/scripts/gui/workshop-job.lua +++ /dev/null @@ -1,332 +0,0 @@ --- Show and modify properties of jobs in a workshop. ---[[=begin - -gui/workshop-job -================ -Bind to a key (the example config uses :kbd:`Alt`:kbd:`a`), and activate with a job selected in -a workshop in the :kbd:`q` mode. - -.. image:: /docs/images/workshop-job.png - -The script shows a list of the input reagents of the selected job, and allows changing -them like the `job` ``item-type`` and `job` ``item-material`` commands. - -Specifically, pressing the :kbd:`i` key pops up a dialog that lets you select an item -type from a list. - -.. image:: /docs/images/workshop-job-item.png - -Pressing :kbd:`m`, unless the item type does not allow a material, -lets you choose a material. - -.. image:: /docs/images/workshop-job-material.png - -Since there are a lot more materials than item types, this dialog is more complex -and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked -with an arrow on the left. - -.. warning:: - - Due to the way input reagent matching works in DF, you must select an item type - if you select a material, or the material will be matched incorrectly in some cases. - If you press :kbd:`m` without choosing an item type, the script will auto-choose - if there is only one valid choice, or pop up an error message box instead of the - material selection dialog. - -Note that both materials and item types presented in the dialogs are filtered -by the job input flags, and even the selected item type for material selection, -or material for item type selection. Many jobs would let you select only one -input item type. - -For example, if you choose a *plant* input item type for your prepare meal job, -it will only let you select cookable materials. - -If you choose a *barrel* item instead (meaning things stored in barrels, like -drink or milk), it will let you select any material, since in this case the -material is matched against the barrel itself. Then, if you select, say, iron, -and then try to change the input item type, now it won't let you select *plant*; -you have to unset the material first. - -=end]] -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local guimat = require 'gui.materials' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -JobDetails = defclass(JobDetails, guidm.MenuOverlay) - -JobDetails.focus_path = 'workshop-job' - -JobDetails.ATTRS { - job = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function JobDetails:init(args) - self.building = dfhack.job.getHolder(self.job) - - local status = { text = 'No worker', pen = COLOR_DARKGREY } - local worker = dfhack.job.getWorker(self.job) - if self.job.flags.suspend then - status = { text = 'Suspended', pen = COLOR_RED } - elseif worker then - status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, - ' ', status - } - }, - widgets.Label{ - frame = { l = 0, t = 4 }, - text = { - { key = 'CUSTOM_I', text = ': Input item, ', - enabled = self:callback('canChangeIType'), - on_activate = self:callback('onChangeIType') }, - { key = 'CUSTOM_M', text = ': Material', - enabled = self:callback('canChangeMat'), - on_activate = self:callback('onChangeMat') } - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 6, b = 2 }, - row_height = 4, - scroll_keys = widgets.SECONDSCROLL, - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } - } - }, - } - - self:initListChoices() -end - -function JobDetails:onGetSelectedBuilding() - return self.building -end - -function JobDetails:onGetSelectedJob() - return self.job -end - -function describe_item_type(iobj) - local itemline = 'any item' - if iobj.item_type >= 0 then - itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) - if def then - itemline = def.name - elseif count >= 0 then - itemline = 'any '..itemline - end - end - return itemline -end - -function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type) -end - -function describe_material(iobj) - local matline = 'any material' - if is_caste_mat(iobj) then - matline = 'material not applicable' - elseif iobj.mat_type >= 0 then - local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) - if info then - matline = info:toString() - else - matline = iobj.mat_type..':'..iobj.mat_index - end - end - return matline -end - -function list_flags(list, bitfield) - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) - end - end -end - -function JobDetails:initListChoices() - local items = {} - for i,ref in ipairs(self.job.items) do - local idx = ref.job_item_idx - if idx >= 0 then - items[idx] = (items[idx] or 0) + 1 - end - end - - local choices = {} - for i,iobj in ipairs(self.job.job_items) do - local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity - if iobj.min_dimension > 0 then - head = head .. '(size '..iobj.min_dimension..')' - end - - local line1 = {} - local reaction = df.reaction.find(iobj.reaction_id) - if reaction and #iobj.contains > 0 then - for _,ri in ipairs(iobj.contains) do - table.insert(line1, 'has '..utils.call_with_string( - reaction.reagents[ri],'getDescription',iobj.reaction_id - )) - end - end - if iobj.metal_ore >= 0 then - local ore = dfhack.matinfo.decode(0, iobj.metal_ore) - if ore then - table.insert(line1, 'ore of '..ore:toString()) - end - end - if iobj.has_material_reaction_product ~= '' then - table.insert(line1, 'product '..iobj.has_material_reaction_product) - end - if iobj.reaction_class ~= '' then - table.insert(line1, 'class '..iobj.reaction_class) - end - if iobj.has_tool_use >= 0 then - table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) - end - list_flags(line1, iobj.flags1) - list_flags(line1, iobj.flags2) - list_flags(line1, iobj.flags3) - if #line1 == 0 then - table.insert(line1, 'no flags') - end - - table.insert(choices, { - index = i, - iobj = iobj, - text = { - head, NEWLINE, - ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, - ' ', { text = curry(describe_material, iobj) }, NEWLINE, - ' ', table.concat(line1, ', '), NEWLINE - } - }) - end - - self.subviews.list:setChoices(choices) -end - -function JobDetails:canChangeIType() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil -end - -function JobDetails:setItemType(obj, item_type, item_subtype) - obj.iobj.item_type = item_type - obj.iobj.item_subtype = item_subtype - - if is_caste_mat(obj.iobj) then - self:setMaterial(obj, -1, -1) - end -end - -function JobDetails:onChangeIType() - local idx, obj = self.subviews.list:getSelected() - guimat.ItemTypeDialog{ - prompt = 'Please select a new item type for input '..idx, - none_caption = 'any item', - item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), - on_select = self:callback('setItemType', obj) - }:show() -end - -function JobDetails:canChangeMat() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil and not is_caste_mat(obj.iobj) -end - -function JobDetails:setMaterial(obj, mat_type, mat_index) - if obj.index == 0 - and self.job.mat_type == obj.iobj.mat_type - and self.job.mat_index == obj.iobj.mat_index - and self.job.job_type ~= df.job_type.PrepareMeal - then - self.job.mat_type = mat_type - self.job.mat_index = mat_index - end - - obj.iobj.mat_type = mat_type - obj.iobj.mat_index = mat_index -end - -function JobDetails:findUnambiguousItem(iobj) - local count = 0 - local itype - - for i = 0,df.item_type._last_item do - if dfhack.job.isSuitableItem(iobj, i, -1) then - count = count + 1 - if count > 1 then return nil end - itype = i - end - end - - return itype -end - -function JobDetails:onChangeMat() - local idx, obj = self.subviews.list:getSelected() - - if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then - -- If the job allows only one specific item type, use it - local vitype = self:findUnambiguousItem(obj.iobj) - - if vitype then - obj.iobj.item_type = vitype - else - dlg.showMessage( - 'Bug Alert', - { 'Please set a specific item type first.\n\n', - 'Otherwise the material will be matched\n', - 'incorrectly due to a limitation in DF code.' }, - COLOR_YELLOW - ) - return - end - end - - guimat.MaterialDialog{ - prompt = 'Please select a new material for input '..idx, - none_caption = 'any material', - mat_filter = function(mat,parent,mat_type,mat_index) - return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index) - end, - on_select = self:callback('setMaterial', obj) - }:show() -end - -function JobDetails:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - JobDetails.super.onInput(self, keys) - end -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") -end - -local dlg = JobDetails{ job = dfhack.gui.getSelectedJob() } -dlg:show() diff --git a/scripts/hfs-pit.lua b/scripts/hfs-pit.lua deleted file mode 100644 index 7fc571090..000000000 --- a/scripts/hfs-pit.lua +++ /dev/null @@ -1,105 +0,0 @@ --- Creates a pit to the Underworld under the target --- Based on script by IndigoFenix, @ https://gist.github.com/IndigoFenix/8776696 ---[[=begin - -hfs-pit -======= -Creates a pit to the underworld at the cursor. - -Takes three arguments: diameter of the pit in tiles, whether to wall off -the pit, and whether to insert stairs. If no arguments are given, the default -is ``hfs-pit 1 0 0``, ie single-tile wide with no walls or stairs.:: - - hfs-pit 4 0 1 - hfs-pit 2 1 0 - -First example is a four-across pit with stairs but no walls; second is a -two-across pit with stairs but no walls. - -=end]] - -args={...} - -if args[1] == '?' then - print("Example usage: 'hfs-pit 2 1 1'") - print("First parameter is size of the pit in all directions.") - print("Second parameter is 1 to wall off the sides of the pit on all layers except the underworld, or anything else to leave them open.") - print("Third parameter is 1 to add stairs. Stairs are buggy; they will not reveal the bottom until you dig somewhere, but underworld creatures will path in.") - print("If no arguments are given, the default is 'hfs-pit 1 0 0', ie single-tile wide with no walls or stairs.") - return -end - -pos = copyall(df.global.cursor) -size = tonumber(args[1]) -if size == nil or size < 1 then size = 1 end - -wallOff = tonumber(args[2]) -stairs = tonumber(args[3]) - ---Get the layer of the underworld -for index,value in ipairs(df.global.world.features.map_features) do - local featureType=value:getType() - if featureType==9 then --Underworld - underworldLayer = value.layer - end -end - -if pos.x==-30000 then - qerror("Select a location by placing the cursor") -end -local x = 0 -local y = 0 -for x=pos.x-size,pos.x+size,1 do - for y=pos.y-size,pos.y+size,1 do - z=1 - local hitAir = false - local hitCeiling = false - while z <= pos.z do - local block = dfhack.maps.ensureTileBlock(x,y,z) - if block then - if block.tiletype[x%16][y%16] ~= 335 then - hitAir = true - end - if hitAir == true then - if not hitCeiling then - if block.global_feature ~= underworldLayer or z > 10 then hitCeiling = true end - if stairs == 1 and x == pos.x and y == pos.y then - if block.tiletype[x%16][y%16] == 32 then - if z == pos.z then - block.tiletype[x%16][y%16] = 56 - else - block.tiletype[x%16][y%16] = 55 - end - else - block.tiletype[x%16][y%16] = 57 - end - end - end - if hitCeiling == true then - if block.designation[x%16][y%16].flow_size > 0 or wallOff == 1 then needsWall = true else needsWall = false end - if (x == pos.x-size or x == pos.x+size or y == pos.y-size or y == pos.y+size) and z==pos.z then - --Do nothing, this is the lip of the hole - elseif x == pos.x-size and y == pos.y-size then if needsWall == true then block.tiletype[x%16][y%16]=320 end - elseif x == pos.x-size and y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=321 end - elseif x == pos.x+size and y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=322 end - elseif x == pos.x+size and y == pos.y-size then if needsWall == true then block.tiletype[x%16][y%16]=323 end - elseif x == pos.x-size or x == pos.x+size then if needsWall == true then block.tiletype[x%16][y%16]=324 end - elseif y == pos.y-size or y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=325 end - elseif stairs == 1 and x == pos.x and y == pos.y then - if z == pos.z then block.tiletype[x%16][y%16]=56 - else block.tiletype[x%16][y%16]=55 end - else block.tiletype[x%16][y%16]=32 - end - block.designation[x%16][y%16].hidden = false - --block.designation[x%16][y%16].liquid_type = true -- if true, magma. if false, water. - block.designation[x%16][y%16].flow_size = 0 - dfhack.maps.enableBlockUpdates(block) - block.designation[x%16][y%16].flow_forbid = false - end - end - block.designation[x%16][y%16].hidden = false - end - z = z+1 - end - end -end \ No newline at end of file diff --git a/scripts/hotkey-notes.lua b/scripts/hotkey-notes.lua deleted file mode 100644 index 428a02d64..000000000 --- a/scripts/hotkey-notes.lua +++ /dev/null @@ -1,16 +0,0 @@ --- prints info on assigned hotkeys to the console ---[[=begin - -hotkey-notes -============ -Lists the key, name, and jump position of your hotkeys in the DFHack console. - -=end]] - -for i=1, #df.global.ui.main.hotkeys do - local hk = df.global.ui.main.hotkeys[i-1] - local key = dfhack.screen.getKeyDisplay(df.interface_key.D_HOTKEY1 + i - 1) - if hk.cmd ~= -1 then - print(key..': '..hk.name..': x='..hk.x..' y='..hk.y..' z='..hk.z) - end -end diff --git a/scripts/item-descriptions.lua b/scripts/item-descriptions.lua deleted file mode 100644 index fe7e7ac00..000000000 --- a/scripts/item-descriptions.lua +++ /dev/null @@ -1,656 +0,0 @@ --- Holds custom descriptions for view-item-info --- By PeridexisErrant ---[[=begin - -item-descriptions -================= -Exports a table with custom description text for every item in the game. -Used by `view-item-info`; see instructions there for how to override -for mods. - -=end]] - --- Each line near the bottom has 53 characters of room until --- it starts clipping over the UI in an ugly fashion. --- For proper spacing, 50 characters is the maximum. --- Descriptions which aren't pushed down the page by --- barrel contents or such line up with the UI on the --- 11th line down. There is room for a 10th long line --- without clipping, but stopping at 9 leaves enough space --- for ideal legibility. - --- The following people contributed descriptions: --- Raideau, PeridexisErrant, /u/Puffin4Tom, /u/KroyMortlach --- /u/genieus, /u/TeamsOnlyMedic, /u/johny5w, /u/DerTanni --- /u/schmee101, /u/coaldiamond, /u/stolencatkarma, /u/sylth01 --- /u/MperorM, /u/SockHoarder, /u/_enclave_, WesQ3 --- /u/Xen0nex, /u/Jurph - -if not moduleMode then - print("scripts/item-descriptions.lua is a content library; calling it does nothing.") -end - -local help --[[ -This script has a single function: to return a custom description for every -vanilla item in the game. - -If "raw/scripts/item-descriptions.lua" exists, it will entirely replace this one. -Instead, mods should use "raw/scripts/more-item-descriptions.lua" to add content or replace -descriptions on a case-by-case basis. If an item description cannot be found in -the latter script, view-item-info will fall back to the former. -]] - --- see http://dwarffortresswiki.org/index.php/cv:Item_token -descriptions = { - AMULET = { "An item of jewellery worn around the neck for it's aesthetic value.", - "An amulet does not interfere with wearing other equipment."}, - ANIMALTRAP = { - "This tiny trap is used by your trappers to catch vermin. Some dwarves", - "like vermin as pets - if your cats don't get them first. May be built", - "from wood or metal. Catching vermin requires trap to be set with bait."}, - ANVIL = { "An essential component of the forge."}, - ARMORSTAND = { - "A rack for the storage of military equipment, specifically armor.", - "Barracks may be designated and assigned from them. Military squads", - "may use their assigned barracks for training, storage, or sleeping,", - "depending on the settings. Some nobles demand an armor stand of", - "their own."}, - BACKPACK = {"A backpack can be used by militia to carry rations in the field.", - "In Adventure mode, backpacks can be used to carry more equipment."}, - BALLISTAARROWHEAD = { - "The arrowtip used to create metal ballista arrows in a siege workshop."}, - BALLISTAPARTS = { - "Three of these can be used to construct a Ballista."}, - BAR = { "A small ingot made of metal, fuel, ash, or soap, made to facilitate", - "stacking and storage. Aside from the uses unique to each material, bars", - "of any type can also be used as building materials in place of blocks.", - "", - "Metal bars are used with fuel at a Metalsmith's Forge to make metal", - "goods and decorations. Fuel is used at furnaces and workshops requiring", - "intense heat, with the exception of magma furnaces or a Wood Furnace.", - "Soap is used by hospitals to greatly reduce the chance of infected", - "wounds, and rarely used by individual dwarves to clean themselves or", - "surrounding tiles. Ash is processed at an Ashery to make potash or lye,", - "potash is used as farm plot fertilizer or made into pearlash, and", - "pearlash is used to make clear or crystal glass products."}, - BARREL = { "A hollow cylinder with a removable lid. It is used to hold liquids,", - "food, and seeds. It can be made from metal or wood, and is replaceable", - "with a rock pot. A barrel (or rock pot) is needed to brew drinks."}, - BED = { "A pallet for dwarves to sleep on, which must be made from wood.", - "It prevents the stress of sleeping on the ground, and can be used", - "to designate a bedroom (used by one dwarf or couple), a dormitory", - "(used by multiple dwarves), or a barracks (used by a military", - "squad for training or sleep)."}, - BIN = { "A container for the storage of ammunition, armor and weapons, bars,", - "blocks, cloth and leather, coins, finished goods and gems. It can", - "be used to carry multiple items to the Trade Depot at once.", - "A bin can be made from wood or forged from metal."}, - BLOCKS = { "Blocks can be used for constructions in place of raw materials such", - "as logs or bars. Cutting boulders into blocks gives four times as", - "many items, all of which are lighter for faster hauling and yield", - "smooth constructions."}, - BOX = { "A container for storing dwarves' items. They are required by nobles,", - "and will increase the value of rooms they are placed in. Also", - "required to store hospital supplies. They can be made from stone or", - "metal (coffers), wood (chests),or textiles or leather (bags)."}, - BUCKET = { "A small cylindrical or conical container for holding and carrying", - "small amounts of liquid such as water or lye. They are used by", - "dwarves to give water to other dwarves, to store lye, and are", - "required to build wells and certain workshops. They can be made", - "from wood or metal."}, - BOOK = { "It's a book. Some books contain the secrets of life and death."}, - BOULDER = { "Mining may yield loose stones for industry. There are four categories:", - "non-economic stones for building materials, ores for metal industry,", - "gems, and special-purpose economic stones like flux, coal and lignite."}, - BRACELET = {"A bracelet is an item of jewellery worn on the hands."}, - CABINET = { "A place for dwarves to store old clothing, used by any dwarf whose room", - "overlaps the cabinet. May be built from wood, stone, metal, or glass."}, - CAGE = { "A cage can be made of glass, metal or wood. All materials are equally", - "strong as cages can not be broken, however the weight of the material", - "affects the time taken to move cages. Cages can be combined with a", - "mechanism to create a cage trap. Cages can also be built as furniture,", - "after which they can store an infinite number of animals or prisoners."}, - CATAPULTPARTS = { - "Three of these can be used to construct a Catapult."}, - CHAIN = { "A chain made of metal. A chain or rope is required to build a well.", - "Due to the marvels of dwarven engineering, a single chain can be used", - "for a well of any depth. Chains are also used to create restraints", - "for prisoners or animals."}, - CHAIR = { "Furniture used for sitting. Named a chair if made from wood,", - "or a throne if made from stone, glass, or metal. Offices may be", - "designated and assigned from them. Dwarves will complain if there", - "aren't enough chairs in the dining room."}, - CLOTH = { "A piece of fabric made from threads of plant fiber, yarn, silk or", - "adamantine. Cloth may be dyed. It is used at a Clothier's Workshop to", - "make clothing, bags, rope, and decorative sewn images. At a", - "Craftsdwarf's Workshop, it can be made into trade goods. Hospitals", - "use cloth for wound dressing - though expensive cloth confers no", - "benefit. Specific types of cloth can be required by a strange mood."}, - COFFIN = { "A final resting place for dwarves. Must be built and assigned before", - "burial can occur. Named a coffin when made from stone or glass,", - "casket when made from wood, and sarcophagus when made from metal.", - "Tombs may be designated and assigned from them. Corpses contained in", - "coffins may still reanimate."}, - COIN = { "A metal coin, which represents value. Surprisingly useless in trade."}, - CROWN = { "A crown may be worn as headgear, or on top of a helmet. Although", - "usually just decorative or symbolic, crowns sometimes deflect attacks."}, - CRUTCH = { "Item used in health-care. May be made from wood or metal. Given to", - "dwarves who receive injuries that impair or prevent normal movement.", - "Requires one hand."}, - DOOR = { "A barrier that covers a hole in a wall and controls horizontal passage.", - "Intelligent creatures can open and close doors as needed. Doors can", - "also block the flow of liquids as long as they remain closed. May be", - "set as 'locked' to prevent all passage or 'tightly closed' to prevent", - "animal passage. Creatures cannot manually open or close doors that are", - "mechanically controlled. May be linked via mechanisms to devices such", - "as pressure plates and levers. Will become stuck open if an item", - "occupies its tile."}, - EARRING = { "Earrings are decorative jewellery. Eleven can be worn on each ear."}, - FIGURINE = {"A small piece of art carved in the likeness of a creature."}, - FLASK = { "A drink container that is worn on the body, keeping the hands free.", - "Soldiers and adventurers can carry any drink of their choice in this", - "container. Called a flask when made from metal, or a vial when", - "made from glass."}, - FLOODGATE = { - "A mechanical gate used to control the flow of water. Floodgates are", - "initially closed when installed, and must be linked to a lever or", - "pressure plate in order to be either opened or closed. Furniture for", - "blocking horizontal passage. It is built as a solid tile and may be", - "linked via mechanism to devices such as pressure plates and levers.", - "When activated, the floodgate will disappear. Will become stuck open", - "if an item occupies its tile."}, - GOBLET = { "A small drink container that is held in one hand."}, - GRATE = { "A barrier with small openings, grates block solid objects and", - "creatures - but not line of sight, liquids, or projectiles. Grates can", - "be installed vertically on a floor, or horizontally over open space.", - "Grates can be retracted if they are mechanically linked to a lever or", - "pressure plate. Grates are not stable enough to support constructions."}, - HATCH_COVER = { - "A barrier that covers a hole in the floor. A hatch cover acts like a", - "door, but placed over a vertical opening. Hatches can also cover carved", - "stairways and ramps. Hatches can be linked to mechanical controls.", - "Creatures cannot manually open or close hatches that are mechanically", - "controlled. They may also be set as 'locked' to prevent all passage or", - "'tightly closed' to prevent animal passage."}, - ITEM_AMMO_ARROWS = { - "Ammunition for bows, which are primarily used by elves."}, - ITEM_AMMO_BOLTS = { - "Ammunition for crossbows, which are a dwarf's preferred ranged weapon.", - "It is not recommended to store bolts in bins, due to pickup bugs."}, - ITEM_ARMOR_BREASTPLATE = { - "A breastplate is a piece of plate armor that covers the upper body and", - "the lower body. It is usually worn in the armor layer."}, - ITEM_ARMOR_CAPE = { - "A (cool-looking) cape. Protects the chest."}, - ITEM_ARMOR_CLOAK = { - "A cloth cloak. Protects the face, neck, chest, arms and upper legs."}, - ITEM_ARMOR_COAT = { - "A heavy cloth coat. Protects the face, neck, chest, arms and upper legs."}, - ITEM_ARMOR_DRESS = { - "A cloth dress. Protects the face, neck, chest, arms and legs."}, - ITEM_ARMOR_LEATHER = { - "Leather armor is light and covers both arms and legs", - "in addition to body"}, - ITEM_ARMOR_MAIL_SHIRT = { - "A chainmail shirt. Protects the face, neck, chest,", - "upper arms and upper legs."}, - ITEM_ARMOR_ROBE = { - "A cloth robe. Protects the face, neck, chest, arms and legs."}, - ITEM_ARMOR_SHIRT = { - "A cloth shirt. Protects the neck, chest and arms."}, - ITEM_ARMOR_TOGA = { - "A cloth toga. Protects the face, neck, chest, upper arms and upper legs."}, - ITEM_ARMOR_TUNIC = { - "A cloth tunic. Protects the neck, chest and upper arms."}, - ITEM_ARMOR_VEST = { - "A cloth vest. Protects the chest."}, - ITEM_FOOD_BISCUITS = { - "Biscuits are the lowest tier of meals that can be prepared by your", - "dwarves. They are made in a kitchen with the 'Prepare Easy Meal' order", - "and use two ingredients. Preparing easy meals is the easiest way to,", - "get experience for you cooks, but the larger volume produced means more", - "hauling to take them to storage."}, - ITEM_FOOD_ROAST = { - "Roasts are the highest tier of meals that can be prepared by your ", - "dwarves. They are made in a kitchen with the 'Prepare Lavish Meal'", - "order, and use four ingredients. As there are more ingredients, there", - "is a better chance that a dwarf will like at least one ingredient."}, - ITEM_FOOD_STEW = { - "Stews are the middle tier of meals that can be prepared by your ", - "dwarves. They are made in a kitchen with the 'Prepare Fine Meal' order,", - "and use three ingredients. They provide more food than Biscuits,", - "but are less valuable than Roasts."}, - ITEM_GLOVES_GAUNTLETS = { - "Gauntlets are armor worn on any body part that can grasp, which for", - "dwarves are the hands. They are similar to mittens and gloves, but", - "act as an armor layer and provide much more protection. Like other", - "armor, gauntlets can be made of metal, shell, or bone."}, - ITEM_GLOVES_GLOVES = { - "Gloves cover the hands, wrapping each finger and thumb individually", - "to preserve the wearer's dexterity at the cost of some warmth"}, - ITEM_GLOVES_MITTENS = { - "Mittens cover the fingers together and thumb separately, preserving", - "the ability to grasp but keeping fingers together for more warmth in", - "cold climates"}, - ITEM_HELM_CAP = { - "A cap covers only the crown of the head. It prevents heat loss through", - "a bald pate and protects the skull from falling objects and", - "downward strikes."}, - ITEM_HELM_HELM = { - "A helm covers the entire face and head. It protects the wearer from", - "falling objects and a variety of weapon strikes from all directions.", - "Every other type of head covering, save a hood, is worn under a helm", - "for padding."}, - ITEM_HELM_HOOD = { - "A hood is a soft loose covering for the head and sides of the face.", - "It shields the wearer from cold breezes and can cushion blows from", - "any direction. It is pulled over the wearer's other headgear, providing", - "a final outer layer of protection from the elements."}, - ITEM_HELM_MASK = { - "A mask hides the wearer's face from view and protects it from all but", - "the most accurate piercing attacks. Some can be carved to present the", - "enemy with a more fearsome visage, or to show no face at all.", - "Masks are worn underneath other layers of headgear."}, - ITEM_HELM_SCARF_HEAD = { - "A head scarf is a loose wrap of cloth or leather that is typically", - "worn in hot climates to protect the head from the rays of the sun.", - "It provides light cushioning against some blows."}, - ITEM_HELM_TURBAN = { - "A turban is a length of cloth or leather that is wrapped many times", - "around the head to shield the wearer from the sun's rays and provide", - "several layers of insulation. A turban can be pinned or clasped in", - "place, or simply folded and tucked into a stable configuration."}, - ITEM_HELM_VEIL_FACE = { - "A face veil is a soft covering that protects the lower half of the", - "wearer's face, leaving only the eyes to gaze out. It can prevent", - "noxious fluids from splashing into the wearer's mouth.", - "It is worn under every other layer of headgear."}, - ITEM_HELM_VEIL_HEAD = { - "A veil for the whole head is a wall of sheer cloth or finely-punched", - "leather extending from above the brow to below the chin, and often hung", - "from a more solid cloth headpiece that covers the crown and cheeks.", - "It admits some light but almost entirely obscures the wearer's face."}, - ITEM_INSTRUMENT_DRUM = { - "Short, wide, and round, this cylindrical percussion instrument can", - "play music when banged by one's hands. It is only useful to trade."}, - ITEM_INSTRUMENT_FLUTE = { - "This long cylindrical woodwind instrument can make a wide array of", - "tones and music when blown into. It is only useful to trade."}, - ITEM_INSTRUMENT_HARP = { - "Vaguely triangular in shape, this stringed instrument can play a", - "variety of notes by plucking the strings with one's fingers.", - "It is only useful to trade."}, - ITEM_INSTRUMENT_PICCOLO = { - "Similar to a flute, but smaller and with a higher tone, a piccolo is", - "a cylindrical woodwind instrument. It is only useful to trade."}, - ITEM_INSTRUMENT_TRUMPET = { - "A dwarven brass instrument - which need not be made of brass.", - "It is only useful to trade."}, - ITEM_PANTS_BRAIES = { - "Braies are undergarments that cover from the waist to the knees.", - "Dwarves cannot craft braies, so they must be obtained through", - "other means."}, - ITEM_PANTS_GREAVES = { - "Greaves are plated armor meant to protect the lower legs, though", - "they are equipped as pants."}, - ITEM_PANTS_LEGGINGS = { - "Leggings are garments that cover everything from the waist to the", - "ankles, though with a tighter fit than other trousers."}, - ITEM_PANTS_LOINCLOTH = { - "Loincloths are draped undergarments meant to cover little more than", - "the 'geldables'. Dwarves cannot craft loincloths, so they must be", - "obtained through other means."}, - ITEM_PANTS_PANTS = { - "Trousers are a garment that covers everything from the waist to the", - "ankles. They keep the legs and lower body warm."}, - ITEM_PANTS_SKIRT = { - "A skirt is a cone-shaped garment that hangs from the waist, covering", - "part of the legs. Its use is more for modesty than protection.", - "Dwarves cannot craft skirts, so they must be obtained through other means."}, - ITEM_PANTS_SKIRT_LONG = { - "A skirt is a cone-shaped garment that hangs from the waist, covering", - "part of the legs. Its use is more for modesty than protection. Long", - "skirts fulfil this purpose well. Dwarves cannot craft long skirts,", - "so they must be obtained through other means."}, - ITEM_PANTS_SKIRT_SHORT = { - "A skirt is a cone-shaped garment that hangs from the waist, covering", - "part of the legs. Its use is more for modesty than protection, though", - "short skirts offer less in the way of modesty. Dwarves cannot craft", - "short skirts, so they must be obtained through other means."}, - ITEM_PANTS_THONG = { - "Thongs are strapped undergarments meant to cover little more than", - "the 'geldables'. Dwarves cannot craft thongs, so they must be obtained", - "through other means."}, - ITEM_SHIELD_BUCKLER = { - "A smaller and less protective type of shield. A buckler can be used", - "to block attacks, and with skill anything from a goblin axe", - "to Dragonfire can be deflected."}, - ITEM_SHIELD_SHIELD = { - "Larger and more defensive than a buckler, a full-sized shield can be", - "used to block attacks. With skill anything from a goblin axe to", - "Dragonfire can be deflected."}, - ITEM_SHOES_BOOTS = { - "Boots are more protective kind of shoe, covering from the foot up to", - "the knee."}, - ITEM_SHOES_BOOTS_LOW = { - "Low boots are more protective kind of shoe, covering from the foot up", - "to just past the ankle."}, - ITEM_SHOES_CHAUSSE = { - "Chausses are chainmail armor meant to protect the legs, though these", - "are equipped as footwear. Dwarves cannot craft chausses, so they", - "must be obtained through other means."}, - ITEM_SHOES_SANDAL = { - "Sandals are open footwear consisting of soles and some number of", - "straps. Dwarves cannot craft sandals, so they must be obtained", - "through other means."}, - ITEM_SHOES_SHOES = { - "Shoes are closed footwear meant to protect the feet from rough terrain", - "and the elements."}, - ITEM_SHOES_SOCKS = { - "Socks are tubular articles of clothing, worn on each foot along with", - "shoes or other footwear."}, - ITEM_SIEGEAMMO_BALLISTA = { - "Ballista ammunition, for an enormous siege weapon."}, - ITEM_TOOL_BOWL = { - "Bowls are used to contain individual servings of meals.", - "At the moment, dwarves have no use for these."}, - ITEM_TOOL_CAULDRON = { - "Cauldrons are large metal pots used to cook meals like soups or stews", - "over an open fire. At the moment, dwarves have no use for these."}, - ITEM_TOOL_FORK_CARVING = { - "A carving fork typically has only two prongs and is exceptionally long.", - "It is used to hold down a piece of cooked meat while using a knife."}, - ITEM_TOOL_HIVE = { - " Hives are structures that house colonies of honey bees. To be", - "productive, they need to be constructed on an above-ground tile with", - "an accessible honey bee colony somewhere on the map. Some time after", - "bees are 'installed' by a beekeeper, the hive will be ready to harvest", - "or split into new colonies."}, - ITEM_TOOL_HONEYCOMB = { - "Honeycomb is an intermediate product of beekeeping, produced along", - "with royal jelly when a beekeeper harvests a suitable hive. It must", - "be processed by a Presser at a Screw Press to produce honey, which may", - "be used in cooking or made into mead and a wax cake, which can be used", - "to make low-value crafts."}, - ITEM_TOOL_JUG = { - "Jugs are small food storage containers that hold royal jelly, honey,", - "or oil. They are used by beekeepers when harvesting suitable hives and", - "by pressers when processing honeycomb or seed pastes at a screw press."}, - ITEM_TOOL_KNIFE_BONING = { - "A boning knife has a sharp point and narrow blade. It is an excellent", - "all-around kitchen knife and decent weapon in a pinch."}, - ITEM_TOOL_KNIFE_CARVING = { - "A carving knife is for cutting thin slices of cooked meat for serving.", - "It may be useful as an improvised weapon."}, - ITEM_TOOL_KNIFE_MEAT_CLEAVER = { - "A meat cleaver is a heavy square-bladed knife for cutting bone and", - "meat alike."}, - ITEM_TOOL_KNIFE_SLICING = { - "A slicing knife is for cutting thin slices of cooked meat."}, - ITEM_TOOL_LADLE = { - "A ladle is a large spoon with a long handle and a deep bowl,", - "intended for serving out portions of soups and stews."}, - ITEM_TOOL_LARGE_POT = { - "Large pots are storage containers made of any hard material. They", - "function identically to barrels when brewing or storing food, while", - "being much lighter than barrels made of the same material.", - "Unfortunately, they cannot be used when making dwarven syrup or when", - "building asheries and dyer's workshops."}, - ITEM_TOOL_MINECART = { - "A minecart is a tool for hauling, and can be made from wood or metal.", - "", - "Minecart systems are the most efficient and most complicated way to", - "move items, and can do anything from improving industrial efficiency,", - "to transporting magma or launching hundreds of weapons at enemies.", - "Misuse may result in horrific injury to drivers and pedestrians."}, - ITEM_TOOL_MORTAR = { - "Half of a mortar and pestle, the mortar is a bowl in which to grind", - "up plants or other reagents."}, - ITEM_TOOL_NEST_BOX = { - "A place for birds to lay eggs. Must be built before use.", - "Forbid eggs to hatch into chicks before a dwarf picks them up."}, - ITEM_TOOL_PESTLE = { - "Half of a mortar and pestle, the pestle is a stick used to grind up", - "plants or other reagents."}, - ITEM_TOOL_POUCH = { - "A small bag used to carry a variety of tools."}, - ITEM_TOOL_STEPLADDER = { - "A small stepladder. If you have one of these, you can use zones to", - "assign your dwarves to pick fruit from certain trees."}, - ITEM_TOOL_WHEELBARROW = { - "A small hand-cart with long handles and a single wheel, this", - "wheelbarrow makes heavy hauling jobs much more manageable."}, - ITEM_TOY_AXE = { - "A small toy axe without an edge. Useless except as a trade good."}, - ITEM_TOY_BOAT = { - "A tiny model of a boat. Only good for trade."}, - ITEM_TOY_HAMMER = { - "A toy hammer. Its only use is to sell."}, - ITEM_TOY_MINIFORGE = { - "A model of a blacksmith's forge that dwarf children love.", - "Only useful as a trade good."}, - ITEM_TOY_PUZZLEBOX = { - "A perplexing toy that dwarves of all ages enjoy.", - "Its only use is as a trade good."}, - ITEM_TRAPCOMP_ENORMOUSCORKSCREW = { - "A massive screw-like object. Can be used to make a pump,", - "or as a component in a trap."}, - ITEM_TRAPCOMP_GIANTAXEBLADE = { - "This massive blade is typically made of metal and can be used in weapon", - "traps, swinging once to slice anyone unfortunate enough to activate it."}, - ITEM_TRAPCOMP_LARGESERRATEDDISC = { - "Serrated discs are typically made of metal and can be used in weapon", - "traps, in which they eviscerate its victims with three powerful slicing", - "attacks. Such traps have a tendency to sever multiple body parts and", - "make a gigantic mess."}, - ITEM_TRAPCOMP_MENACINGSPIKE = { - "Menacing spikes are made of wood or metal and can be used in weapon", - "traps or upright spike traps, in which they impale the victim. They", - "are especially effective against unarmored foes or, in an upright", - "spike trap, anyone falling from great heights."}, - ITEM_TRAPCOMP_SPIKEDBALL = { - "This trap component menaces with spikes of wood or metal. It hits three", - "times with its spikes, but does not penetrate as deeply as a menacing", - "spike. Compared to other trap components, spiked balls are slightly", - "more effective against heavily armored foes. They also make for a", - "surprisingly valuable trade good, on par with serrated discs."}, - ITEM_WEAPON_AXE_BATTLE = { - "A battle axe is an edged weapon: essentially a sharp blade", - "mounted along the end of a short and heavy handle.", - "", - "Dwarves can forge battle axes out of any weapon-grade metal,", - "though those with superior edge properties are more effective.", - "", - "A battle axe may also be used as a tool for chopping down trees."}, - ITEM_WEAPON_AXE_GREAT = { - "This is an axe nearly twice as large as a battle axe. Its size", - "makes it unsuitable for a dwarf, but those who can wield it find", - "its increased size and weight contribute to its effectiveness."}, - ITEM_WEAPON_AXE_TRAINING = { - "As a battleaxe made from wood, this practise weapon is useful for", - "training recruits. Thanks to good craftsdwarfship, it can also", - "be used to cut down trees."}, - ITEM_WEAPON_BLOWGUN = { - "A very simple ranged weapon: blow into one end of the long narrow", - "tube, and project a pellet or dart into the body of one's prey.", - "If the prey approaches, this blowgun makes a useless melee weapon."}, - ITEM_WEAPON_BOW = { - "Bows are the preferred ranged weapon for elves and goblins, and", - "shoot arrows as projectiles. As they are a foreign weapon, they", - "cannot be made in your fort. In melee, bowmen will use their bow as", - "a weapon, training the swordsman skill."}, - ITEM_WEAPON_CROSSBOW = { - "The favoured ranged weapon of choice for any dwarf, crossbows can be", - "made of wood, bones or metal, and shoot bolts as projectiles. Hunters", - "or marks-dwarves that run out of ammunition will use their crossbow", - "as a melee weapon, training the hammerdwarf skill."}, - ITEM_WEAPON_DAGGER_LARGE = { - "A large dagger is a edge weapon that is essentially just a bit smaller", - "than a short sword. It's used for stabbing rather than slashing. Large", - "daggers use and train the knife user skill, and are common weapons for", - "kobold and goblin thieves. As foreign weapons dwarves cannot forge", - "large daggers."}, - ITEM_WEAPON_FLAIL = { - "A flail is a blunt weapon that consists of a rounded weight attached to", - "a handle by a length of chain. Flails are the same size as a morningstar,", - "but have a contact area twice as large as a much larger maul. As foreign", - "weapons dwarves cannot forge flails. Flails use and train the", - "macedwarf skill."}, - ITEM_WEAPON_HALBERD = { - "A halberd is a foreign weapon, and cannot be made by your dwarves.", - "Even the largest and strongest dwarves cannot use a halberd, making", - "them useless in military terms. They can however be placed in a weapon", - "trap or melted down to provide metal bars, redeeming them."}, - ITEM_WEAPON_HAMMER_WAR = { - "A war hammer is a blunt weapon that is essentially a hammer with a long", - "handle. War hammers use and train the hammerdwarf skill. Dwarves can", - "forge war hammers out of any weapons-grade metal, though those with", - "higher densities tend to cause more damage."}, - ITEM_WEAPON_MACE = { - "A mace is a blunt weapon that consists of a rounded or flanged weight", - "mounted on the end of a handle. Despite similarities to a morningstar", - "in appearance, a mace is 60% larger and has twice the contact area.", - "Maces use and train the macedwarf skill. Dwarves can forge maces out of", - "any weapons-grade metal, though those with higher densities", - "(like silver) cause more damage."}, - ITEM_WEAPON_MAUL = { - "A maul is a blunt weapon that is essentially a very large war hammer,", - "similar to a sledgehammer. Mauls are more than three times larger than", - "standard war hammers, with a similar 'bash' attack. Mauls also have", - "ten times the contact area and greatly reduced penetration, which", - "reduces their effectiveness. Mauls use and train the hammerdwarf", - "skill. Being foreign weapons, dwarves cannot forge mauls."}, - ITEM_WEAPON_MORNINGSTAR = { - "A morningstar is an edged weapon that consists of a spiked ball mounted", - "on the end of a handle. Despite similarities to a mace in appearance,", - "a morningstar's size and contact area are closer to those of a war", - "hammer. Specifically, a morningstar is 25% larger than a war hammer", - "with the same contact area, and uses piercing damage to inflict internal", - "injuries. Morningstars use and train the macedwarf skill."}, - ITEM_WEAPON_PICK = { - "The most important item for a beginning fortress, a pick can", - "get a party underground. Also crucial mining for stone or", - "metals, expansion of living space, and so on.", - "", - "A pick is also useful as a weapon, though putting miners in the", - "military causes equipment clashes."}, - ITEM_WEAPON_PIKE = { - "A pike is a weapon that is essentially a very long spear.", - "Pikes use and train the Pikedwarf skill. As foreign weapons,", - "dwarves cannot forge pikes."}, - ITEM_WEAPON_SCIMITAR = { - "A scimitar is an edged weapon with a curved blade that is very similar", - "to a short sword. Scimitars use and train the swordsdwarf skill.", - "As foreign weapons dwarves cannot forge scimitars."}, - ITEM_WEAPON_SCOURGE = { - "A scourge is an edge weapon that consists of a spike or bladed weight", - "on the end of a flexible length of material that can be swung at", - "high speed. Scourges are similar to whips, though the whip is a blunt", - "weapon with an even smaller contact area.Scourges use and train the", - "lasher skill. As foreign weapons dwarves cannot forge scourges."}, - ITEM_WEAPON_SPEAR = { - "A pole weapon consisting of a shaft, usually of wood, with a pointed", - "head made of metal or just the sharpened end of the shaft itself.", - "With the ability to pin opponents, spears are most effective with axe", - "or macedwarves for combo attacks. "}, - ITEM_WEAPON_SPEAR_TRAINING = { - "A wooden training spear, this has no sharp edges and thus presents", - "little risk of injury. Military dwarves can become", - "attached to them, and refuse to swap them for weapons that cause", - "actual injury to your enemies."}, - ITEM_WEAPON_SWORD_2H = { - "An enormous sword taller than many humans. Victims may be split in", - "two by a single blow, though no dwarf is large enough to wield a", - "greatsword and do so. As foreign weapons, dwarves cannot forge them."}, - ITEM_WEAPON_SWORD_LONG = { - "A longsword is a classic weapon, consisting of a short handle and a", - "long sharp blade. Most dwarves are large enough to use a longsword,", - "but as foreign weapons cannot forge them."}, - ITEM_WEAPON_SWORD_SHORT = { - "A sword just the right size for dwarves, though small dwarves may", - "need both hands. Shortswords can be made from metal at a forge."}, - ITEM_WEAPON_SWORD_SHORT_TRAINING = { - "A wooden training sword, this has no sharp edges and thus presents", - "little risk of injury. Military dwarves can become", - "attached to them, and refuse to swap them for weapons that cause", - "actual injury to your enemies."}, - ITEM_WEAPON_WHIP = { - "A highly effective weapon known to cause large amounts of pain.", - "It cannot be forged by dwarves."}, - MEAT = { "Butchering an animal gives meat, the amount depending on the size", - "of the butchered animal. Along with plants, meat is the", - "backbone of every food industry."}, - MILLSTONE = { - "A large grinding stone, used in a mill to produce flour, sugar, and", - "dyes much faster than a quern. It is too large to be operated by hand,", - "and must be powered for operation. Millstones are made of stone."}, - ORTHOPEDIC_CAST = { - "Casts are made from plaster, and are used to keep broken bones in", - "place until they are healed. Applying a cast requires a bucket,", - "cloth and a water source."}, - PIPE_SECTION = { - "An enormous piece of pipe, it is a part of a screw pump."}, - QUERN = { "A hand-operated mill for plants, grains, and seeds. It mills plants", - "much slower than a millstone. Must be built from stone."}, - QUIVER = { "Item used to hold ammunition, made out of leather. Hunting dwarves", - "and crossbow dwarves will automatically grab one to store their ammo."}, - RING = { "A ring is an item of jewellery, which does not interfere with", - "wearing other equipment. Eleven rings can be worn on each finger", - "or toe, for a maximum of 220 rings."}, - ROCK = { "A small rock, sharpened as a weapon in Adventure mode."}, - ROUGH = { "Rough gemstones and raw glass are cut by a Gem Cutter at a Jeweler's", - "Workshop into small decorative gems. Sometimes, the gem-cutting job", - "results in a craft or large gem that is useless except as a very", - "valuable trade good."}, - SCEPTER = { "A scepter is a short, ornamental rod or wand typically associated", - "with royalty. It's only use is as a trade good."}, - SKIN_TANNED = { - "The tanned hide of animals is flexible enough to be made into an", - "assortment of goods for military and civilian use. Leather can also", - "be used to decorate items with sewn images at a Leather Works. Armor", - "and shields made from leather are not terribly effective, but are", - "still better than nothing at all."}, - SLAB = { "A memorial stone, used to quiet restless ghost when engraved with", - "the name of the deceased and built."}, - SMALLGEM = {"Cut gemstones and the odd gizzard stone (a product of butchering", - "certain species of animals) are used by a Gem Setter to decorate items", - "at a Jeweler's Workshop."}, - SPLINT = { "Splints are used to immobilise fractured limbs. They are made out of", - "wood or metal, and allow dwarves to leave the hospital and continue", - "their normal jobs. Splints are applied with the bonedoctor skill."}, - STATUE = { "A large piece of art carved in the likeness of a creature. Statues", - "can be installed on any open floor space, but cannot share the space", - "with creatures. Statues can be used as the focal point of a", - "recreational statue garden. Dwarves will admire or revile as they", - "pass, depending on the statue and the individual's preferences."}, - TABLE = { "A flat-topped piece of furniture useful as a work-surface for a", - "scribe or a dining-surface for a hungry dwarf. Typically found in", - "shops, dinning rooms, and offices. Dining rooms may be designated and", - "assigned from them, though dwarves will complain if there are too few."}, - THREAD = { "A small bundle of processed material, ready to be woven into cloth.", - "Thread made from animal hair will not be used to make cloth. Thread", - "can also be used by doctors to sew wounds shut. It is sourced from", - "shearing, plant processing, trade, or web gathering. It can be dyed", - "for additional value before being woven."}, - TOTEM = { "A carved and polished skull."}, - TRACTION_BENCH = { - "A special hospital bed made to secure dwarves with complex or", - "overlapping fractures until healed. Patients may need several months", - "or more in a traction bench to heal. Constructed from a table,", - "a mechanism, and a rope or chain."}, - TRAPPARTS = { - "Used to build traps, levers and other machines."}, - WEAPONRACK = { - "Furniture used for training. Barracks may be designated and assigned", - "from them. Military squads may use their assigned barracks for", - "training, storage, or sleeping, depending on the settings."}, - WINDOW = { "Furniture used for ambiance. Either made in a glass furnace from glass", - "or built on site using three cut gems. While it is treated as a wall,", - "it does not support constructions. Passing dwarves will admire them."}, - WOOD = { "A porous and fibrous structural tissue found in the stems and roots", - "of trees and underground fungus. Wood is renewable and essential for", - "numerous industries. It can be made into charcoal, ash for further", - "processing, furniture, crafts, tools, some trap components, training", - "gear, and (ineffective) weapons and armor. Elves take serious offence", - "when wood or wooden items are offered in trade."} -} diff --git a/scripts/lever.rb b/scripts/lever.rb deleted file mode 100644 index 05a159861..000000000 --- a/scripts/lever.rb +++ /dev/null @@ -1,152 +0,0 @@ -# control your levers from the dfhack console -=begin - -lever -===== -Allow manipulation of in-game levers from the dfhack console. - -Can list levers, including state and links, with:: - - lever list - -To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``. -This is the same as :kbd:`q` querying the building and queue a :kbd:`P` pull request. - -To magically toggle the lever immediately, use:: - - lever pull 42 --now - -=end - -def lever_pull_job(bld) - ref = DFHack::GeneralRefBuildingHolderst.cpp_new - ref.building_id = bld.id - - job = DFHack::Job.cpp_new - job.job_type = :PullLever - job.pos = [bld.centerx, bld.centery, bld.z] - job.general_refs << ref - bld.jobs << job - df.job_link job - - puts lever_descr(bld) -end - -def lever_pull_cheat(bld) - bld.linked_mechanisms.each { |i| - i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| - r.building_tg.setTriggerState(bld.state) - } - } - - bld.state = (bld.state == 0 ? 1 : 0) - - puts lever_descr(bld) -end - -def lever_descr(bld, idx=nil) - ret = [] - - # lever description - descr = '' - descr << "#{idx}: " if idx - descr << "lever ##{bld.id} " - descr << "(#{bld.name}) " if bld.name.length != 0 - descr << "@[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" - bld.jobs.each { |j| - if j.job_type == :PullLever - flags = '' - flags << ', repeat' if j.flags.repeat - flags << ', suspended' if j.flags.suspend - descr << " (pull order#{flags})" - end - } - - bld.linked_mechanisms.map { |i| - i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) - }.flatten.each { |r| - # linked building description - tg = r.building_tg - state = '' - if tg.respond_to?(:gate_flags) - state << (tg.gate_flags.closed ? 'closed' : 'opened') - state << ", closing (#{tg.timer})" if tg.gate_flags.closing - state << ", opening (#{tg.timer})" if tg.gate_flags.opening - end - - ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") - - # indent other links - descr = descr.gsub(/./, ' ') - } - - ret << descr if ret.empty? - - ret -end - -def lever_list - @lever_list = [] - df.world.buildings.other[:TRAP].find_all { |bld| - bld.trap_type == :Lever - }.sort_by { |bld| bld.id }.each { |bld| - puts lever_descr(bld, @lever_list.length) - @lever_list << bld.id - } -end - - -@lever_list ||= [] - -case $script_args[0] -when 'pull' - cheat = $script_args.delete('--cheat') || $script_args.delete('--now') - - if $script_args[1].nil? - bld = df.building_find(:selected) if not bld - raise 'no lever under cursor and no lever id given' if not bld - else - id = $script_args[1].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld - end - - if cheat - lever_pull_cheat(bld) - else - lever_pull_job(bld) - end - -when 'list' - lever_list - -when /^\d+$/ - id = $script_args[0].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld - - puts lever_descr(bld) - -else - - puts < 0 and bx+dx < df.world.map.x_count-1 and by+dy > 0 and by+dy < df.world.map.y_count-1 - pos = [bx+dx, by+dy, bz] - end - } - } - } - } - df.center_viewscreen(*pos) - df.map_tile_at(*pos).dig - puts "Here is some #{df.world.raws.inorganics[found_mat].id}" - else - puts "Cannot find unmined #{mats.map { |mat| df.world.raws.inorganics[mat].id }.join(', ')}" - end - -else - puts "Available ores:", $ore_veins.sort_by { |mat, pos| pos.length }.map { |mat, pos| - ore = df.world.raws.inorganics[mat] - metals = ore.metal_ore.mat_index.map { |m| df.world.raws.inorganics[m] } - ' ' + ore.id.downcase + ' (' + metals.map { |m| m.id.downcase }.join(', ') + ')' - } - -end diff --git a/scripts/lua.lua b/scripts/lua.lua deleted file mode 100644 index 17c47d41a..000000000 --- a/scripts/lua.lua +++ /dev/null @@ -1,91 +0,0 @@ --- Execute lua commands interactively or from files. ---[[=begin - -lua -=== -There are the following ways to invoke this command: - -1. ``lua`` (without any parameters) - - This starts an interactive lua interpreter. - -2. ``lua -f "filename"`` or ``lua --file "filename"`` - - This loads and runs the file indicated by filename. - -3. ``lua -s ["filename"]`` or ``lua --save ["filename"]`` - - This loads and runs the file indicated by filename from the save - directory. If the filename is not supplied, it loads "dfhack.lua". - -4. ``:lua`` *lua statement...* - - Parses and executes the lua statement like the interactive interpreter would. - -=end]] - -local args={...} -local cmd = args[1] - -env = env or {} -setmetatable(env, {__index = function(self, k) - if k == 'scr' or k == 'screen' then - return dfhack.gui.getCurViewscreen() - elseif k == 'bld' or k == 'building' then - return dfhack.gui.getSelectedBuilding() - elseif k == 'item' then - return dfhack.gui.getSelectedItem() - elseif k == 'job' then - return dfhack.gui.getSelectedJob() - elseif k == 'wsjob' or k == 'workshop_job' then - return dfhack.gui.getSelectedWorkshopJob() - elseif k == 'unit' then - return dfhack.gui.getSelectedUnit() - else - return _G[k] - end -end}) - -if cmd=="--file" or cmd=="-f" then - local f,err=loadfile (args[2]) - if f==nil then - qerror(err) - end - dfhack.safecall(f,table.unpack(args,3)) -elseif cmd=="--save" or cmd=="-s" then - if df.global.world.cur_savegame.save_dir=="" then - qerror("Savefile not loaded") - end - local fname=args[2] or "dfhack.lua" - fname=string.format("data/save/%s/%s",df.global.world.cur_savegame.save_dir,fname) - local f,err=loadfile (fname) - if f==nil then - qerror(err) - end - dfhack.safecall(f,table.unpack(args,3)) -elseif cmd~=nil then - -- Support some of the prefixes allowed by dfhack.interpreter - local prefix - if string.match(cmd, "^[~@!]") then - prefix = string.sub(cmd, 1, 1) - cmd = 'return '..string.sub(cmd, 2) - end - - local f,err=load(cmd,'=(lua command)', 't') - if f==nil then - qerror(err) - end - - local rv = table.pack(dfhack.safecall(f,table.unpack(args,2))) - - if rv[1] and prefix then - print(table.unpack(rv,2,rv.n)) - if prefix == '~' then - printall(rv[2]) - elseif prefix == '@' then - printall_ipairs(rv[2]) - end - end -else - dfhack.interpreter("lua","lua.history",env) -end diff --git a/scripts/make-legendary.lua b/scripts/make-legendary.lua deleted file mode 100644 index da29cd4c2..000000000 --- a/scripts/make-legendary.lua +++ /dev/null @@ -1,141 +0,0 @@ --- Make a skill or skills of a unit Legendary +5 --- by vjek ---[[=begin - -make-legendary -============== -Makes the selected dwarf legendary in one skill, a group of skills, or all -skills. View groups with ``make-legendary classes``, or all skills with -``make-legendary list``. Use ``make-legendary MINING`` when you need something -dug up, or ``make-legendary all`` when only perfection will do. - -=end]] - --- this function will return the number of elements, starting at zero. --- useful for counting things where #foo doesn't work -function count_this(to_be_counted) - local count = -1 - local var1 = "" - while var1 ~= nil do - count = count + 1 - var1 = (to_be_counted[count]) - end - count=count-1 - return count -end - -function getName(unit) - return dfhack.df2console(dfhack.TranslateName(dfhack.units.getVisibleName(unit))) -end - -function make_legendary(skillname) - local skillnamenoun,skillnum - unit=dfhack.gui.getSelectedUnit() - - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - - if (df.job_skill[skillname]) then - skillnamenoun = df.job_skill.attrs[df.job_skill[skillname]].caption_noun - else - print ("The skill name provided is not in the list.") - return - end - - if skillnamenoun ~= nil then - utils = require 'utils' - skillnum = df.job_skill[skillname] - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = skillnum, rating = 20 }, 'id') - print (getName(unit) .. " is now a Legendary "..skillnamenoun) - else - print ("Empty skill name noun, bailing out!") - return - end -end - -function PrintSkillList() - local count_max = count_this(df.job_skill) - local i - for i=0, count_max do - print("'"..df.job_skill.attrs[i].caption.."' "..df.job_skill[i].." Type: "..df.job_skill_class[df.job_skill.attrs[i].type]) - end - print ("Provide the UPPER CASE argument, for example: PROCESSPLANTS rather than Threshing") -end - -function BreathOfArmok() - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - local i - local count_max = count_this(df.job_skill) - utils = require 'utils' - for i=0, count_max do - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - print ("The breath of Armok has engulfed "..getName(unit)) -end - -function LegendaryByClass(skilltype) - unit=dfhack.gui.getSelectedUnit() - if unit==nil then - print ("No unit under cursor! Aborting with extreme prejudice.") - return - end - - utils = require 'utils' - local i - local skillclass - local count_max = count_this(df.job_skill) - for i=0, count_max do - skillclass = df.job_skill_class[df.job_skill.attrs[i].type] - if skilltype == skillclass then - print ("Skill "..df.job_skill.attrs[i].caption.." is type: "..skillclass.." and is now Legendary for "..getName(unit)) - utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id') - end - end -end - -function PrintSkillClassList() - local i - local count_max = count_this(df.job_skill_class) - for i=0, count_max do - print(df.job_skill_class[i]) - end - print ("Provide one of these arguments, and all skills of that type will be made Legendary") - print ("For example: Medical will make all medical skills legendary") -end - ---main script operation starts here ----- -local opt = ... -local skillname - -if opt then - if opt=="list" then - PrintSkillList() - return - end - if opt=="classes" then - PrintSkillClassList() - return - end - if opt=="all" then - BreathOfArmok() - return - end - if opt=="Normal" or opt=="Medical" or opt=="Personal" or opt=="Social" or opt=="Cultural" or opt=="MilitaryWeapon" or opt=="MilitaryAttack" or opt=="MilitaryDefense" or opt=="MilitaryMisc" then - LegendaryByClass(opt) - return - end - skillname = opt -else - print ("No skillname supplied.\nUse argument 'list' to see a list, 'classes' to show skill classes, or use 'all' if you want it all!") - print ("Example: To make a legendary miner, use make_legendary MINING") - return -end - -make_legendary(skillname) diff --git a/scripts/make-monarch.lua b/scripts/make-monarch.lua deleted file mode 100644 index 1c8764367..000000000 --- a/scripts/make-monarch.lua +++ /dev/null @@ -1,39 +0,0 @@ ---set target unit as king/queen ---[[=begin - -make-monarch -============ -Make the selected unit King or Queen of your civilisation. - -=end]] - -local unit=dfhack.gui.getSelectedUnit() -if not unit then qerror("No unit selected") end -local newfig=dfhack.units.getNemesis(unit).figure -local my_entity=df.historical_entity.find(df.global.ui.civ_id) -local monarch_id -for k,v in pairs(my_entity.positions.own) do - if v.code=="MONARCH" then - monarch_id=v.id - break - end -end -if not monarch_id then qerror("No monarch found!") end -local old_id -for pos_id,v in pairs(my_entity.positions.assignments) do - if v.position_id==monarch_id then - old_id=v.histfig - v.histfig=newfig.id - local oldfig=df.historical_figure.find(old_id) - - for k,v in pairs(oldfig.entity_links) do - if df.histfig_entity_link_positionst:is_instance(v) and v.assignment_id==pos_id and v.entity_id==df.global.ui.civ_id then - oldfig.entity_links:erase(k) - break - end - end - newfig.entity_links:insert("#",{new=df.histfig_entity_link_positionst,entity_id=df.global.ui.civ_id, - link_strength=100,assignment_id=pos_id,start_year=df.global.cur_year}) - break - end -end diff --git a/scripts/markdown.lua b/scripts/markdown.lua deleted file mode 100644 index 14ecdaa2c..000000000 --- a/scripts/markdown.lua +++ /dev/null @@ -1,239 +0,0 @@ --- Save a text screen in markdown (eg for reddit) --- This is a derivatiwe work based upon scripts/forum-dwarves.lua by Caldfir and expwnent --- Adapted for markdown by Mchl https://github.com/Mchl ---[[=begin - -markdown -======== -Save a copy of a text screen in markdown (for reddit among others). -Use ``markdown help`` for more details. - -=end]] - -local args = {...} - -if args[1] == 'help' then - print([[ -description: - This script will attempt to read the current df-screen, and if it is a - text-viewscreen (such as the dwarf 'thoughts' screen or an item / creature - 'description') or an announcement list screen (such as announcements and - combat reports) then append a marked-down version of this text to the - target file (for easy pasting on reddit for example). - Previous entries in the file are not overwritten, so you - may use the 'markdown' command multiple times to create a single - document containing the text from multiple screens (eg: text screens - from several dwarves, or text screens from multiple artifacts/items, - or some combination). - -usage: - markdown [/n] [filename] - - /n - overwrites contents of output file - filename - if provided, the data will be saved in md_filename.md instead - of default md_export.md - -known screens: - The screens which have been tested and known to function properly with - this script are: - 1: dwarf/unit 'thoughts' screen - 2: item/art 'description' screen - 3: individual 'historical item/figure' screens - 4: manual - 4: announements screen - 5: combat reports screen - 6: latest news (when meeting with liaison) - There may be other screens to which the script applies. It should be - safe to attempt running the script with any screen active, with an - error message to inform you when the selected screen is not appropriate - for this script. - -target file: - The default target file's name is 'md_export.md'. A remider to this effect - will be displayed if the script is successful. - -character encoding: - The text will likely be using system-default encoding, and as such - will likely NOT display special characters (eg:é,õ,ç) correctly. To - fix this, you need to modify the character set that you are reading - the document with. 'Notepad++' is a freely available program which - can do this using the following steps: - 1: open the document in Notepad++ - 2: in the menu-bar, select - Encoding->Character Sets->Western European->OEM-US - 3: copy the text normally to wherever you want to use it -]]) - return -end - -local writemode = 'a' - --- check if we want to append to an existing file (default) or overwrite previous contents -if args[1] == '/n' then - writemode = 'w' - table.remove(args, 1) -end - -local filename - -if args[1] ~= nil then - filename = 'md_' .. table.remove(args, 1) .. '.md' -else - filename = 'md_export.md' -end - -local utils = require 'utils' -local gui = require 'gui' -local dialog = require 'gui.dialogs' - - - - -local scrn = dfhack.gui.getCurViewscreen() -local flerb = dfhack.gui.getFocusString(scrn) - -local months = { - [1] = 'Granite', - [2] = 'Slate', - [3] = 'Felsite', - [4] = 'Hematite', - [5] = 'Malachite', - [6] = 'Galena', - [7] = 'Limestone', - [8] = 'Sandstone', - [9] = 'Timber', - [10] = 'Moonstone', - [11] = 'Opal', - [12] = 'Obsidian', -} - -local function getFileHandle() - return io.open(filename, writemode) -end - -local function closeFileHandle(handle) - handle:write('\n***\n\n') - handle:close() - print ('Data exported to "' .. filename .. '"') -end - -local function reformat(strin) - local strout = strin - - -- [P] tags seem to indicate a new paragraph - local newline_idx = string.find(strout, '[P]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout, 1, newline_idx - 1) .. '\n***\n\n' .. string.sub(strout, newline_idx + 3) - newline_idx = string.find(strout, '[P]', 1, true) - end - - -- [R] tags seem to indicate a new 'section'. Let's mark it with a horizontal line. - newline_idx = string.find(strout, '[R]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout, 1, newline_idx - 1) .. '\n***\n\n' .. string.sub(strout,newline_idx + 3) - newline_idx = string.find(strout, '[R]', 1, true) - end - - -- No idea what [B] tags might indicate. Just removing them seems to work fine - newline_idx = string.find(strout, '[B]', 1, true) - while newline_idx ~= nil do - strout = string.sub(strout, 1, newline_idx - 1) .. string.sub(strout,newline_idx + 3) - newline_idx = string.find(strout, '[B]', 1, true) - end - - -- Reddit doesn't support custom colors in markdown. We need to remove all color information :( - local color_idx = string.find(strout, '[C:', 1, true) - while color_idx ~= nil do - strout = string.sub(strout, 1, color_idx - 1) .. string.sub(strout, color_idx + 9) - color_idx = string.find(strout, '[C:', 1, true) - end - - return strout -end - -local function formattime(year, ticks) - -- Dwarf Mode month is 33600 ticks long - local month = math.floor(ticks / 33600) - local dayRemainder = ticks - month * 33600 - - -- Dwarf Mode day is 1200 ticks long - local day = math.floor(dayRemainder / 1200) - local timeRemainder = dayRemainder - day * 1200 - - -- Assuming a 24h day each Dwarf Mode tick corresponds to 72 seconds - local seconds = timeRemainder * 72 - - local H = string.format("%02.f", math.floor(seconds / 3600)); - local m = string.format("%02.f", math.floor(seconds / 60 - (H * 60))); - local i = string.format("%02.f", math.floor(seconds - H * 3600 - m * 60)); - - day = day + 1 - if (day == 1 or day == 21) then - day = day .. 'st' - elseif (day == 2 or day == 22) then - day = day .. 'nd' - else - day = day .. 'th' - end - - return (day .. " " .. months[month + 1] .. " " .. year .. " " .. H .. ":" .. m..":" .. i) -end - -if flerb == 'textviewer' then - - local lines = scrn.src_text - - if lines ~= nil then - - local log = getFileHandle() - log:write('### ' .. scrn.title .. '\n') - - print('Exporting ' .. scrn.title .. '\n') - - for n,x in ipairs(lines) do - log:write(reformat(x.value).." ") --- debug output --- print(x.value) - end - closeFileHandle(log) - end - -elseif flerb == 'announcelist' then - - local lines = scrn.reports - - if lines ~= nil then - local log = getFileHandle() - local lastTime = "" - - for n,x in ipairs(lines) do - local currentTime = formattime(x.year, x.time) - if (currentTime ~= lastTime) then - lastTime = currentTime - log:write('\n***\n\n') - log:write('## ' .. currentTime .. '\n') - end --- debug output --- print(x.text) - log:write(x.text .. '\n') - end - closeFileHandle(log) - end - - -elseif flerb == 'topicmeeting' then - local lines = scrn.text - - if lines ~= nil then - local log = getFileHandle() - - for n,x in ipairs(lines) do --- debug output --- print(x.value) - log:write(x.value .. '\n') - end - closeFileHandle(log) - end -else - print 'This is not a textview, announcelist or topicmeeting screen. Can\'t export data, sorry.' -end diff --git a/scripts/masspit.rb b/scripts/masspit.rb deleted file mode 100644 index 9c276faf5..000000000 --- a/scripts/masspit.rb +++ /dev/null @@ -1,51 +0,0 @@ -# pit all caged creatures in a zone -=begin - -masspit -======= -Designate all creatures in cages on top of a pit/pond activity zone for pitting. -Works best with an animal stockpile on top of the zone. - -Works with a zone number as argument (eg ``Activity Zone #6`` -> ``masspit 6``) -or with the game cursor on top of the area. - -=end - -case $script_args[0] -when '?', 'help' - puts <= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y - } - -end - -if not bld - puts "Please select a pit/pond zone" - throw :script_finished -end - -found = 0 -df.world.items.other[:CAGE].each { |cg| - next if not cg.flags.on_ground - next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2 - next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first - found += 1 - u = uref.unit_tg - puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}" - u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id) - bld.assigned_units << u.id -} -puts "No creature available for pitting" if found == 0 diff --git a/scripts/migrants-now.lua b/scripts/migrants-now.lua deleted file mode 100644 index aa9ad7b3f..000000000 --- a/scripts/migrants-now.lua +++ /dev/null @@ -1,16 +0,0 @@ --- Force a migrant wave (only after hardcoded waves) ---[[=begin - -migrants-now -============ -Forces an immediate migrant wave. Only works after migrants have -arrived naturally. Equivalent to `modtools/force` ``-eventType migrants`` - -=end]] -df.global.timed_events:insert('#',{ - new = true, - type = df.timed_event_type.Migrants, - season = df.global.cur_season, - season_ticks = df.global.cur_season_tick+1, - entity = df.historical_entity.find(df.global.ui.civ_id) -}) diff --git a/scripts/modtools/about.txt b/scripts/modtools/about.txt deleted file mode 100644 index d2924b301..000000000 --- a/scripts/modtools/about.txt +++ /dev/null @@ -1,17 +0,0 @@ -``modtools/*`` scripts provide tools for modders, often with changes -to the raw files, and are not intended to be called manually by end-users. - -These scripts are mostly useful for raw modders and scripters. They all have -standard arguments: arguments are of the form ``tool -argName1 argVal1 --argName2 argVal2``. This is equivalent to ``tool -argName2 argVal2 -argName1 -argVal1``. It is not necessary to provide a value to an argument name: ``tool --argName3`` is fine. Supplying the same argument name multiple times will -result in an error. Argument names are preceded with a dash. The ``-help`` -argument will print a descriptive usage string describing the nature of the -arguments. For multiple word argument values, brackets must be used: ``tool --argName4 [ sadf1 sadf2 sadf3 ]``. In order to allow passing literal braces as -part of the argument, backslashes are used: ``tool -argName4 [ \] asdf \foo ]`` -sets ``argName4`` to ``\] asdf foo``. The ``*-trigger`` scripts have a similar -policy with backslashes. - -Any of these scripts can be called with ``-help`` for more information. diff --git a/scripts/modtools/add-syndrome.lua b/scripts/modtools/add-syndrome.lua deleted file mode 100644 index 2de2462b1..000000000 --- a/scripts/modtools/add-syndrome.lua +++ /dev/null @@ -1,114 +0,0 @@ --- Add or remove syndromes from units ---author expwnent ---[[=begin - -modtools/add-syndrome -===================== -This allows adding and removing syndromes from units. - -=end]] -local syndromeUtil = require 'syndrome-util' -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'syndrome', - 'resetPolicy', - 'erase', - 'eraseOldest', - 'eraseAll', - 'target', - 'skipImmunities', - 'eraseClass' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/add-syndrome usage: -arguments: - -help - print this help message - -syndrome name - the name of the syndrome to operate on - examples: - "gila monster bite" - -resetPolicy policy - specify a policy of what to do if the unit already has an instance of the syndrome - examples: - NewInstance - default behavior: create a new instance of the syndrome - DoNothing - ResetDuration - AddDuration - -erase - instead of adding an instance of the syndrome, erase one - -eraseAll - erase every instance of the syndrome - -eraseClass SYN_CLASS - erase every instance of every syndrome with the given SYN_CLASS - -target id - the unit id of the target unit - examples: - 0 - 28 - -skipImmunities - add the syndrome to the target regardless of whether it is immune to the syndrome -]]) - return -end - -if not args.target then - error 'Specify a target.' -end -local targ = df.unit.find(tonumber(args.target)) -if not targ then - error ('Could not find target: ' .. args.target) -end -args.target = targ - -if args.eraseClass then - local count = syndromeUtil.eraseSyndromeClass(args.target, args.eraseClass) - --print('deleted ' .. tostring(count) .. ' syndromes') - return -end - -if args.resetPolicy then - args.resetPolicy = syndromeUtil.ResetPolicy[args.resetPolicy] - if not args.resetPolicy then - error ('Invalid reset policy.') - end -end - -if not args.syndrome then - error 'Specify a syndrome name.' -end - -local syndrome -for _,syn in ipairs(df.global.world.raws.syndromes.all) do - if syn.syn_name == args.syndrome then - syndrome = syn - break - end -end -if not syndrome then - error ('Invalid syndrome: ' .. args.syndrome) -end -args.syndrome = syndrome - -if args.erase then - syndromeUtil.eraseSyndrome(args.target,args.syndrome.id,args.eraseOldest) - return -end - -if args.eraseAll then - syndromeUtil.eraseSyndromes(args.target,args.syndrome.id) - return -end - -if skipImmunities then - syndromeUtil.infectWithSyndrome(args.target,args.syndrome,args.resetPolicy) -else - syndromeUtil.infectWithSyndromeIfValidTarget(args.target,args.syndrome,args.resetPolicy) -end - diff --git a/scripts/modtools/anonymous-script.lua b/scripts/modtools/anonymous-script.lua deleted file mode 100644 index 73eaefb92..000000000 --- a/scripts/modtools/anonymous-script.lua +++ /dev/null @@ -1,27 +0,0 @@ --- invoke simple lua scripts from strings ---author expwnent ---[[=begin - -modtools/anonymous-script -========================= -This allows running a short simple Lua script passed as an argument instead of -running a script from a file. This is useful when you want to do something too -complicated to make with the existing modtools, but too simple to be worth its -own script file. Example:: - - anonymous-script "print(args[1])" arg1 arg2 - # prints "arg1" - -=end]] -local args = {...} - ---automatically collect arguments to make the anonymous script more succinct ---load(ld,source,mode,env) -local f,err = load('local args = {...}; ' .. args[1], '=(anonymous lua script)') --,'=(lua command)', 't') -if err then - error(err) -end - ---we don't care about the result even if they return something for some reason: we just want to ensure its side-effects happen and print appropriate error messages -dfhack.safecall(f,table.unpack(args,2)) - diff --git a/scripts/modtools/create-item.lua b/scripts/modtools/create-item.lua deleted file mode 100644 index 0e4149222..000000000 --- a/scripts/modtools/create-item.lua +++ /dev/null @@ -1,140 +0,0 @@ --- creates an item of a given type and material ---author expwnent ---[[=begin - -modtools/create-item -==================== -Replaces the `createitem` plugin, with standard -arguments. The other versions will be phased out in a later version. - -=end]] -local utils = require 'utils' - -validArgs = --[[validArgs or--]] utils.invert({ - 'help', - 'creator', - 'material', - 'item', --- 'creature', --- 'caste', - 'leftHand', - 'rightHand', - 'quality' -}) - -organicTypes = organicTypes or utils.invert({ - df.item_type.REMAINS, - df.item_type.FISH, - df.item_type.FISH_RAW, - df.item_type.VERMIN, - df.item_type.PET, - df.item_type.EGG, -}) - -badTypes = badTypes or utils.invert({ - df.item_type.CORPSE, - df.item_type.CORPSEPIECE, - df.item_type.FOOD, -}) - -function createItem(creatorID, item, material, leftHand, rightHand, quality) - local itemQuality = quality and df.item_quality[quality] - print(itemQuality) - - local creator = df.unit.find(creatorID) - if not creator then - error 'Invalid creator.' - end - - if not item then - error 'Invalid item.' - end - local itemType = dfhack.items.findType(item) - if itemType == -1 then - error 'Invalid item.' - end - local itemSubtype = dfhack.items.findSubtype(item) - - if organicTypes[itemType] then - --TODO: look up creature and caste - error 'Not yet supported.' - end - - if badTypes[itemType] then - error 'Not supported.' - end - - if not material then - error 'Invalid material.' - end - local materialInfo = dfhack.matinfo.find(material) - if not materialInfo then - error 'Invalid material.' - end - - local item1 = dfhack.items.createItem(itemType, itemSubtype, materialInfo['type'], materialInfo.index, creator) - local item = df.item.find(item1) - if leftHand then - item:setGloveHandedness(2) - elseif rightHand then - item:setGloveHandedness(1) - end - - if itemQuality then - item:setQuality(itemQuality) - end - --[[if matchingGloves or matchingShoes then - if matchingGloves then - item1 = df.item.find(item1) - item1:setGloveHandedness(1); - end - local item2 = dfhack.items.createItem(itemType, itemSubtype, materialInfo['type'], materialInfo.index, creator) - if matchingGloves then - item2 = df.item.find(item2) - item2:setGloveHandedness(2); - end - end --]] - return item1 -end - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print( -[[scripts/modtools/create-item.lua -arguments: - -help - print this help message - -creator id - specify the id of the unit who will create the item, or \\LAST to indicate the unit with id df.global.unit_next_id-1 - examples: - 0 - 2 - \\LAST - -material matstring - specify the material of the item to be created - examples: - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -item itemstr - specify the itemdef of the item to be created - examples: - WEAPON:ITEM_WEAPON_PICK - -matchingShoes - create two of this item - -matchingGloves - create two of this item, and set handedness appropriately - ]]) - return -end - -if args.creator == '\\LAST' then - args.creator = tostring(df.global.unit_next_id-1) -end - -createItem(tonumber(args.creator), args.item, args.material, args.leftHand, args.rightHand, args.quality) diff --git a/scripts/modtools/create-unit.lua b/scripts/modtools/create-unit.lua deleted file mode 100644 index 9505ec739..000000000 --- a/scripts/modtools/create-unit.lua +++ /dev/null @@ -1,575 +0,0 @@ --- Creates a unit. Beta; use at own risk. --- Originally created by warmist, edited by Putnam for the dragon ball mod to be used in reactions, modified by Dirst for use in The Earth Strikes Back mod, incorporating fixes discovered by Boltgun then Mifiki wrote the bit where it switches to arena mode briefly to do some of the messy work, then Expwnent combined that with the old script to make it function for histfigs --- version 0.51 --- This is a beta version. Use at your own risk. - --- Modifications from 0.5: civ -1 creates are NOT historical figures, mitigated screen-movement bug in createUnit() - ---[[ - TODO - children and babies: set child/baby job - confirm body size is computed appropriately for different ages / life stages - incarnate pre-existing historical figures - some sort of invasion helper script - set invasion_id, etc - announcement for fake natural birth if appropriate -]] ---[[=begin - -modtools/create-unit -==================== -Creates a unit. Use ``modtools/create-unit -help`` for more info. - -=end]] - ---[[ -if dfhack.gui.getCurViewscreen()._type ~= df.viewscreen_dwarfmodest or df.global.ui.main.mode ~= df.ui_sidebar_mode.LookAround then - print 'activate loo[k] mode' - return -end ---]] - -local utils=require 'utils' - -function createUnit(race_id, caste_id) - local view_x = df.global.window_x - local view_y = df.global.window_y - local view_z = df.global.window_z - - local curViewscreen = dfhack.gui.getCurViewscreen() - local dwarfmodeScreen = df.viewscreen_dwarfmodest:new() - curViewscreen.child = dwarfmodeScreen - dwarfmodeScreen.parent = curViewscreen - local oldMode = df.global.ui.main.mode - df.global.ui.main.mode = df.ui_sidebar_mode.LookAround - - local gui = require 'gui' - - df.global.world.arena_spawn.race:resize(0) - df.global.world.arena_spawn.race:insert(0,race_id) --df.global.ui.race_id) - - df.global.world.arena_spawn.caste:resize(0) - df.global.world.arena_spawn.caste:insert(0,caste_id) - - df.global.world.arena_spawn.creature_cnt:resize(0) - df.global.world.arena_spawn.creature_cnt:insert(0,0) - - --df.global.world.arena_spawn.equipment.skills:insert(0,99) - --df.global.world.arena_spawn.equipment.skill_levels:insert(0,0) - - local old_gametype = df.global.gametype - df.global.gametype = df.game_type.DWARF_ARENA - - gui.simulateInput(dfhack.gui.getCurViewscreen(), 'D_LOOK_ARENA_CREATURE') - gui.simulateInput(dfhack.gui.getCurViewscreen(), 'SELECT') - - df.global.gametype = old_gametype - - curViewscreen.child = nil - dwarfmodeScreen:delete() - df.global.ui.main.mode = oldMode - - local id = df.global.unit_next_id-1 - - df.global.window_x = view_x - df.global.window_y = view_y - df.global.window_z = view_z - - return id -end - ---local u = df.unit.find(df.global.unit_next_id-1) ---u.civ_id = df.global.ui.civ_id ---u.population_id = df.historical_entity.find(df.global.ui.civ_id).populations[0] ---local group = df.global.ui.group_id - --- Picking a caste or gender at random -function getRandomCasteId(race_id) - local cr = df.creature_raw.find(race_id) - local caste_id, casteMax - - casteMax = #cr.caste - 1 - - if casteMax > 0 then - return math.random(0, casteMax) - end - - return 0 -end - -local function allocateNewChunk(hist_entity) - hist_entity.save_file_id=df.global.unit_chunk_next_id - df.global.unit_chunk_next_id=df.global.unit_chunk_next_id+1 - hist_entity.next_member_idx=0 - print("allocating chunk:",hist_entity.save_file_id) -end - -local function allocateIds(nemesis_record,hist_entity) - if hist_entity.next_member_idx==100 then - allocateNewChunk(hist_entity) - end - nemesis_record.save_file_id=hist_entity.save_file_id - nemesis_record.member_idx=hist_entity.next_member_idx - hist_entity.next_member_idx=hist_entity.next_member_idx+1 -end - -function createFigure(trgunit,he,he_group) - local hf=df.historical_figure:new() - hf.id=df.global.hist_figure_next_id - hf.race=trgunit.race - hf.caste=trgunit.caste - hf.profession = trgunit.profession - hf.sex = trgunit.sex - df.global.hist_figure_next_id=df.global.hist_figure_next_id+1 - hf.appeared_year = df.global.cur_year - - hf.born_year = trgunit.relations.birth_year - hf.born_seconds = trgunit.relations.birth_time - hf.curse_year = trgunit.relations.curse_year - hf.curse_seconds = trgunit.relations.curse_time - hf.birth_year_bias = trgunit.relations.birth_year_bias - hf.birth_time_bias = trgunit.relations.birth_time_bias - hf.old_year = trgunit.relations.old_year - hf.old_seconds = trgunit.relations.old_time - hf.died_year = -1 - hf.died_seconds = -1 - hf.name:assign(trgunit.name) - hf.civ_id = trgunit.civ_id - hf.population_id = trgunit.population_id - hf.breed_id = -1 - hf.unit_id = trgunit.id - - df.global.world.history.figures:insert("#",hf) - - hf.info = df.historical_figure_info:new() - hf.info.unk_14 = df.historical_figure_info.T_unk_14:new() -- hf state? - --unk_14.region_id = -1; unk_14.beast_id = -1; unk_14.unk_14 = 0 - hf.info.unk_14.unk_18 = -1; hf.info.unk_14.unk_1c = -1 - -- set values that seem related to state and do event - --change_state(hf, dfg.ui.site_id, region_pos) - - - --lets skip skills for now - --local skills = df.historical_figure_info.T_skills:new() -- skills snap shot - -- ... - -- note that innate skills are automaticaly set by DF - hf.info.skills = {new=true} - - - he.histfig_ids:insert('#', hf.id) - he.hist_figures:insert('#', hf) - if he_group then - he_group.histfig_ids:insert('#', hf.id) - he_group.hist_figures:insert('#', hf) - hf.entity_links:insert("#",{new=df.histfig_entity_link_memberst,entity_id=he_group.id,link_strength=100}) - end - trgunit.flags1.important_historical_figure = true - trgunit.flags2.important_historical_figure = true - trgunit.hist_figure_id = hf.id - trgunit.hist_figure_id2 = hf.id - - hf.entity_links:insert("#",{new=df.histfig_entity_link_memberst,entity_id=trgunit.civ_id,link_strength=100}) - - --add entity event - local hf_event_id=df.global.hist_event_next_id - df.global.hist_event_next_id=df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#",{new=df.history_event_add_hf_entity_linkst,year=trgunit.relations.birth_year, - seconds=trgunit.relations.birth_time,id=hf_event_id,civ=hf.civ_id,histfig=hf.id,link_type=0}) - return hf -end - -function createNemesis(trgunit,civ_id,group_id) - local id=df.global.nemesis_next_id - local nem=df.nemesis_record:new() - - nem.id=id - nem.unit_id=trgunit.id - nem.unit=trgunit - nem.flags:resize(4) - --not sure about these flags... - -- [[ - nem.flags[4]=true - nem.flags[5]=true - nem.flags[6]=true - nem.flags[7]=true - nem.flags[8]=true - nem.flags[9]=true - --]] - --[[for k=4,8 do - nem.flags[k]=true - end]] - nem.unk10=-1 - nem.unk11=-1 - nem.unk12=-1 - df.global.world.nemesis.all:insert("#",nem) - df.global.nemesis_next_id=id+1 - trgunit.general_refs:insert("#",{new=df.general_ref_is_nemesisst,nemesis_id=id}) - trgunit.flags1.important_historical_figure=true - - nem.save_file_id=-1 - - local he=df.historical_entity.find(civ_id) - he.nemesis_ids:insert("#",id) - he.nemesis:insert("#",nem) - local he_group - if group_id and group_id~=-1 then - he_group=df.historical_entity.find(group_id) - end - if he_group then - he_group.nemesis_ids:insert("#",id) - he_group.nemesis:insert("#",nem) - end - allocateIds(nem,he) - nem.figure=createFigure(trgunit,he,he_group) -end - ---createNemesis(u, u.civ_id,group) -function createUnitInCiv(race_id, caste_id, civ_id, group_id) - local uid = createUnit(race_id, caste_id) - local unit = df.unit.find(uid) - if ( civ_id ) then - createNemesis(unit, civ_id, group_id) - end - return uid -end - -function createUnitInFortCiv(race_id, caste_id) - return createUnitInCiv(race_id, caste_id, df.global.ui.civ_id) -end - -function createUnitInFortCivAndGroup(race_id, caste_id) - return createUnitInCiv(race_id, caste_id, df.global.ui.civ_id, df.global.ui.group_id) -end - -function domesticate(uid, group_id) - local u = df.unit.find(uid) - group_id = group_id or df.global.ui.group_id - -- If a friendly animal, make it domesticated. From Boltgun & Dirst - local caste=df.creature_raw.find(u.race).caste[u.caste] - if not(caste.flags.CAN_SPEAK and caste.flags.CAN_LEARN) then - -- Fix friendly animals (from Boltgun) - u.flags2.resident = false; - u.flags3.body_temp_in_range = true; - u.population_id = -1 - u.status.current_soul.unit_id = u.id - - u.animal.population.region_x = -1 - u.animal.population.region_y = -1 - u.animal.population.unk_28 = -1 - u.animal.population.population_idx = -1 - u.animal.population.depth = -1 - - u.counters.soldier_mood_countdown = -1 - u.counters.death_cause = -1 - - u.enemy.anon_4 = -1 - u.enemy.anon_5 = -1 - u.enemy.anon_6 = -1 - - -- And make them tame (from Dirst) - u.flags1.tame = true - u.training_level = 7 - end -end - -function wild(uid) - local u = df.unit.find(uid) - local caste=df.creature_raw.find(u.race).caste[u.caste] - -- x = df.global.world.world_data.active_site[0].pos.x - -- y = df.global.world.world_data.active_site[0].pos.y - -- region = df.global.map.map_blocks[df.global.map.x_count_block*x+y] - if not(caste.flags.CAN_SPEAK and caste.flags.CAN_LEARN) then - u.animal.population.region_x = 1 - u.animal.population.region_y = 1 - u.animal.population.unk_28 = -1 - u.animal.population.population_idx = 1 - u.animal.population.depth = 0 - end -end - - -function nameUnit(id, entityRawName, civ_id) - --pick a random appropriate name - --choose three random words in the appropriate things - local unit = df.unit.find(id) - local entity_raw - if entityRawName then - for k,v in ipairs(df.global.world.raws.entities) do - if v.code == entityRawName then - entity_raw = v - break - end - end - else - local entity = df.historical_entity.find(civ_id) - entity_raw = entity.entity_raw - end - - if not entity_raw then - error('entity raw = nil: ', id, entityRawName, civ_id) - end - - local translation = entity_raw.translation - local translationIndex - for k,v in ipairs(df.global.world.raws.language.translations) do - if v.name == translation then - translationIndex = k - break - end - end - --translation = df.language_translation.find(translation) - local language_word_table = entity_raw.symbols.symbols1[0] --educated guess - function randomWord() - local index = math.random(0, #language_word_table.words[0] - 1) - return index - end - local firstName = randomWord() - local lastName1 = randomWord() - local lastName2 = randomWord() - local name = unit.status.current_soul.name - name.words[0] = language_word_table.words[0][lastName1] - name.parts_of_speech[0] = language_word_table.parts[0][lastName1] - name.words[1] = language_word_table.words[0][lastName2] - name.parts_of_speech[1] = language_word_table.parts[0][lastName2] - name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]] - name.has_name = true - name.language = translationIndex - name = unit.name - name.words[0] = language_word_table.words[0][lastName1] - name.parts_of_speech[0] = language_word_table.parts[0][lastName1] - name.words[1] = language_word_table.words[0][lastName2] - name.parts_of_speech[1] = language_word_table.parts[0][lastName2] - name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]] - name.has_name = true - name.language = translationIndex - if unit.hist_figure_id ~= -1 then - local histfig = df.historical_figure.find(unit.hist_figure_id) - name = histfig.name - name.words[0] = language_word_table.words[0][lastName1] - name.parts_of_speech[0] = language_word_table.parts[0][lastName1] - name.words[1] = language_word_table.words[0][lastName2] - name.parts_of_speech[1] = language_word_table.parts[0][lastName2] - name.first_name = df.language_word.find(language_word_table.words[0][firstName]).forms[language_word_table.parts[0][firstName]] - name.has_name = true - name.language = translationIndex - end -end - -validArgs = --[[validArgs or]]utils.invert({ - 'help', - 'race', - 'caste', - 'domesticate', - 'civId', - 'groupId', - 'flagSet', - 'flagClear', - 'name', - 'nick', - 'location', - 'age' -}) - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) -if args.help then - print( -[[scripts/modtools/create-unit.lua -arguments: - -help - print this help message - -race raceName - specify the race of the unit to be created - examples: - DWARF - HUMAN - -caste casteName - specify the caste of the unit to be created - examples: - MALE - FEMALE - -domesticate - if the unit can't learn or can't speak, then make it a friendly animal - -civId id - make the created unit a member of the specified civ (or none if id = -1) - if id is \\LOCAL, then make it a member of the civ associated with the current fort - otherwise id must be an integer - -groupId id - make the created unit a member of the specified group (or none if id = -1) - if id is \\LOCAL, then make it a member of the group associated with the current fort - otherwise id must be an integer - -name entityRawName - set the unit's name to be a random name appropriate for the given entity - examples: - MOUNTAIN - -nick nickname - set the unit's nickname directly - -location [ x y z ] - create the unit at the specified coordinates - -age howOld - set the birth date of the unit to the specified number of years ago - -flagSet [ flag1 flag2 ... ] - set the specified unit flags in the new unit to true - flags may be selected from df.unit_flags1, df.unit_flags2, or df.unit_flags3 - -flagClear [ flag1 flag2 ... ] - set the specified unit flags in the new unit to false - flags may be selected from df.unit_flags1, df.unit_flags2, or df.unit_flags3 -]]) - return -end - -local race -local raceIndex -local casteIndex - -if not args.race or not args.caste then - error 'Specfiy a race and caste for the new unit.' -end - ---find race -for i,v in ipairs(df.global.world.raws.creatures.all) do - if v.creature_id == args.race then - raceIndex = i - race = v - break - end -end - -if not race then - error 'Invalid race.' -end - -for i,v in ipairs(race.caste) do - if v.caste_id == args.caste then - casteIndex = i - break - end -end - -if not casteIndex then - error 'Invalid caste.' -end - -local age -if args.age then - age = tonumber(args.age) - if not age and not age == 0 then - error('Invalid age: ' .. args.age) - end -end - -local civ_id -if args.civId == '\\LOCAL' then - civ_id = df.global.ui.civ_id -elseif args.civId and tonumber(args.civId) then - civ_id = tonumber(args.civId) -end - -local group_id -if args.groupId == '\\LOCAL' then - group_id = df.global.ui.group_id -elseif args.groupId and tonumber(args.groupId) then - group_id = tonumber(args.groupId) -end - -local unitId -if civ_id == -1 then - unitId = createUnit(raceIndex, casteIndex) - else - unitId = createUnitInCiv(raceIndex, casteIndex, civ_id, group_id) -end - -if args.domesticate then - domesticate(unitId, group_id) - else - wild(unitId) -end - -if age or age == 0 then - local u = df.unit.find(unitId) - local oldYearDelta = u.relations.old_year - u.relations.birth_year - u.relations.birth_year = df.global.cur_year - age - u.relations.old_year = u.relations.birth_year + oldYearDelta - --these flags are an educated guess of how to get the game to compute sizes correctly: use -flagSet and -flagClear arguments to override or supplement - u.flags2.calculated_nerves = false - u.flags2.calculated_bodyparts = false - u.flags3.body_part_relsize_computed = false - u.flags3.size_modifier_computed = false - u.flags3.compute_health = true - u.flags3.weight_computed = false - --TODO: if the unit is a child or baby it will still behave like an adult -end - -if args.flagSet or args.flagClear then - local u = df.unit.find(unitId) - local flagsToSet = {} - local flagsToClear = {} - for _,v in ipairs(args.flagSet or {}) do - flagsToSet[v] = true - end - for _,v in ipairs(args.flagClear or {}) do - flagsToClear[v] = true - end - for _,k in ipairs(df.unit_flags1) do - if flagsToSet[k] then - u.flags1[k] = true; - elseif flagsToClear[k] then - u.flags1[k] = false; - end - end - for _,k in ipairs(df.unit_flags2) do - if flagsToSet[k] then - u.flags2[k] = true; - elseif flagsToClear[k] then - u.flags2[k] = false; - end - end - for _,k in ipairs(df.unit_flags3) do - if flagsToSet[k] then - u.flags3[k] = true; - elseif flagsToClear[k] then - u.flags3[k] = false; - end - end -end - -if args.name then - nameUnit(unitId, args.name, civ_id) -else - local unit = df.unit.find(unitId) - unit.name.has_name = false - if unit.status.current_soul then - unit.status.current_soul.name.has_name = false - end - --[[if unit.hist_figure_id ~= -1 then - local histfig = df.historical_figure.find(unit.hist_figure_id) - histfig.name.has_name = false - end--]] -end - -if args.nick and type(args.nick) == 'string' then - dfhack.units.setNickname(df.unit.find(unitId), args.nick) -end - -if civ_id then - local u = df.unit.find(unitId) - u.civ_id = civ_id -end - -if args.location then - local u = df.unit.find(unitId) - local pos = df.coord:new() - pos.x = tonumber(args.location[1]) - pos.y = tonumber(args.location[2]) - pos.z = tonumber(args.location[3]) - local teleport = dfhack.script_environment('teleport') - teleport.teleport(u, pos) -end - ---[[if group_id then - local u = df.unit.find(unitId) - u.group_id = group_id -end--]] diff --git a/scripts/modtools/equip-item.lua b/scripts/modtools/equip-item.lua deleted file mode 100644 index bf17fc127..000000000 --- a/scripts/modtools/equip-item.lua +++ /dev/null @@ -1,105 +0,0 @@ --- equip an item on a unit with a particular body part ---[[=begin - -modtools/equip-item -=================== -Force a unit to equip an item with a particular body part; useful in -conjunction with the ``create`` scripts above. See also `forceequip`. - -=end]] -local utils = require 'utils' - -function equipItem(unit, item, bodyPart, mode) - --it is assumed that the item is on the ground - item.flags.on_ground = false - item.flags.in_inventory = true - local block = dfhack.maps.getTileBlock(item.pos) - local occupancy = block.occupancy[item.pos.x%16][item.pos.y%16] - for k,v in ipairs(block.items) do - --local blockItem = df.item.find(v) - if v == item.id then - block.items:erase(k) - break - end - end - local foundItem = false - for k,v in ipairs(block.items) do - local blockItem = df.item.find(v) - if blockItem.pos.x == item.pos.x and blockItem.pos.y == item.pos.y then - foundItem = true - break - end - end - if not foundItem then - occupancy.item = false - end - - local inventoryItem = df.unit_inventory_item:new() - inventoryItem.item = item - inventoryItem.mode = mode - inventoryItem.body_part_id = bodyPart - unit.inventory:insert(#unit.inventory,inventoryItem) - -end - -validArgs = --[[validArgs or--]] utils.invert({ - 'help', - 'unit', - 'item', - 'bodyPart', - 'mode' -}) - -if moduleMode then - return -end - - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print( -[[scripts/modtools/equip-item.lua -arguments: - -help - print this help message - ]]) - return -end - -local unitId = tonumber(args.unit) or ((args.unit == '\\LAST') and (df.global.unit_next_id-1)) -local unit = df.unit.find(unitId) -if not unit then - error('invalid unit!', args.unit) -end - -local itemId = tonumber(args.item) or ((args.item == '\\LAST') and (df.global.item_next_id-1)) -local item = df.item.find(itemId) -if not item then - error('invalid item!', args.item) -end - -local bodyPartName = args.bodyPart -local creature_raw = df.global.world.raws.creatures.all[unit.race] -local caste_raw = creature_raw.caste[unit.caste] -local body_info = caste_raw.body_info - -local partId -local part -for k,v in ipairs(body_info.body_parts) do - if v.token == bodyPartName then - partId = k - part = v - break - end -end - -if not part then - error('invalid body part name: ', bodyPartName) -end - -local mode = args.mode -mode = df.unit_inventory_item.T_mode[mode] - -equipItem(unit, item, partId, mode) - diff --git a/scripts/modtools/extra-gamelog.lua b/scripts/modtools/extra-gamelog.lua deleted file mode 100644 index 0046ceb26..000000000 --- a/scripts/modtools/extra-gamelog.lua +++ /dev/null @@ -1,192 +0,0 @@ --- Regularly writes extra info to gamelog.txt -local help = [[=begin - -modtools/extra-gamelog -====================== -This script writes extra information to the gamelog. -This is useful for tools like :forums:`Soundsense <106497>`. - -=end]] - -msg = dfhack.gui.writeToGamelog - -function log_on_load(op) - if op ~= SC_WORLD_LOADED then return end - - -- Seasons fix for Soundsense - local seasons = { - [-1] = 'Nothing', -- worldgen - [0] = 'Spring', - [1] = 'Summer', - [2] = 'Autumn', - [3] = 'Winter'} - msg(seasons[df.global.cur_season]..' has arrived on the calendar.') - - -- Weather fix for Soundsense - local raining = false - local snowing = false - for _, row in ipairs(df.global.current_weather) do - for _, weather in ipairs(row) do - raining = raining or weather == 1 - snowing = snowing or weather == 2 - end - end - if (not snowing and not raining) then msg("The weather has cleared.") - elseif raining then msg("It has started raining.") - elseif snowing then msg("A snow storm has come.") - end - - -- Log site information for forts - if df.world_site.find(df.global.ui.site_id) == nil then return end - local site = df.world_site.find(df.global.ui.site_id) - local fort_ent = df.global.ui.main.fortress_entity - local civ_ent = df.historical_entity.find(df.global.ui.civ_id) - local function fullname(item) - return dfhack.TranslateName(item.name)..' ('..dfhack.TranslateName(item.name ,true)..')' - end - msg('Loaded '..df.global.world.cur_savegame.save_dir..', '..fullname(df.global.world.world_data).. - ' at coordinates ('..site.pos.x..','..site.pos.y..')') - msg('Loaded the fortress '..fullname(site).. - (fort_ent and ', colonized by the group '..fullname(fort_ent) or '').. - (civ_ent and ' of the civilization '..fullname(civ_ent)..'.' or '.')) -end - - -old_expedition_leader = nil -old_mayor = nil -function log_nobles() - local expedition_leader = nil - local mayor = nil - local function check(unit) - if not dfhack.units.isCitizen(unit) then return end - for _, pos in ipairs(dfhack.units.getNoblePositions(unit) or {}) do - if pos.position.name[0] == "expedition leader" then - expedition_leader = unit - elseif pos.position.name[0] == "mayor" then - mayor = unit - end - end - end - for _, unit in ipairs(df.global.world.units.active) do - check(unit) - end - - if old_mayor == nil and expedition_leader == nil and mayor ~= nil and old_expedition_leader ~= nil then - msg("Expedition leader was replaced by mayor.") - end - - if expedition_leader ~= old_expedition_leader then - if expedition_leader == nil then - msg("Expedition leader position is now vacant.") - else - msg(dfhack.TranslateName(dfhack.units.getVisibleName(expedition_leader)).." became expedition leader.") - end - end - - if mayor ~= old_mayor then - if mayor == nil then - msg("Mayor position is now vacant.") - else - msg(dfhack.TranslateName(dfhack.units.getVisibleName(mayor)).." became mayor.") - end - end - old_mayor = mayor - old_expedition_leader = expedition_leader -end - -siege = false -function log_siege() - local function cur_siege() - for _, unit in ipairs(df.global.world.units.active) do - if unit.flags1.active_invader then return true end - end - return false - end - local old_siege = siege - siege = cur_siege() - if siege ~= old_siege and siege then - msg("A vile force of darkness has arrived!") - elseif siege ~= old_siege and not siege then - msg("Siege was broken.") - end -end - - -local workshopTypes = { - [0]="Carpenters Workshop", - "Farmers Workshop", - "Masons Workshop", - "Craftsdwarfs Workshop", - "Jewelers Workshop", - "Metalsmiths Forge", - "Magma Forge", - "Bowyers Workshop", - "Mechanics Workshop", - "Siege Workshop", - "Butchers Workshop", - "Leatherworks Workshop", - "Tanners Workshop", - "Clothiers Workshop", - "Fishery", - "Still", - "Loom", - "Quern", - "Kennels", - "Kitchen", - "Ashery", - "Dyers Workshop", - "Millstone", - "Custom", - "Tool", - } - -local furnaceTypes = { - [0]="Wood Furnace", - "Smelter", - "Glass Furnace", - "Kiln", - "Magma Smelter", - "Magma Glass Furnace", - "Magma Kiln", - "Custom Furnace", - } - -buildStates = {} - -function log_buildings() - for _, building in ipairs(df.global.world.buildings.all) do - if getmetatable(building) == "building_workshopst" or getmetatable(building) == "building_furnacest" then - buildStates[building.id] = buildStates[building.id] or building.flags.exists - if buildStates[building.id] ~= building.flags.exists then - buildStates[building.id] = building.flags.exists - if building.flags.exists then - if getmetatable(building) == "building_workshopst" then - msg(workshopTypes[building.type].." was built.") - elseif getmetatable(building) == "building_furnacest" then - msg(furnaceTypes[building.type].." was built.") - end - end - end - end - end -end - -local function event_loop() - log_nobles() - log_siege() - log_buildings() - if extra_gamelog_enabled then dfhack.timeout(50, 'ticks', event_loop) end -end - -extra_gamelog_enabled = false -local args = {...} -if args[1] == 'disable' then - dfhack.onStateChange[_ENV] = nil - extra_gamelog_enabled = false -elseif args[1] == 'enable' then - dfhack.onStateChange[_ENV] = log_on_load - extra_gamelog_enabled = true - event_loop() -else - print(help) -end diff --git a/scripts/modtools/force.lua b/scripts/modtools/force.lua deleted file mode 100644 index 3b25b0895..000000000 --- a/scripts/modtools/force.lua +++ /dev/null @@ -1,90 +0,0 @@ --- Forces an event (caravan, migrants, etc) --- author Putnam --- edited by expwnent ---[[=begin - -modtools/force -============== -This tool triggers events like megabeasts, caravans, and migrants. - -=end]] -local utils = require 'utils' - -local function findCiv(arg) - local entities = df.global.world.entities.all - if tonumber(arg) then return arg end - if arg then - for eid,entity in ipairs(entities) do - if entity.entity_raw.code == arg then return entity end - end - end - return nil -end - -validArgs = validArgs or utils.invert({ - 'eventType', - 'help', - 'civ' -}) - -local args = utils.processArgs({...}, validArgs) -if next(args) == nil or args.help then - print([[force usage -arguments: - -help - print this help message - -eventType event - specify the type of the event to trigger - examples: - MegaBeast - Migrants - Caravan - Diplomat - WildlifeCurious - WildlifeMischievous - WildlifeFlier - NightCreature - -civ entity - specify the civ of the event, if applicable - examples: - player - MOUNTAIN - EVIL - 28 -]]) - print('force: -eventType [Megabeast, Migrants, Caravan, Diplomat, WildlifeCurious, WildlifeMischievous, WildlifeFlier, NightCreature] -civ [player,ENTITY_ID]') - return -end - -if not args.eventType then - error 'Specify an eventType.' -elseif not df.timed_event_type[args.eventType] then - error('Invalid eventType: ' .. args.eventType) -end - -if args.civ == 'player' then - args.civ = df.historical_entity.find(df.global.ui.civ_id) -elseif args.civ then - local civ = args.civ - args.civ = findCiv(args.civ) - if not args.civ then - error('Invalid civ: ' .. civ) - end -elseif args.eventType == 'Caravan' or args.eventType == 'Diplomat' then - error('Specify civ for this eventType') -end - -if args.eventType == 'Migrants' then - args.civ = df.historical_entity.find(df.global.ui.civ_id) -end - -local timedEvent = df.timed_event:new() -timedEvent['type'] = df.timed_event_type[args.eventType] -timedEvent.season = df.global.cur_season -timedEvent.season_ticks = df.global.cur_season_tick -if args.civ then - timedEvent.entity = args.civ -end - -df.global.timed_events:insert('#', timedEvent) - diff --git a/scripts/modtools/interaction-trigger.lua b/scripts/modtools/interaction-trigger.lua deleted file mode 100644 index e611a27fe..000000000 --- a/scripts/modtools/interaction-trigger.lua +++ /dev/null @@ -1,176 +0,0 @@ --- triggers scripts based on unit interactions ---author expwnent ---[[=begin - -modtools/interaction-trigger -============================ -This triggers events when a unit uses an interaction on another. It works by -scanning the announcements for the correct attack verb, so the attack verb -must be specified in the interaction. It includes an option to suppress this -announcement after it finds it. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -attackTriggers = attackTriggers or {} -defendTriggers = defendTriggers or {} -commands = commands or {} -commandCount = commandCount or 0 - -eventful.enableEvent(eventful.eventType.INTERACTION,1) --cheap, so every tick is fine -eventful.enableEvent(eventful.eventType.UNLOAD,1) - -eventful.onUnload.interactionTrigger = function() - attackTriggers = {} - defendTriggers = {} - commands = {} - commandCount = 0 -end - -local function processTrigger(args) - local command = {} - for _,arg in ipairs(args.command) do - if arg == '\\ATTACK_VERB' then - table.insert(command,args.attackVerb) - elseif arg == '\\DEFEND_VERB' then - table.insert(command,args.defendVerb) - elseif arg == '\\ATTACKER_ID' then - table.insert(command,args.attackerId) - elseif arg == '\\DEFENDER_ID' then - table.insert(command,args.defenderId) - elseif arg == '\\ATTACK_REPORT' then - table.insert(command,args.attackReport) - elseif arg == '\\DEFEND_REPORT' then - table.insert(command,args.defendReport) - elseif string.sub(arg,1,1) == '\\' then - table.insert(command,string.sub(arg,2)) - else - table.insert(command,arg) - end - end - dfhack.run_command(table.unpack(command)) -end - -eventful.onInteraction.interactionTrigger = function(attackVerb, defendVerb, attacker, defender, attackReport, defendReport) - local extras = {} - extras.attackVerb = attackVerb - extras.defendVerb = defendVerb - extras.attackReport = attackReport - extras.defendReport = defendReport - extras.attackerId = attacker - extras.defenderId = defender - local suppressAttack = false - local suppressDefend = false - local todo = {} - for _,trigger in ipairs(attackTriggers[attackVerb] or {}) do - todo[trigger] = true - end - for _,trigger in ipairs(defendTriggers[defendVerb] or {}) do - todo[trigger] = true - end - for k,v in pairs(todo) do - command = commands[k] - suppressAttack = suppressAttack or command.suppressAttack - suppressDefend = suppressDefend or command.suppressDefend - utils.fillTable(command,extras) - processTrigger(command) - utils.unfillTable(command,extras) - end - - local eraseReport = function(unit,report) - for i,v in ipairs(unit.reports.log.Combat) do - if v == report then - unit.reports.log.Combat:erase(i) - break - end - end - end - if suppressAttack or suppressDefend then - attacker = df.unit.find(tonumber(attacker)) - defender = df.unit.find(tonumber(defender)) - end - if suppressAttack then - eraseReport(attacker,attackReport) - eraseReport(defender,attackReport) - end - if suppressDefend then - eraseReport(attacker,defendReport) - eraseReport(defender,defendReport) - end - --TODO: get rid of combat report on LHS of screen -end - ----------------------------------------------------- ---argument processing - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'onAttackStr', - 'onDefendStr', - 'command', - 'suppressAttack', - 'suppressDefend', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/interaction-trigger.lua -arguments: - -help - print this help message - -clear - unregisters all triggers - -onAttackStr str - trigger the command when the attack verb is "str". both onAttackStr and onDefendStr MUST be specified - -onDefendStr str - trigger the command when the defend verb is "str". both onAttackStr and onDefendStr MUST be specified - -suppressAttack - delete the attack announcement from the combat logs - -suppressDefend - delete the defend announcement from the combat logs - -command [ commandStrs ] - specify the command to be executed - commandStrs - \\ATTACK_VERB - \\DEFEND_VERB - \\ATTACKER_ID - \\DEFENDER_ID - \\ATTACK_REPORT - \\DEFEND_REPORT - \\anything -> \anything - anything -> anything -You must specify both an attack string and a defend string to guarantee correct performance. Either will trigger the script when it happens, but it will not be triggered twice in a row if both happen. -]]) - return -end - -if args.clear then - triggers = {} - commands = {} - commandCount = 0 -end - -if not args.command then - return -end - -commands[commandCount] = args - -if args.onAttackStr then - if not attackTriggers[args.onAttackStr] then - attackTriggers[args.onAttackStr] = {} - end - table.insert(attackTriggers[args.onAttackStr],commandCount) -end - -if args.onDefendStr then - if not defendTriggers[args.onDefendStr] then - defendTriggers[args.onDefendStr] = {} - end - table.insert(defendTriggers[args.onDefendStr],commandCount) -end - -commandCount = commandCount+1 diff --git a/scripts/modtools/invader-item-destroyer.lua b/scripts/modtools/invader-item-destroyer.lua deleted file mode 100644 index 9b06c944e..000000000 --- a/scripts/modtools/invader-item-destroyer.lua +++ /dev/null @@ -1,184 +0,0 @@ --- delete invader items when they die ---author expwnent ---[[=begin - -modtools/invader-item-destroyer -=============================== -This tool configurably destroys invader items to prevent clutter or to prevent -the player from getting tools exclusive to certain races. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - ---invaders = invaders or {} -entities = entities or {} -items = items or {} - -allEntities = allEntities or false -allItems = allitems or true - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.invaderItemDestroyer = function() - entities = {} - items = {} - allEntities = false - allItems = true -end - -eventful.enableEvent(eventful.eventType.UNIT_DEATH, 1) --requires iterating through all units -eventful.onUnitDeath.invaderItemDestroyer = function(unitId) - local unit = df.unit.find(unitId) - if not unit then - return - end - - local entity = df.historical_entity.find(unit.civ_id) - if not allEntities and not entity then - return - end - - if not allEntities and not entities[entity.entity_raw.code] then - return - end - - if dfhack.units.isCitizen(unit) then - return - end - - local function forEach(item) - if not allItems and not items[dfhack.items.getSubtypeDef(item:getType(),item:getSubtype()).id] then - return - end - if not (item.flags.foreign and item.flags.forbid) then - return - end - if item.pos.x ~= unit.pos.x then - return - end - if item.pos.y ~= unit.pos.y then - return - end - if item.pos.z ~= unit.pos.z then - return - end - item.flags.garbage_collect = true - item.flags.forbid = true - item.flags.hidden = true - end - - for _,item in ipairs(unit.inventory) do - local item2 = df.item.find(item.item) - forEach(item2) - end - --for each item on the ground - local block = dfhack.maps.getTileBlock(unit.pos.x, unit.pos.y, unit.pos.z) - for _,item in ipairs(block.items) do - local item2 = df.item.find(item) - forEach(item2) - end -end ---[[eventful.onUnitDeath.invaderItemDestroyer = function(unit) - if invaders[unit] then - print ('Invader ' .. unit .. ' dies.') - end - for _,item in ipairs(invaders[unit] or {}) do - local item2 = df.item.find(item) - if item2 then - print ('deleting item ' .. item) - item2.flags.garbage_collect = true - item2.flags.forbid = true - item2.flags.hidden = true - item2.flags.encased = true - end - end - invaders[unit] = nil - --TODO: delete corpses? -end]] - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'allRaces', - 'allEntities', - 'allItems', - 'item', - 'entity', - 'race', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.clear then - entities = {} - items = {} - allEntities = false - allItems = true -end - -if args.help then - print([[scripts/modtools/invader-item-destroyer.lua usage -arguments: - -help - print this help message - -clear - reset all registered data - -allEntities [true/false] - set whether it should delete items from invaders from any civ - -allItems [true/false] - set whether it should delete all invader items regardless of type when an appropriate invader dies - -item itemdef - set a particular itemdef to be destroyed when an invader from an appropriate civ dies - examples: - ITEM_WEAPON_PICK - -entity entityName - set a particular entity up so that its invaders destroy their items shortly after death - examples: - MOUNTAIN - EVIL -]]) - return -end - -if args.allEntities then - if args.allEntities == 'true' then - allEntities = true - else - allEntities = false - end -end -if args.allItems then - if args.allItems == 'true' then - allItems = true - else - allItems = false - end -end - -if args.item then - local itemType - for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do - if itemdef.id == args.item then - itemType = itemdef.id - break - end - end - if not itemType then - error ('Invalid item type: ' .. args.item) - end - items[itemType] = true -end -if args.entity then - local success - for _,entity in ipairs(df.global.world.entities.all) do - if entity.entity_raw.code == args.entity then - success = true - break - end - end - if not success then - error 'Invalid entity' - end - entities[args.entity] = true -end - diff --git a/scripts/modtools/item-trigger.lua b/scripts/modtools/item-trigger.lua deleted file mode 100644 index ee44fd447..000000000 --- a/scripts/modtools/item-trigger.lua +++ /dev/null @@ -1,294 +0,0 @@ --- trigger commands based on attacks with certain items ---author expwnent ---based on itemsyndrome by Putnam ---triggers scripts when a unit attacks another with a weapon type, a weapon of a particular material, or a weapon contaminated with a particular material, or when a unit equips/unequips a particular item type, an item of a particular material, or an item contaminated with a particular material ---[[=begin - -modtools/item-trigger -===================== -This powerful tool triggers DFHack commands when a unit equips, unequips, or -attacks another unit with specified item types, specified item materials, or -specified item contaminants. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -itemTriggers = itemTriggers or {} -materialTriggers = materialTriggers or {} -contaminantTriggers = contaminantTriggers or {} - -eventful.enableEvent(eventful.eventType.UNIT_ATTACK,1) -- this event type is cheap, so checking every tick is fine -eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,5) --this is expensive, but you might still want to set it lower -eventful.enableEvent(eventful.eventType.UNLOAD,1) - -eventful.onUnload.itemTrigger = function() - itemTriggers = {} - materialTriggers = {} - contaminantTriggers = {} -end - -function processTrigger(command) - local command2 = {} - for i,arg in ipairs(command.command) do - if arg == '\\ATTACKER_ID' then - command2[i] = '' .. command.attacker.id - elseif arg == '\\DEFENDER_ID' then - command2[i] = '' .. command.defender.id - elseif arg == '\\ITEM_MATERIAL' then - command2[i] = command.itemMat:getToken() - elseif arg == '\\ITEM_MATERIAL_TYPE' then - command2[i] = command.itemMat['type'] - elseif arg == '\\ITEM_MATERIAL_INDEX' then - command2[i] = command.itemMat.index - elseif arg == '\\ITEM_ID' then - command2[i] = '' .. command.item.id - elseif arg == '\\ITEM_TYPE' then - command2[i] = command.itemType - elseif arg == '\\CONTAMINANT_MATERIAL' then - command2[i] = command.contaminantMat:getToken() - elseif arg == '\\CONTAMINANT_MATERIAL_TYPE' then - command2[i] = command.contaminantMat['type'] - elseif arg == '\\CONTAMINANT_MATERIAL_INDEX' then - command2[i] = command.contaminantMat.index - elseif arg == '\\MODE' then - command2[i] = command.mode - elseif arg == '\\UNIT_ID' then - command2[i] = command.unit.id - elseif string.sub(arg,1,1) == '\\' then - command2[i] = string.sub(arg,2) - else - command2[i] = arg - end - end - dfhack.run_command(table.unpack(command2)) -end - -function getitemType(item) - if item:getSubtype() ~= -1 then - itemType = dfhack.items.getSubtypeDef(item:getType(),item:getSubtype()).id - else - itemType = df.item_type[item:getType()] - end - return itemType -end - -function handler(table) - local itemMat = dfhack.matinfo.decode(table.item) - local itemMatStr = itemMat:getToken() - local itemType = getitemType(table.item) - table.itemMat = itemMat - table.itemType = itemType - - for _,command in ipairs(itemTriggers[itemType] or {}) do - if command[table.mode] then - utils.fillTable(command,table) - processTrigger(command) - utils.unfillTable(command,table) - end - end - - for _,command in ipairs(materialTriggers[itemMatStr] or {}) do - if command[table.mode] then - utils.fillTable(command,table) - processTrigger(command) - utils.unfillTable(command,table) - end - end - - for _,contaminant in ipairs(table.item.contaminants or {}) do - local contaminantMat = dfhack.matinfo.decode(contaminant.mat_type, contaminant.mat_index) - local contaminantStr = contaminantMat:getToken() - table.contaminantMat = contaminantMat - for _,command in ipairs(contaminantTriggers[contaminantStr] or {}) do - utils.fillTable(command,table) - processTrigger(command) - utils.unfillTable(command,table) - end - table.contaminantMat = nil - end -end - -function equipHandler(unit, item, isEquip) - local mode = (isEquip and 'onEquip') or (not isEquip and 'onUnequip') - - local table = {} - table.mode = mode - table.item = df.item.find(item) - table.unit = df.unit.find(unit) - handler(table) -end - -eventful.onInventoryChange.equipmentTrigger = function(unit, item, item_old, item_new) - if item_old and item_new then - return - end - - local isEquip = item_new and not item_old - equipHandler(unit,item,isEquip) -end - -eventful.onUnitAttack.attackTrigger = function(attacker,defender,wound) - attacker = df.unit.find(attacker) - defender = df.unit.find(defender) - - if not attacker then - return - end - - local attackerWeapon - for _,item in ipairs(attacker.inventory) do - if item.mode == df.unit_inventory_item.T_mode.Weapon then - attackerWeapon = item.item - break - end - end - - if not attackerWeapon then - return - end - - local table = {} - table.attacker = attacker - table.defender = defender - table.item = attackerWeapon - table.mode = 'onStrike' - handler(table) -end - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'checkAttackEvery', - 'checkInventoryEvery', - 'command', - 'itemType', - 'onStrike', - 'onEquip', - 'onUnequip', - 'material', - 'contaminant', -}) -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/item-trigger.lua usage -arguments: - -help - print this help message - -clear - clear all registered triggers - -checkAttackEvery n - check the attack event at least every n ticks - -checkInventoryEvery n - check inventory event at least every n ticks - -itemType type - trigger the command for items of this type - examples: - ITEM_WEAPON_PICK - RING - -onStrike - trigger the command when someone strikes someone with an appropriate weapon - -onEquip - trigger the command when someone equips an appropriate item - -onUnequip - trigger the command when someone unequips an appropriate item - -material mat - trigger the commmand on items with the given material - examples - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -contaminant mat - trigger the command on items with a given material contaminant - examples - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -command [ commandStrs ] - specify the command to be executed - commandStrs - \\ATTACKER_ID - \\DEFENDER_ID - \\ITEM_MATERIAL - \\ITEM_MATERIAL_TYPE - \\ITEM_ID - \\ITEM_TYPE - \\CONTAMINANT_MATERIAL - \\CONTAMINANT_MATERIAL_TYPE - \\CONTAMINANT_MATERIAL_INDEX - \\MODE - \\UNIT_ID - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - itemTriggers = {} - materialTriggers = {} - contaminantTriggers = {} -end - -if args.checkAttackEvery then - if not tonumber(args.checkAttackEvery) then - error('checkAttackEvery must be a number') - end - eventful.enableEvent(eventful.eventType.UNIT_ATTACK,tonumber(args.checkAttackEvery)) -end - -if args.checkInventoryEvery then - if not tonumber(args.checkInventoryEvery) then - error('checkInventoryEvery must be a number') - end - eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,tonumber(args.checkInventoryEvery)) -end - -if not args.command then - if not args.clear then - error 'specify a command' - end - return -end - -if args.itemType then - if dfhack.items.findType(args.itemType) == -1 then - local temp - for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do - if itemdef.id == args.itemType then - temp = args.itemType --itemdef.subtype - break - end - end - if not temp then - error 'Could not find item type.' - end - args.itemType = temp - end -end - -local numConditions = (args.material and 1 or 0) + (args.itemType and 1 or 0) + (args.contaminant and 1 or 0) -if numConditions > 1 then - error 'too many conditions defined: not (yet) supported (pester expwnent if you want it)' -elseif numConditions == 0 then - error 'specify a material, weaponType, or contaminant' -end - -if args.material then - if not materialTriggers[args.material] then - materialTriggers[args.material] = {} - end - table.insert(materialTriggers[args.material],args) -elseif args.itemType then - if not itemTriggers[args.itemType] then - itemTriggers[args.itemType] = {} - end - table.insert(itemTriggers[args.itemType],args) -elseif args.contaminant then - if not contaminantTriggers[args.contaminant] then - contaminantTriggers[args.contaminant] = {} - end - table.insert(contaminantTriggers[args.contaminant],args) -end - diff --git a/scripts/modtools/moddable-gods.lua b/scripts/modtools/moddable-gods.lua deleted file mode 100644 index 9445af7a9..000000000 --- a/scripts/modtools/moddable-gods.lua +++ /dev/null @@ -1,102 +0,0 @@ --- Create gods from the command-line ---based on moddableGods by Putnam ---edited by expwnent ---[[=begin - -modtools/moddable-gods -====================== -This is a standardized version of Putnam's moddableGods script. It allows you -to create gods on the command-line. - -=end]] -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'name', - 'spheres', - 'gender', - 'depictedAs', - 'domain', - 'description', --- 'entities', -}) -local args = utils.processArgs({...}) - -if args.help then - print([[scripts/modtools/moddable-gods.lua -arguments: - -help - print this help message - -name godName - sets the name of the god to godName - if there's already a god of that name, the script halts - -spheres [ sphereList ] - define a space-separated list of spheres of influence of the god - -depictedAs str - often depicted as a str - -domain str - set the domain of the god - -description str - set the description of the god -]]) - return -end - -if not args.name or not args.depictedAs or not args.domain or not args.description or not args.spheres or not args.gender then - error('All arguments must be specified.') -end - -local templateGod -for _,fig in ipairs(df.global.world.history.figures) do - if fig.flags.deity then - templateGod = fig - break - end -end -if not templateGod then - error 'Could not find template god.' -end - -if args.gender == 'male' then - args.gender = 1 -elseif args.gender == 'female' then - args.gender = 0 -else - error 'invalid gender' -end - -for _,fig in ipairs(df.global.world.history.figures) do - if fig.name.first_name == args.name then - print('god ' .. args.name .. ' already exists. Skipping') - return - end -end - -local godFig = df.historical_figure:new() -godFig.appeared_year = -1 -godFig.born_year = -1 -godFig.born_seconds = -1 -godFig.curse_year = -1 -godFig.curse_seconds = -1 -godFig.old_year = -1 -godFig.old_seconds = -1 -godFig.died_year = -1 -godFig.died_seconds = -1 -godFig.name.has_name = true -godFig.breed_id = -1 -godFig.flags:assign(templateGod.flags) -godFig.id = df.global.hist_figure_next_id -df.global.hist_figure_next_id = 1+df.global.hist_figure_next_id -godFig.info = df.historical_figure_info:new() -godFig.info.spheres = {new=true} -godFig.info.secret = df.historical_figure_info.T_secret:new() - -godFig.sex = args.gender -godFig.name.first_name = args.name -for _,sphere in ipairs(args.spheres) do - godFig.info.spheres:insert('#',df.sphere_type[sphere]) -end -df.global.world.history.figures:insert('#',godFig) - - diff --git a/scripts/modtools/outside-only.lua b/scripts/modtools/outside-only.lua deleted file mode 100644 index f4ed1414f..000000000 --- a/scripts/modtools/outside-only.lua +++ /dev/null @@ -1,141 +0,0 @@ --- enables outside only and inside only buildings ---author expwnent ---[[=begin - -modtools/outside-only -===================== -This allows you to specify certain custom buildings as outside only, or inside -only. If the player attempts to build a building in an inappropriate location, -the building will be destroyed. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -buildingType = buildingType or utils.invert({'EITHER','OUTSIDE_ONLY','INSIDE_ONLY'}) -registeredBuildings = registeredBuildings or {} -checkEvery = checkEvery or 100 -timeoutId = timeoutId or nil - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.outsideOnly = function() - registeredBuildings = {} - checkEvery = 100 - timeoutId = nil -end - -local function destroy(building) - if #building.jobs > 0 and building.jobs[0] and building.jobs[0].job_type == df.job_type.DestroyBuilding then - return - end - local b = dfhack.buildings.deconstruct(building) - if b then - --TODO: print an error message to the user so they know - return - end --- building.flags.almost_deleted = 1 -end - -local function checkBuildings() - local toDestroy = {} - local function forEach(building) - if building:getCustomType() < 0 then - --TODO: support builtin building types if someone wants - return - end - local pos = df.coord:new() - pos.x = building.centerx - pos.y = building.centery - pos.z = building.z - local outside = dfhack.maps.getTileBlock(pos).designation[pos.x%16][pos.y%16].outside - local def = df.global.world.raws.buildings.all[building:getCustomType()] - local btype = registeredBuildings[def.code] - if btype then --- print('outside: ' .. outside==true .. ', type: ' .. btype) - end - - if not btype or btype == buildingType.EITHER then - registeredBuildings[def.code] = nil - return - elseif btype == buildingType.OUTSIDE_ONLY then - if outside then - return - end - else - if not outside then - return - end - end - table.insert(toDestroy,building) - end - for _,building in ipairs(df.global.world.buildings.all) do - forEach(building) - end - for _,building in ipairs(toDestroy) do - destroy(building) - end - if timeoutId then - dfhack.timeout_active(timeoutId,nil) - timeoutId = nil - end - timeoutId = dfhack.timeout(checkEvery, 'ticks', checkBuildings) -end - -eventful.enableEvent(eventful.eventType.BUILDING, 100) -eventful.onBuildingCreatedDestroyed.outsideOnly = function(buildingId) - checkBuildings() -end - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'checkEvery', - 'building', - 'type' -}) -local args = utils.processArgs({...}, validArgs) -if args.help then - print([[scripts/modtools/outside-only -arguments - -help - print this help message - -clear - clears the list of registered buildings - -checkEvery n - set how often existing buildings are checked for whether they are in the appropriate location to n ticks - -type [EITHER, OUTSIDE_ONLY, INSIDE_ONLY] - specify what sort of restriction to put on the building - -building name - specify the id of the building -]]) - return -end - -if args.clear then - registeredBuildings = {} -end - -if args.checkEvery then - if not tonumber(args.checkEvery) then - error('Invalid checkEvery.') - end - checkEvery = tonumber(args.checkEvery) -end - -if not args.building then - return -end - -if not args['type'] then - print 'outside-only: please specify type' - return -end - -if not buildingType[args['type']] then - error('Invalid building type: ' .. args['type']) -end - -registeredBuildings[args.building] = buildingType[args['type']] - -checkBuildings() - diff --git a/scripts/modtools/projectile-trigger.lua b/scripts/modtools/projectile-trigger.lua deleted file mode 100644 index 8e6227e0a..000000000 --- a/scripts/modtools/projectile-trigger.lua +++ /dev/null @@ -1,108 +0,0 @@ --- trigger commands when projectiles hit targets ---author expwnent ---based on Putnam's projectileExpansion ---TODO: trigger based on contaminants ---[[=begin - -modtools/projectile-trigger -=========================== -This triggers dfhack commands when projectiles hit their targets. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -materialTriggers = materialTriggers or {} - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.projectileTrigger = function() - materialTriggers = {} -end - -function processTrigger(args) - local command2 = {} - for _,arg in ipairs(args.command) do - if arg == '\\LOCATION' then - table.insert(command2,args.pos.x) - table.insert(command2,args.pos.y) - table.insert(command2,args.pos.z) - elseif arg == '\\PROJECTILE_ID' then - table.insert(command2,args.projectile.id) - elseif arg == '\\FIRER_ID' then - table.insert(command2,args.projectile.firer.id) - elseif string.sub(arg,1,1) == '\\' then - table.insert(command2,string.sub(arg,2)) - else - table.insert(command2,arg) - end - end - dfhack.run_command(table.unpack(command2)) -end - -eventful.onProjItemCheckImpact.expansion = function(projectile) - local matStr = dfhack.matinfo.decode(projectile.item):getToken() - local table = {} - table.pos = projectile.cur_pos - table.projectile = projectile - table.item = projectile.item - for _,args in ipairs(materialTriggers[matStr] or {}) do - utils.fillTable(args,table) - processTrigger(args) - utils.unfillTable(args,table) - end -end - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'command', - 'material', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/projectile-trigger.lua -arguments - -help - print this help message - -clear - unregister all triggers - -material - specify a material for projectiles that will trigger the command - examples: - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -command [ commandList ] - \\LOCATION - \\PROJECTILE_ID - \\FIRER_ID - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - materialTriggers = {} -end - -if not args.command then - return -end - -if not args.material then - error 'specify a material' -end - -if not dfhack.matinfo.find(args.material) then - error ('invalid material: ' .. args.material) -end - -if not materialTriggers[args.material] then - materialTriggers[args.material] = {} -end -table.insert(materialTriggers[args.material], args) - - diff --git a/scripts/modtools/random-trigger.lua b/scripts/modtools/random-trigger.lua deleted file mode 100644 index b234b85ef..000000000 --- a/scripts/modtools/random-trigger.lua +++ /dev/null @@ -1,179 +0,0 @@ --- triggers random scripts ---[[=begin - -modtools/random-trigger -======================= -This triggers random dfhack commands with specified probabilities. -Register a few scripts, then tell it to "go" and it will pick one -based on the probability weights you specified. Outcomes are mutually -exclusive. To make independent random events, call the script multiple -times. - -=end]] -local utils = require 'utils' -local eventful = require 'plugins.eventful' - -outcomeLists = outcomeLists or {} -randomGen = randomGen or dfhack.random.new() - -eventful.enableEvent(eventful.eventType.UNLOAD, 1) -eventful.onUnload.randomTrigger = function() - outcomeLists = {} -end - -validArgs = validArgs or utils.invert({ - 'help', - 'command', - 'outcomeListName', - 'weight', - 'seed', - 'trigger', - 'preserveList', - 'withProbability', - 'listOutcomes', - 'clear', -}) - -local function triggerEvent(outcomeListName) - local outcomeList = outcomeLists[outcomeListName] - local r = randomGen:random(outcomeList.total) - local sum = 0 - --print ('r = ' .. r) - for i,outcome in ipairs(outcomeList.outcomes) do - sum = sum + outcome.weight - if sum > r then - local temp = outcome.command - --print('triggering outcome ' .. i .. ': "' .. table.concat(temp, ' ') .. '"') - --dfhack.run_command(table.unpack(temp)) - dfhack.run_script(table.unpack(temp)) - break - else - --print ('sum = ' .. sum .. ' <= r = ' .. r) - end - end - --print('Done.') - --dfhack.print('\n') -end - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/random-trigger.lua -Allows mutually-exclusive random events. Register a list of scripts along with positive integer relative weights, then tell the script to select one of them with the specified probabilities and run it. -The weights must be positive integers, but they do NOT have to sum to 100 or any other particular number. -The outcomes are mutually exclusive: only one will be triggered. -If you want multiple independent random events, call the script multiple times. -99% of the time, you won't need to worry about this, but just in case, you can specify a name of a list of outcomes to prevent interference from other scripts that call this one. -That also permits situations where you don't know until runtime what outcomes you want. -For example, you could make a reaction-trigger that registers the worker as a mayor candidate, then run this script to choose a random mayor from the list of units that did the mayor reaction. - -arguments: - -help - print this help message - -outcomeListName name - specify the name of this list of outcomes to prevent interference if two scripts are registering outcomes at the same time - if none is specified, the default outcome list is selected automatically - -command [ commandStrs ] - specify the command to be run if this outcome is selected - must be specified unless the -trigger argument is given - -weight n - the relative probability weight of this outcome - n must be a non-negative integer - if not specified, n=1 is used by default - -trigger - selects a random script based on the specified outcomeList (or the default one if none is specified) - -preserveList - when combined with trigger, preserves the list of outcomes so you don't have to register them again - it is extremely highly recommended that you always specify the outcome list name when you give this command to prevent almost certain interference - if you want to trigger one of 5 outcomes three times, you might want this option even without -outcomeListName - most of the time, you won't want this - will NOT be preserved after the user saves/loads (ask expwnent if you want this: it's not that hard but if nobody wants it I won't bother) - performance will be slightly faster if you preserve the outcome lists when possible and trigger them multiple times instead of reregistering each time, but the effect should be small - -withProbability p - p is a real number between 0 and 1 inclusive - triggers the command immediately with this probability - -seed s - sets the random seed (guarantees the same sequence of random numbers will be produced internally) - use for debugging purposes - -listOutcomes - lists the currently registered list of outcomes of the outcomeList along with their probability weights - use for debugging purposes - -clear - unregister everything -]]) - return -end - -if args.clear then - outcomeLists = {} -end - -if args.weight and not tonumber(args.weight) then - error ('Invalid weight: ' .. args.weight) -end -args.weight = (args.weight and tonumber(args.weight)) or 1 -if args.weight ~= math.floor(args.weight) then - error 'Noninteger weight.' -end -if args.weight < 0 then - error 'invalid weight: must be non-negative' -end - -if args.seed then - randomGen:init(tonumber(args.seed), 37) --37 is probably excessive and definitely arbitrary -end - -args.outcomeListName = args.outcomeListName or '' -args.outcomeListName = 'outcomeList ' .. args.outcomeListName - -if args.withProbability then - args.withProbability = tonumber(args.withProbability) - if not args.withProbability or args.withProbability < 0 or args.withProbability > 1 then - error('Invalid withProbability: ' .. (args.withProbability or 'nil')) - end - if randomGen:drandom() < args.withProbability then - dfhack.run_command(table.unpack(args.command)) - end -end - -if args.trigger then - triggerEvent(args.outcomeListName) - if not args.preserveList then - outcomeLists[args.outcomeListName] = nil - end - return -end - -if args.listOutcomes then - local outcomeList = outcomeLists[args.outcomeListName] - if not outcomeList then - print ('No outcomes registered.') - return - end - print ('Total weight: ' .. outcomeList.total) - for _,outcome in ipairs(outcomeList.outcomes) do - print(' outcome weight ' .. outcome.weight .. ': ' .. table.concat(outcome.command, ' ')) - end - print('\n') - return -end - -if not args.command then - return -end - ---actually register -local outcomeList = outcomeLists[args.outcomeListName] -if not outcomeList then - outcomeLists[args.outcomeListName] = {} - outcomeList = outcomeLists[args.outcomeListName] -end - -outcomeList.total = args.weight + (outcomeList.total or 0) -local outcome = {} -outcome.weight = args.weight -outcome.command = args.command -outcomeList.outcomes = outcomeList.outcomes or {} -table.insert(outcomeList.outcomes, outcome) - - diff --git a/scripts/modtools/reaction-product-trigger.lua b/scripts/modtools/reaction-product-trigger.lua deleted file mode 100644 index 69688ce2c..000000000 --- a/scripts/modtools/reaction-product-trigger.lua +++ /dev/null @@ -1,132 +0,0 @@ --- trigger commands before/after reactions produce items --- author expwnent ---@ module = true ---[[=begin - -modtools/reaction-product-trigger -================================= -This triggers dfhack commands when reaction products are produced, once per -product. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - ---TODO: onUnload -productHooks = productHooks or {} - -reactionInputItems = reactionInputItems - -function preserveReagents() - reactionInputItems:resize(0) -end - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.reactionProductTrigger = function() - productHooks = {} -end - ---productHooks.before = productHooks.before or {} ---productHooks.after = productHooks.after or {} - -local function processArgs(args, reaction, reaction_product, unit, input_items, input_reagents, output_items, buildingId) - local result = {} - for _,arg in ipairs(args) do - if arg == '\\WORKER_ID' then - table.insert(result,tostring(unit.id)) - elseif arg == '\\REACTION' then - table.insert(result,reaction.code) --- elseif arg == '\\REACTION_PRODUCT' then --- table.insert(result,reaction_product) - elseif arg == '\\INPUT_ITEMS' then - --table.insert(result,'[') - for _,item in ipairs(input_items) do - table.insert(result,tostring(item.id)) - end - --table.insert(result,']') - elseif arg == '\\OUTPUT_ITEMS' then - --table.insert(result,'[') - for _,item in ipairs(output_items) do - table.insert(result,tostring(item.id)) - end - --table.insert(result,']') - elseif arg == '\\BUILDING_ID' then - table.insert(result,tostring(buildingId)) - elseif string.sub(arg,1,1) == '\\' then - table.insert(result,string.sub(arg,2)) - else - table.insert(result,arg) - end - end - return result -end - -local function afterProduce(reaction,reaction_product,unit,input_items,input_reagents,output_items) - --printall(unit.job.current_job) - local _,buildingId = dfhack.script_environment('modtools/reaction-trigger').getWorkerAndBuilding(unit.job.current_job) - for _,hook in ipairs(productHooks[reaction.code] or {}) do - local command = hook.command - local processed = processArgs(command, reaction, reaction_product, unit, input_items, input_reagents, output_items, buildingId) - dfhack.run_command(table.unpack(processed)) - end -end - -eventful.onReactionComplete.reactionProductTrigger = function(reaction,reaction_product,unit,input_items,input_reagents,output_items) - reactionInputItems = input_items - afterProduce(reaction,reaction_product,unit,input_items,input_reagents,output_items) - reactionInputItems = nil -end - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'reactionName', - 'command', -}) - -if moduleMode then - return -end - -local args = {...} or {} -args = utils.processArgs(args, validArgs) - -if args.help then - print([[scripts/modtools/reaction-product-trigger.lua -arguments: - -help - print this help message - -clear - unregister all reaction hooks - -reactionName name - specify the name of the reaction - -command [ commandStrs ] - specify the command to be run on the target(s) - special args - \\WORKER_ID - \\REACTION - \\BUILDING_ID - \\LOCATION - \\INPUT_ITEMS - \\OUTPUT_ITEMS - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - productHooks = {} -end - -if not args.reactionName then - error('No reactionName.') -end - -if not args.command then - error('No command.') -end - -productHooks[args.reactionName] = productHooks[args.reactionName] or {} -table.insert(productHooks[args.reactionName], args) - diff --git a/scripts/modtools/reaction-trigger-transition.lua b/scripts/modtools/reaction-trigger-transition.lua deleted file mode 100644 index 3d0b7ee65..000000000 --- a/scripts/modtools/reaction-trigger-transition.lua +++ /dev/null @@ -1,147 +0,0 @@ --- help transition from autoSyndrome --- author expwnent ---[[=begin - -modtools/reaction-trigger-transition -==================================== -Scans raw files and creates a file to help modders transition from -autoSyndrome to reaction-trigger. - -Prints useful things to the console and a file to help modders -transition from autoSyndrome to reaction-trigger. This script -is basically an apology for breaking backward compatibiility, -and will be removed eventually. - -=end]] -local function maybeQuote(str) - if str == '' or string.find(str,' ') then - return ('"' .. str .. '"') - else - return str - end -end - -warnings = '' -output = '' -for _,reaction in ipairs(df.global.world.raws.reactions) do - local function foreachProduct(product) - local prodType = product:getType() - if prodType ~= df.reaction_product_type.item then - return - end - if product.item_type ~= df.item_type.BOULDER then - return - end - if product.mat_index < 0 then - return - end - local inorganic = df.global.world.raws.inorganics[product.mat_index] - local didInorganicName - for _,syndrome in ipairs(inorganic.material.syndrome) do - local workerOnly = true - local allowMultipleTargets = false; - local command - local commandStr - local destroyRock = true; - local foundAutoSyndrome = false; - local resetPolicy; - for i,synclass in ipairs(syndrome.syn_class) do - synclass = synclass.value - if false then - elseif synclass == '\\AUTO_SYNDROME' then - foundAutoSyndrome = true - elseif synclass == '\\ALLOW_NONWORKER_TARGETS' then - workerOnly = false - elseif synclass == '\\ALLOW_MULTIPLE_TARGETS' then - allowMultipleTargets = true - elseif synclass == '\\PRESERVE_ROCK' then - destroyRock = false - elseif synclass == '\\RESET_POLICY DoNothing' then - resetPolicy = 'DoNothing' - elseif synclass == '\\RESET_POLICY ResetDuration' then - resetPolicy = 'ResetDuration' - elseif synclass == '\\RESET_POLICY AddDuration' then - resetPolicy = 'AddDuration' - elseif synclass == '\\RESET_POLICY NewInstance' then - resetPolicy = 'NewInstance' - elseif synclass == '\\COMMAND' then - command = '' - elseif command then - if synclass == '\\LOCATION' then - command = command .. '\\LOCATION ' - elseif synclass == '\\WORKER_ID' then - command = command .. '\\WORKER_ID ' - elseif synclass == '\\REACTION_INDEX' then - warnings = warnings .. ('Warning: \\REACTION_INDEX is deprecated. Use \\REACTION_NAME instead.\n') - command = command .. '\\REACTION_NAME ' - else - commandStr = true - command = command .. maybeQuote(synclass) .. ' ' - end - end - end - if foundAutoSyndrome then - if destroyRock then - warnings = warnings .. ('Warning: instead of destroying the rock, do not produce it in the first place.\n') - end - if workerOnly then - workerOnly = 'true' - else - workerOnly = 'false' - end - if allowMultipleTargets then - allowMultipleTargets = 'true' - else - allowMultipleTargets = 'false' - end - local reactionTriggerStr = 'modtools/reaction-trigger -reactionName ' .. maybeQuote(reaction.code) --.. '"' - if workerOnly ~= 'true' then - reactionTriggerStr = reactionTriggerStr .. ' -workerOnly ' .. workerOnly - end - if allowMultipleTargets ~= 'false' then - reactionTriggerStr = reactionTriggerStr .. ' -allowMultipleTargets ' .. allowMultipleTargets - end - if resetPolicy and resetPolicy ~= 'NewInstance' then - reactionTriggerStr = reactionTriggerStr .. ' -resetPolicy ' .. resetPolicy - end - if #syndrome.ce > 0 then - if syndrome.syn_name == '' then - warnings = warnings .. ('Warning: give this syndrome a name!\n') - end - reactionTriggerStr = reactionTriggerStr .. ' -syndrome ' .. maybeQuote(syndrome.syn_name) .. '' - end - if command and commandStr then - reactionTriggerStr = reactionTriggerStr .. ' -command [ ' .. command .. ']' - end - if (not command or command == '') and (not syndrome.syn_name or syndrome.syn_name == '') then - --output = output .. '#' - else - if not didInorganicName then --- output = output .. '# ' .. (inorganic.id) .. '\n' - didInorganicName = true - end - output = output .. (reactionTriggerStr) .. '\n' - end - end - end - end - for _,product in ipairs(reaction.products) do - foreachProduct(product) - end -end - -print(warnings) -print('\n\n\n\n') -print(output) -local file = io.open('reaction-trigger-transition.txt', 'w+') ---io.output(file) ---file:write(warnings) ---file:write('\n\n\n\n') -file:write(output) -file:flush() ---io.flush(file) -io.close(file) ---io.output() -print('transition information written to reaction-trigger-transition.txt') - - diff --git a/scripts/modtools/reaction-trigger.lua b/scripts/modtools/reaction-trigger.lua deleted file mode 100644 index 9589a9c4c..000000000 --- a/scripts/modtools/reaction-trigger.lua +++ /dev/null @@ -1,241 +0,0 @@ --- trigger commands when custom reactions complete --- author expwnent --- replaces autoSyndrome ---@ module = true ---[[=begin - -modtools/reaction-trigger -========================= -Triggers dfhack commands when custom reactions complete, regardless of whether -it produced anything, once per completion. Use the ``-help`` command -for more information. - -=end]] -local eventful = require 'plugins.eventful' -local syndromeUtil = require 'syndrome-util' -local utils = require 'utils' - -reactionHooks = reactionHooks or {} - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.reactionTrigger = function() - reactionHooks = {} -end - -function getWorkerAndBuilding(job) - local workerId = -1 - local buildingId = -1 - for _,generalRef in ipairs(job.general_refs) do - if generalRef:getType() == df.general_ref_type.UNIT_WORKER then - if workerId ~= -1 then - print(job) - printall(job) - error('reaction-trigger: two workers on same job: ' .. workerId .. ', ' .. generalRef.unit_id) - else - workerId = generalRef.unit_id - if workerId == -1 then - print(job) - printall(job) - error('reaction-trigger: invalid worker') - end - end - elseif generalRef:getType() == df.general_ref_type.BUILDING_HOLDER then - if buildingId ~= -1 then - print(job) - printall(job) - error('reaction-trigger: two buildings same job: ' .. buildingId .. ', ' .. generalRef.building_id) - else - buildingId = generalRef.building_id - if buildingId == -1 then - print(job) - printall(job) - error('reaction-trigger: invalid building') - end - end - end - end - return workerId,buildingId -end - -local function processCommand(job, worker, target, building, command) - local result = {} - for _,arg in ipairs(command) do - if arg == '\\WORKER_ID' then - table.insert(result,''..worker.id) - elseif arg == '\\TARGET_ID' then - table.insert(result,''..target.id) - elseif arg == '\\BUILDING_ID' then - table.insert(result,''..building.id) - elseif arg == '\\LOCATION' then - table.insert(result,''..job.pos.x) - table.insert(result,''..job.pos.y) - table.insert(result,''..job.pos.z) - elseif arg == '\\REACTION_NAME' then - table.insert(result,''..job.reaction_name) - elseif string.sub(arg,1,1) == '\\' then - table.insert(result,string.sub(arg,2)) - else - table.insert(result,arg) - end - end - return result -end - -eventful.onJobCompleted.reactionTrigger = function(job) - if job.completion_timer > 0 then - return - end - --- if job.job_type ~= df.job_type.CustomReaction then --- --TODO: support builtin reaction triggers if someone asks --- return --- end - - if not job.reaction_name or job.reaction_name == '' then - return - end --- print('reaction name: ' .. job.reaction_name) - if not job.reaction_name or not reactionHooks[job.reaction_name] then - return - end - - local worker,building = getWorkerAndBuilding(job) - worker = df.unit.find(worker) - building = df.building.find(building) - if not worker or not building then - --this probably means that it finished before EventManager could get a copy of the job while the job was running - --TODO: consider printing a warning once - return - end - - local function doAction(action) - local didSomething - if action.command then - local processed = processCommand(job, worker, worker, building, action.command) - dfhack.run_command(table.unpack(processed)) - end - if action.syndrome then - didSomething = syndromeUtil.infectWithSyndromeIfValidTarget(worker, action.syndrome, action.resetPolicy) or didSomething - end - if action.workerOnly then - return - end - if didSomething and not action.allowMultipleTargets then - return - end - local function foreach(unit) - if unit == worker then - return false - elseif unit.pos.z ~= building.z then - return false - elseif unit.pos.x < building.x1 or unit.pos.x > building.x2 then - return false - elseif unit.pos.y < building.y1 or unit.pos.y > building.y2 then - return false - else - if action.command then - processCommand(job, worker, unit, building, action.command) - end - if action.syndrome then - didSomething = syndrome.infectWithSyndromeIfValidTarget(unit,action.syndrome,action.resetPolicy) or didSomething - end - if didSomething and not action.allowMultipleTargets then - return true - end - return false - end - end - for _,unit in ipairs(df.global.world.units.all) do - if foreach(unit) then - break - end - end - end - for _,action in ipairs(reactionHooks[job.reaction_name]) do - doAction(action) - end -end -eventful.enableEvent(eventful.eventType.JOB_COMPLETED,0) --0 is necessary to catch cancelled jobs and not trigger them - -validArgs = validArgs or utils.invert({ - 'help', - 'clear', - 'reactionName', - 'syndrome', - 'command', - 'allowNonworkerTargets', - 'allowMultipleTargets' -}) - -if moduleMode then - return -end -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/reaction-trigger.lua -arguments: - -help - print this help message - -clear - unregister all reaction hooks - -reactionName name - specify the name of the reaction - -syndrome name - specify the name of the syndrome to be applied to the targets - -allowNonworkerTargets - allow other units in the same building to be targetted by either the script or the syndrome - -allowMultipleTargets - allow multiple targets to the script or syndrome - if absent: - if running a script, only one target will be used - if applying a syndrome, then only one target will be infected - -resetPolicy policy - set the reset policy in the case that the syndrome is already present - policy - NewInstance (default) - DoNothing - ResetDuration - AddDuration - -command [ commandStrs ] - specify the command to be run on the target(s) - special args - \\WORKER_ID - \\TARGET_ID - \\BUILDING_ID - \\LOCATION - \\REACTION_NAME - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - reactionHooks = {} -end - -if not args.reactionName then - return -end - -if not reactionHooks[args.reactionName] then - reactionHooks[args.reactionName] = {} -end - -if args.syndrome then - local foundIt - for _,syndrome in ipairs(df.global.world.raws.syndromes.all) do - if syndrome.syn_name == args.syndrome then - args.syndrome = syndrome - foundIt = true - break - end - end - if not foundIt then - error('Could not find syndrome ' .. args.syndrome) - end -end - -table.insert(reactionHooks[args.reactionName], args) - diff --git a/scripts/modtools/skill-change.lua b/scripts/modtools/skill-change.lua deleted file mode 100644 index c5cd52889..000000000 --- a/scripts/modtools/skill-change.lua +++ /dev/null @@ -1,115 +0,0 @@ --- Sets or modifies a skill of a unit ---author expwnent ---based on skillChange.lua by Putnam ---TODO: update skill level once experience increases/decreases ---TODO: skill rust? ---[[=begin - -modtools/skill-change -===================== -Sets or modifies a skill of a unit. Args: - -:-help: print the help message -:-skill skillName: set the skill that we're talking about -:-mode (add/set): are we adding experience/levels or setting them? -:-granularity (experience/level): - direct experience, or experience levels? -:-unit id: id of the target unit -:-value amount: how much to set/add - -=end]] -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'skill', - 'mode', - 'value', - 'granularity', - 'unit' -}) - -mode = mode or utils.invert({ - 'add', - 'set', -}) - -granularity = granularity or utils.invert({ - 'experience', - 'level', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/skill-change.lua -arguments - -help - print this help message - -skill skillName - set the skill that we're talking about - -mode (add/set) - are we adding experience/levels or setting them? - -granularity (experience/level) - direct experience, or experience levels? - -unit id - id of the target unit - -value amount - how much to set/add -]]) - return -end - -if not args.unit or not tonumber(args.unit) or not df.unit.find(tonumber(args.unit)) then - error 'Invalid unit.' -end -args.unit = df.unit.find(tonumber(args.unit)) - -args.skill = df.job_skill[args.skill] -args.mode = mode[args.mode or 'set'] -args.granularity = granularity[args.granularity or 'level'] -args.value = tonumber(args.value) - -if not args.skill then - error('invalid skill') -end -if not args.value then - error('invalid value') -end - -local skill -for _,skill_c in ipairs(args.unit.status.current_soul.skills) do - if skill_c.id == args.skill then - skill = skill_c - end -end - -if not skill then - skill = df.unit_skill:new() - skill.id = args.skill - utils.insert_sorted(args.unit.status.current_soul.skills,skill,'id') -end - -print('old: ' .. skill.rating .. ': ' .. skill.experience) -if args.granularity == granularity.experience then - if args.mode == mode.set then - skill.experience = args.value - elseif args.mode == mode.add then - skill.experience = skill.experience + args.value - else - error 'bad mode' - end -elseif args.granularity == granularity.level then - if args.mode == mode.set then - skill.rating = args.value - elseif args.mode == mode.add then - skill.rating = args.value + skill.rating - else - error 'bad mode' - end -else - error 'bad granularity' -end - -print('new: ' .. skill.rating .. ': ' .. skill.experience) - diff --git a/scripts/modtools/spawn-flow.lua b/scripts/modtools/spawn-flow.lua deleted file mode 100644 index 6bca9ace4..000000000 --- a/scripts/modtools/spawn-flow.lua +++ /dev/null @@ -1,91 +0,0 @@ --- spawns flows at locations ---author expwnent ---[[=begin - -modtools/spawn-flow -=================== -Creates flows at the specified location. - -=end]] -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'material', - 'flowType', - 'location', - 'flowSize', -}) -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/spawn-flow.lua -arguments: - -help - print this help message - -material mat - specify the material of the flow, if applicable - examples: - INORGANIC:IRON - CREATURE_MAT:DWARF:BRAIN - PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK - -location [ x y z] - the location to spawn the flow - -flowType type - specify the flow type - examples: - Miasma - Steam - Mist - MaterialDust - MagmaMist - Smoke - Dragonfire - Fire - Web - MaterialGas - MaterialVapor - OceanWave - SeaFoam - -flowSize size - specify how big the flow is -]]) - return -end - -local mat_index = -1; -local mat_type = -1; -if args.material then - local mat = dfhack.matinfo.find(args.material) - if not mat then - error ('Invalid material: ' .. mat) - end - mat_index = mat.index - mat_type = mat['type'] -end - -if args.flowSize and not tonumber(args.flowSize) then - error ('Invalid flow size: ' .. args.flowSize) -end - -args.flowSize = tonumber(args.flowSize or 'z') or 100 - -if not args.flowType or not df.flow_type[args.flowType] then - error ('Invalid flow type: ' .. (args.flowType or 'none specified')) -end -args.flowType = df.flow_type[args.flowType] - -if not args.location then - error 'Specify a location.' -end - -local pos = df.coord:new(); -pos.x = tonumber(args.location[1] or 'a') -pos.y = tonumber(args.location[2] or 'a') -pos.z = tonumber(args.location[3] or 'a') -if not pos.x or not pos.y or not pos.z then - error ('Invalid pos.') -end - -dfhack.maps.spawnFlow(pos, args.flowType, mat_type, mat_index, args.flowSize) - diff --git a/scripts/modtools/syndrome-trigger.lua b/scripts/modtools/syndrome-trigger.lua deleted file mode 100644 index f46bbc5ad..000000000 --- a/scripts/modtools/syndrome-trigger.lua +++ /dev/null @@ -1,122 +0,0 @@ --- triggers scripts when a syndrome is applied ---author expwnent ---[[=begin - -modtools/syndrome-trigger -========================= -Triggers dfhack commands when syndromes are applied to units. - -=end]] -local eventful = require 'plugins.eventful' -local utils = require 'utils' - -onInfection = onInfection or {} - -eventful.enableEvent(eventful.eventType.UNLOAD,1) -eventful.onUnload.syndromeTrigger = function() - onInfection = {} -end - -eventful.enableEvent(eventful.eventType.SYNDROME,5) --requires iterating through every unit, so not cheap, but not slow either - -local function processTrigger(args) - local command = {} - for i,arg in ipairs(args.command) do - if arg == '\\SYNDROME_ID' then - table.insert(command, '' .. args.syndrome.id) - elseif arg == '\\UNIT_ID' then - table.insert(command, '' .. args.unit.id) - elseif arg == '\\LOCATION' then - table.insert(command, '' .. args.unit.pos.x) - table.insert(command, '' .. args.unit.pos.y) - table.insert(command, '' .. args.unit.pos.z) - elseif string.sub(arg,1,1) == '\\' then - table.insert(command, string.sub(arg,2)) - else - table.insert(command, arg) - end - end - dfhack.run_command(table.unpack(command)) -end - -eventful.onSyndrome.syndromeTrigger = function(unitId, syndromeIndex) - local unit = df.unit.find(unitId) - local unit_syndrome = unit.syndromes.active[syndromeIndex] - local syn_id = unit_syndrome['type'] - if not onInfection[syn_id] then - return - end - local syndrome = df.syndrome.find(syn_id) - local table = {} - table.unit = unit - table.unit_syndrome = unit_syndrome - table.syndrome = syndrome - for _,args in ipairs(onInfection[syn_id] or {}) do - utils.fillTable(args,table) - processTrigger(args) - utils.unfillTable(args,table) - end -end - ------------------------------- ---argument processing - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'command', - 'syndrome' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/syndrome-trigger.lua -arguments - -help - print this help message - -clear - clear all triggers - -syndrome name - specify the name of a syndrome - -command [ commandStrs ] - specify the command to be executed after infection - args - \\SYNDROME_ID - \\UNIT_ID - \\LOCATION - \\anything -> \anything - anything -> anything -]]) - return -end - -if args.clear then - onInfection = {} -end - -if not args.command then - return -end - -if not args.syndrome then - error 'Select a syndrome.' -end - -local syndrome -for _,syn in ipairs(df.global.world.raws.syndromes.all) do - if syn.syn_name == args.syndrome then - if syndrome then - error ('Multiple syndromes with same name: ' .. syn.syn_name) - end - syndrome = syn.id - end -end - -if not syndrome then - error ('Could not find syndrome named ' .. args.syndrome) -end - -onInfection[syndrome] = onInfection[syndrome] or {} -table.insert(onInfection[syndrome], args) - diff --git a/scripts/modtools/transform-unit.lua b/scripts/modtools/transform-unit.lua deleted file mode 100644 index 2ebdc3c8a..000000000 --- a/scripts/modtools/transform-unit.lua +++ /dev/null @@ -1,164 +0,0 @@ --- Transforms a unit into another unit type ---author expwnent ---based on shapechange by Putnam ---[[=begin - -modtools/transform-unit -======================= -Transforms a unit into another unit type, possibly permanently. -Warning: this will crash arena mode if you view the unit on the -same tick that it transforms. If you wait until later, it will be fine. - -=end]] -local utils = require 'utils' - -normalRace = normalRace or {} - -local function transform(unit,race,caste) - unit.enemy.normal_race = race - unit.enemy.normal_caste = caste - unit.enemy.were_race = race - unit.enemy.were_caste = caste -end - -validArgs = validArgs or utils.invert({ - 'clear', - 'help', - 'unit', - 'duration', - 'setPrevRace', - 'keepInventory', - 'race', - 'caste', - 'suppressAnnouncement', - 'untransform', -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[scripts/modtools/transform-unit.lua -arguments - -help - print this help message - -clear - clear records of normal races - -unit id - set the target unit - -duration ticks - how long it should last, or "forever" - -setPrevRace - make a record of the previous race so that you can change it back with -untransform - -keepInventory - move items back into inventory after transformation - -race raceName - -caste casteName - -suppressAnnouncement - don't show the Unit has transformed into a Blah! event - -untransform - turn the unit back into what it was before -]]) - return -end - -if args.clear then - normalRace = {} -end - -if not args.unit then - error 'Specify a unit.' -end - -if not args.duration then - args.duration = 'forever' -end - -local raceIndex -local race -local caste -if args.untransform then - local unit = df.unit.find(tonumber(args.unit)) - raceIndex = normalRace[args.unit].race - race = df.creature_raw.find(raceIndex) - caste = normalRace[args.unit].caste - normalRace[args.unit] = nil -else - if not args.race or not args.caste then - error 'Specficy a target form.' - end - - --find race - for i,v in ipairs(df.global.world.raws.creatures.all) do - if v.creature_id == args.race then - raceIndex = i - race = v - break - end - end - - if not race then - error 'Invalid race.' - end - - for i,v in ipairs(race.caste) do - if v.caste_id == args.caste then - caste = i - break - end - end - - if not caste then - error 'Invalid caste.' - end -end - -local unit = df.unit.find(tonumber(args.unit)) -local oldRace = unit.enemy.normal_race -local oldCaste = unit.enemy.normal_caste -if args.setPrevRace then - normalRace[args.unit] = {} - normalRace[args.unit].race = oldRace - normalRace[args.unit].caste = oldCaste -end -transform(unit,raceIndex,caste,args.setPrevRace) - -local inventoryItems = {} - -local function getInventory() - local result = {} - for _,item in ipairs(unit.inventory) do - table.insert(result, item:new()); - end - return result -end - -local function restoreInventory() - dfhack.timeout(1, 'ticks', function() - for _,item in ipairs(inventoryItems) do - dfhack.items.moveToInventory(item.item, unit, item.mode, item.body_part_id) - item:delete() - end - inventoryItems = {} - end) -end - -if args.keepInventory then - inventoryItems = getInventory() -end - -if args.keepInventory then - restoreInventory() -end -if args.duration and args.duration ~= 'forever' then - --when the timeout ticks down, transform them back - dfhack.timeout(tonumber(args.duration), 'ticks', function() - if args.keepInventory then - inventoryItems = getInventory() - end - transform(unit,oldRace,oldCaste) - if args.keepInventory then - restoreInventory() - end - end) -end - diff --git a/scripts/multicmd.rb b/scripts/multicmd.rb deleted file mode 100644 index 1481cc52c..000000000 --- a/scripts/multicmd.rb +++ /dev/null @@ -1,16 +0,0 @@ -# run many dfhack commands separated by ; -=begin - -multicmd -======== -Run multiple dfhack commands. The argument is split around the -character ; and all parts are run sequentially as independent -dfhack commands. Useful for hotkeys. - -Example:: - - multicmd locate-ore IRON ; digv ; digcircle 16 - -=end - -$script_args.join(' ').split(/\s*;\s*/).each { |cmd| df.dfhack_run cmd } diff --git a/scripts/points.lua b/scripts/points.lua deleted file mode 100644 index b81d4637b..000000000 --- a/scripts/points.lua +++ /dev/null @@ -1,13 +0,0 @@ --- Set available points at the embark screen --- http://www.bay12forums.com/smf/index.php?topic=135506.msg4925005#msg4925005 ---[[=begin - -points -====== -Sets available points at the embark screen to the specified number. Eg. -``points 1000000`` would allow you to buy everything, or ``points 0`` would -make life quite difficult. - -=end]] - -df.global.world.worldgen.worldgen_parms.embark_points=tonumber(...) diff --git a/scripts/position.lua b/scripts/position.lua deleted file mode 100644 index 2ff44c14a..000000000 --- a/scripts/position.lua +++ /dev/null @@ -1,51 +0,0 @@ ---prints current time and position ---[[=begin - -position -======== -Reports the current time: date, clock time, month, and season. Also reports -location: z-level, cursor position, window size, and mouse location. - -=end]] - -local months = { - 'Granite, in early Spring.', - 'Slate, in mid Spring.', - 'Felsite, in late Spring.', - 'Hematite, in early Summer.', - 'Malachite, in mid Summer.', - 'Galena, in late Summer.', - 'Limestone, in early Autumn.', - 'Sandstone, in mid Autumn.', - 'Timber, in late Autumn.', - 'Moonstone, in early Winter.', - 'Opal, in mid Winter.', - 'Obsidian, in late Winter.', -} - ---Fortress mode counts 1200 ticks per day and 403200 per year ---Adventurer mode counts 86400 ticks to a day and 29030400 ticks per year ---Twelve months per year, 28 days to every month, 336 days per year - -local julian_day = math.floor(df.global.cur_year_tick / 1200) + 1 -local month = math.floor(julian_day / 28) + 1 --days and months are 1-indexed -local day = julian_day % 28 - -local time_of_day = math.floor(df.global.cur_year_tick_advmode / 336) -local second = time_of_day % 60 -local minute = math.floor(time_of_day / 60) % 60 -local hour = math.floor(time_of_day / 3600) % 24 - -print('Time:') -print(' The time is '..string.format('%02d:%02d:%02d', hour, minute, second)) -print(' The date is '..string.format('%05d-%02d-%02d', df.global.cur_year, month, day)) -print(' It is the month of '..months[month]) ---TODO: print(' It is the Age of '..age_name) - -print('Place:') -print(' The z-level is z='..df.global.window_z) -print(' The cursor is at x='..df.global.cursor.x..', y='..df.global.cursor.y) -print(' The window is '..df.global.gps.dimx..' tiles wide and '..df.global.gps.dimy..' tiles high') -if df.global.gps.mouse_x == -1 then print(' The mouse is not in the DF window') else -print(' The mouse is at x='..df.global.gps.mouse_x..', y='..df.global.gps.mouse_y..' within the window') end ---TODO: print(' The fortress is at '..x, y..' on the world map ('..worldsize..' square)') diff --git a/scripts/pref-adjust.lua b/scripts/pref-adjust.lua deleted file mode 100644 index a43ee8712..000000000 --- a/scripts/pref-adjust.lua +++ /dev/null @@ -1,106 +0,0 @@ --- Adjust all preferences of all dwarves in play --- by vjek ---[[=begin - -pref-adjust -=========== -A two-stage script: ``pref-adjust clear`` removes preferences from all dwarves, -and ``pref-adjust`` inserts an 'ideal' set which is easy to satisfy:: - - Feb Idashzefon likes wild strawberries for their vivid red color, - fisher berries for their round shape, prickle berries for their - precise thorns, plump helmets for their rounded tops, prepared meals, - plants, drinks, doors, thrones, tables and beds. When possible, she - prefers to consume wild strawberries, fisher berries, prickle - berries, plump helmets, strawberry wine, fisher berry wine, prickle - berry wine, and dwarven wine. - -=end]] - --- --------------------------------------------------------------------------- -function brainwash_unit(unit) - - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local pss_counter=31415926 - - local prefcount = #(unit.status.current_soul.preferences) - print ("Before, unit "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." has "..prefcount.." preferences") - - utils = require 'utils' - -- below populates an array with all creature names and id's, used for 'detests...' - rtbl={} - vec=df.global.world.raws.creatures.all - for k=0,#vec-1 do - local name=vec[k].creature_id - rtbl[name]=k - end - - -- Now iterate through for the type 3 detests... - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.TROLL , creature_id = rtbl.TROLL , color_id = rtbl.TROLL , shape_id = rtbl.TROLL , plant_id = rtbl.TROLL , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.BIRD_BUZZARD , creature_id = rtbl.BIRD_BUZZARD , color_id = rtbl.BIRD_BUZZARD , shape_id = rtbl.BIRD_BUZZARD , plant_id = rtbl.BIRD_BUZZARD , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.BIRD_VULTURE , creature_id = rtbl.BIRD_VULTURE , color_id = rtbl.BIRD_VULTURE , shape_id = rtbl.BIRD_VULTURE , plant_id = rtbl.BIRD_VULTURE , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 3 , item_type = rtbl.CRUNDLE , creature_id = rtbl.CRUNDLE , color_id = rtbl.CRUNDLE , shape_id = rtbl.CRUNDLE , plant_id = rtbl.CRUNDLE , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- and the type 4 likes - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 4 , item_type = df.item_type.WEAPON , creature_id = df.item_type.WEAPON , color_id = df.item_type.WEAPON , shape_id = df.item_type.WEAPON , plant_id = df.item_type.WEAPON , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 4 , item_type = df.item_type.ARMOR , creature_id = df.item_type.ARMOR , color_id = df.item_type.ARMOR , shape_id = df.item_type.ARMOR , plant_id = df.item_type.ARMOR , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 4 , item_type = df.item_type.SHIELD , creature_id = df.item_type.SHIELD , color_id = df.item_type.SHIELD , shape_id = df.item_type.SHIELD , plant_id = df.item_type.SHIELD , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- prefers plump helmets for their ... - local ph_mat_type=dfhack.matinfo.find("MUSHROOM_HELMET_PLUMP:STRUCTURAL").index - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 5 , item_type = ph_mat_type , creature_id = ph_mat_type , color_id = ph_mat_type , shape_id = ph_mat_type , plant_id = ph_mat_type , item_subtype = -1 , mattype = -1 , matindex = -1 , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- prefers to consume dwarven wine: - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 2 , item_type = 68 , creature_id = 68 , color_id = 68 , shape_id = 68 , plant_id = 68 , item_subtype = -1 , mattype = dfhack.matinfo.find("MUSHROOM_HELMET_PLUMP:DRINK").type , matindex = dfhack.matinfo.find("MUSHROOM_HELMET_PLUMP:DRINK").index , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - -- likes iron, steel (0,8) adam is 25 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 0 , item_type = -1 , creature_id = -1 , color_id = -1 , shape_id = -1 , plant_id = -1 , item_subtype = -1 , mattype = 0 , matindex = dfhack.matinfo.find("IRON").index , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - pss_counter = pss_counter + 1 - utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = 0 , item_type = -1 , creature_id = -1 , color_id = -1 , shape_id = -1 , plant_id = -1 , item_subtype = -1 , mattype = 0 , matindex = dfhack.matinfo.find("STEEL").index , active = true, prefstring_seed = pss_counter }, 'prefstring_seed') - - prefcount = #(unit.status.current_soul.preferences) - print ("After, unit "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." has "..prefcount.." preferences") - -end --- --------------------------------------------------------------------------- -function clear_preferences(v) - unit=v - - local prefs=unit.status.current_soul.preferences - for index,pref in ipairs(prefs) do - pref:delete() - end - prefs:resize(0) -end --- --------------------------------------------------------------------------- -function clearpref_all_dwarves() - for _,v in ipairs(df.global.world.units.active) do - if v.race == df.global.ui.race_id then - print("Clearing Preferences for "..dfhack.TranslateName(dfhack.units.getVisibleName(v))) - clear_preferences(v) - end - end -end --- --------------------------------------------------------------------------- -function adjust_all_dwarves() - for _,v in ipairs(df.global.world.units.active) do - if v.race == df.global.ui.race_id then - print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(v))) - brainwash_unit(v) - end - end -end --- --------------------------------------------------------------------------- --- main script operation starts here --- --------------------------------------------------------------------------- -clearpref_all_dwarves() -adjust_all_dwarves() diff --git a/scripts/putontable.lua b/scripts/putontable.lua deleted file mode 100644 index daba8a46f..000000000 --- a/scripts/putontable.lua +++ /dev/null @@ -1,36 +0,0 @@ --- Makes item appear on the table (just like in shops) ---[[=begin - -putontable -========== -Makes item appear on the table, like in adventure mode shops. -Arguments: ``-a`` or ``--all`` for all items. - -=end]] - -local pos=df.global.cursor -local args={...} -local doall -if args[1]=="-a" or args[1]=="--all" then - doall=true -end -local build,items -items={} -build=dfhack.buildings.findAtTile(pos.x,pos.y,pos.z) -if not df.building_tablest:is_instance(build) then - error("No table found at cursor") -end -for k,v in pairs(df.global.world.items.all) do - if pos.x==v.pos.x and pos.y==v.pos.y and pos.z==v.pos.z and v.flags.on_ground then - table.insert(items,v) - if not doall then - break - end - end -end -if #items==0 then - error("No items found!") -end -for k,v in pairs(items) do - dfhack.items.moveToBuilding(v,build,0) -end diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua deleted file mode 100644 index 3f4cc3954..000000000 --- a/scripts/quicksave.lua +++ /dev/null @@ -1,39 +0,0 @@ --- Makes the game immediately save the state. ---[[=begin - -quicksave -========= -If called in dwarf mode, makes DF immediately saves the game by setting a flag -normally used in seasonal auto-save. - -=end]] - -if not dfhack.isMapLoaded() then - qerror("World and map aren't loaded.") -end - -if not dfhack.world.isFortressMode() then - qerror('This script can only be used in fortress mode') -end - -local ui_main = df.global.ui.main -local flags4 = df.global.d_init.flags4 - -local function restore_autobackup() - if ui_main.autosave_request and dfhack.isMapLoaded() then - dfhack.timeout(10, 'frames', restore_autobackup) - else - flags4.AUTOBACKUP = true - end -end - --- Request auto-save -ui_main.autosave_request = true - --- And since it will overwrite the backup, disable it temporarily -if flags4.AUTOBACKUP then - flags4.AUTOBACKUP = false - restore_autobackup() -end - -print 'The game should save the state now.' diff --git a/scripts/region-pops.lua b/scripts/region-pops.lua deleted file mode 100644 index 935cd0f02..000000000 --- a/scripts/region-pops.lua +++ /dev/null @@ -1,199 +0,0 @@ --- Show or edit regional plant and animal populations ---[[=begin - -region-pops -=========== -Show or modify the populations of animals in the region. - -Usage: - -:region-pops list [pattern]: - Lists encountered populations of the region, possibly restricted by pattern. -:region-pops list-all [pattern]: - Lists all populations of the region. -:region-pops boost : - Multiply all populations of TOKEN by factor. - If the factor is greater than one, increases the - population, otherwise decreases it. -:region-pops boost-all : - Same as above, but match using a pattern acceptable to list. -:region-pops incr : - Augment (or diminish) all populations of TOKEN by factor (additive). -:region-pops incr-all : - Same as above, but match using a pattern acceptable to list. - -=end]] - -local utils = require 'utils' - -local function sort_keys(tab) - local kt = {} - for k,v in pairs(tab) do table.insert(kt,k) end - table.sort(kt) - return ipairs(kt) -end - -local is_plant_map = { - Animal = false, Vermin = false, VerminInnumerable = false, - ColonyInsect = false, Tree = true, Grass = true, Bush = true -} - -function enum_populations() - local stat_table = { - plants = {}, - creatures = {}, - any = {} - } - - for i,v in ipairs(df.global.world.populations) do - local typeid = df.world_population_type[v.type] - local is_plant = is_plant_map[typeid] - local id, obj, otable, idtoken - - if is_plant then - id = v.plant - obj = df.plant_raw.find(id) - otable = stat_table.plants - idtoken = obj.id - else - id = v.race - obj = df.creature_raw.find(id) - otable = stat_table.creatures - idtoken = obj.creature_id - end - - local entry = otable[idtoken] - if not entry then - entry = { - obj = obj, token = idtoken, id = id, records = {}, - count = 0, known_count = 0, - known = false, infinite = false - } - otable[idtoken] = entry - stat_table.any[idtoken] = entry - end - - table.insert(entry.records, v) - entry.known = entry.known or v.flags.discovered - - if v.quantity < 10000001 then - entry.count = entry.count + v.quantity - if v.flags.discovered then - entry.known_count = entry.known_count + v.quantity - end - else - entry.infinite = true - end - end - - return stat_table -end - -function list_poptable(entries, all, pattern) - for _,k in sort_keys(entries) do - local entry = entries[k] - if (all or entry.known) and (not pattern or string.match(k,pattern)) then - local count = entry.known_count - if all then - count = entry.count - end - if entry.infinite then - count = 'innumerable' - end - print(string.format('%-40s %s', entry.token, count)) - end - end -end - -function list_populations(stat_table, all, pattern) - print('Plants:') - list_poptable(stat_table.plants, true, pattern) - print('\nCreatures and vermin:') - list_poptable(stat_table.creatures, all, pattern) -end - - -function boost_population(entry, factor, boost_count) - for _,v in ipairs(entry.records) do - if v.quantity < 10000001 then - boost_count = boost_count + 1 - v.quantity = math.floor(v.quantity * factor) - end - end - return boost_count -end - -function incr_population(entry, factor, boost_count) - for _,v in ipairs(entry.records) do - if v.quantity < 10000001 then - boost_count = boost_count + 1 - v.quantity = math.max(0, v.quantity + factor) - end - end - return boost_count -end - -local args = {...} -local pops = enum_populations() - -if args[1] == 'list' or args[1] == 'list-all' then - list_populations(pops, args[1] == 'list-all', args[2]) -elseif args[1] == 'boost' or args[1] == 'boost-all' then - local factor = tonumber(args[3]) - if not factor or factor < 0 then - qerror('Invalid boost factor.') - end - - local count = 0 - - if args[1] == 'boost' then - local entry = pops.any[args[2]] or qerror('Unknown population token.') - count = boost_population(entry, factor, count) - else - for k,entry in pairs(pops.any) do - if string.match(k, args[2]) then - count = boost_population(entry, factor, count) - end - end - end - - print('Updated '..count..' populations.') -elseif args[1] == 'incr' or args[1] == 'incr-all' then - local factor = tonumber(args[3]) - if not factor then - qerror('Invalid increment factor.') - end - - local count = 0 - - if args[1] == 'incr' then - local entry = pops.any[args[2]] or qerror('Unknown population token.') - count = incr_population(entry, factor, count) - else - for k,entry in pairs(pops.any) do - if string.match(k, args[2]) then - count = incr_population(entry, factor, count) - end - end - end - - print('Updated '..count..' populations.') -else - print([[ -Usage: - region-pops list [pattern] - Lists encountered populations of the region, possibly restricted by pattern. - region-pops list-all [pattern] - Lists all populations of the region. - region-pops boost - Multiply all populations of TOKEN by factor. - If the factor is greater than one, increases the - population, otherwise decreases it. - region-pops boost-all - Same as above, but match using a pattern acceptable to list. - region-pops incr - Augment (or diminish) all populations of TOKEN by factor (additive). - region-pops incr-all - Same as above, but match using a pattern acceptable to list. -]]) -end diff --git a/scripts/rejuvenate.lua b/scripts/rejuvenate.lua deleted file mode 100644 index fe19ad545..000000000 --- a/scripts/rejuvenate.lua +++ /dev/null @@ -1,30 +0,0 @@ --- make the selected dwarf 20 years old --- by vjek ---[[=begin - -rejuvenate -========== -Set the age of the selected dwarf to 20 years. Useful if valuable citizens are -getting old, or there are too many babies around... - -=end]] - -function rejuvenate() - local current_year,newbirthyear - unit=dfhack.gui.getSelectedUnit() - - if unit==nil then print ("No unit under cursor! Aborting.") return end - - current_year=df.global.cur_year - newbirthyear=current_year - 20 - if unit.relations.birth_year < newbirthyear then - unit.relations.birth_year=newbirthyear - end - if unit.relations.old_year < current_year+100 then - unit.relations.old_year=current_year+100 - end - print (dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." is now 20 years old and will live at least 100 years") - -end - -rejuvenate() diff --git a/scripts/remove-stress.lua b/scripts/remove-stress.lua deleted file mode 100644 index d384129fd..000000000 --- a/scripts/remove-stress.lua +++ /dev/null @@ -1,41 +0,0 @@ --- Sets stress to negative one million ---By Putnam; http://www.bay12forums.com/smf/index.php?topic=139553.msg5820486#msg5820486 ---[[=begin - -remove-stress -============= -Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or -very stressed dwarves taking on negative or greater values respectively. -Applies to the selected unit, or use ``remove-stress -all`` to apply to all units. - -=end]] - -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'all' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[ -remove-stress [-all] - sets the stress level of every unit to -1000000, or just the selected unit if the '-all' argument is not given -]]) - return -end - -if args.all then - for k,v in ipairs(df.global.world.units.active) do - v.status.current_soul.personality.stress_level=-1000000 - end -else - local unit = dfhack.gui.getSelectedUnit() - if unit then - unit.status.current_soul.personality.stress_level=-1000000 - else - error 'Invalid usage: No unit selected and -all argument not given.' - end -end diff --git a/scripts/remove-wear.lua b/scripts/remove-wear.lua deleted file mode 100644 index e1ed3989a..000000000 --- a/scripts/remove-wear.lua +++ /dev/null @@ -1,62 +0,0 @@ --- Resets all items in your fort to 0 wear --- original author: Laggy, edited by expwnent ---[[=begin - -remove-wear -=========== -Sets the wear on all items in your fort to zero. - -=end]] - -local args = {...} - -if args[1] == 'help' then - print([[remove-wear - this script removes wear from all items, or from individual ones - -remove-wear all - remove wear from all items -remove-wear n1 n2 n3 ... - remove wear from items with the given ids. order does not matter -repeat -time 2 months -command remove-wear all - remove wear from all items every 2 months. see repeat.lua for details -]]) - do return end -elseif args[1] == 'all' then - local count = 0; - for _,item in ipairs(df.global.world.items.all) do - if (item.wear > 0) then - item:setWear(0) - count = count+1 - end - end - print('remove-wear removed wear from '..count..' objects') -else - local argIndex = 1 - local isCompleted = {} - for i,x in ipairs(args) do - args[i] = tonumber(x) - end - table.sort(args) - for _,item in ipairs(df.global.world.items.all) do - local function loop() - if argIndex > #args then - return - elseif item.id > args[argIndex] then - argIndex = argIndex+1 - loop() - return - elseif item.id == args[argIndex] then - --print('removing wear from item with id ' .. args[argIndex]) - item:setWear(0) - isCompleted[args[argIndex]] = true - argIndex = argIndex+1 - end - end - loop() - end - for _,arg in ipairs(args) do - if isCompleted[arg] ~= true then - print('failed to remove wear from item ' .. arg .. ': could not find item with that id') - end - end -end diff --git a/scripts/repeat.lua b/scripts/repeat.lua deleted file mode 100644 index b048584d8..000000000 --- a/scripts/repeat.lua +++ /dev/null @@ -1,85 +0,0 @@ --- repeatedly call a lua script --- eg "repeat -time 1 months -command cleanowned"; to disable "repeat -cancel cleanowned" --- repeat -help for details --- author expwnent --- vaguely based on a script by Putnam ---[[=begin - -repeat -====== -Repeatedly calls a lua script at the specified interval. - -This allows neat background changes to the function of the game, especially when -invoked from an init file. For detailed usage instructions, use ``repeat -help``. - -Usage examples:: - - repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ] - repeat -time 1 -timeUnits months -command [ multicmd cleanowned scattered x; clean all ] -name clean - -The first example is abstract; the second will regularly remove all contaminants -and worn items from the game. - -``-name`` sets the name for the purposes of cancelling and making sure you don't schedule the -same repeating event twice. If not specified, it's set to the first argument after ``-command``. -``-time delay -timeUnits units``; delay is some positive integer, and units is some valid time -unit for ``dfhack.timeout(delay,timeUnits,function)``. ``-command [ ... ]`` specifies the -command to be run. - -=end]] - -local repeatUtil = require 'repeat-util' -local utils = require 'utils' - -validArgs = validArgs or utils.invert({ - 'help', - 'cancel', - 'name', - 'time', - 'timeUnits', - 'command' -}) - -local args = utils.processArgs({...}, validArgs) - -if args.help then - print([[repeat.lua - repeat -help - print this help message - repeat -cancel bob - cancels the repetition with the name bob - repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ] - -name sets the name for the purposes of cancelling and making sure you don't schedule the same repeating event twice - if not specified, it's set to the first argument after -command - -time delay -timeUnits units - delay is some positive integer - units is some valid time unit for dfhack.timeout(delay,timeUnits,function) - -command [ ... ] - specify the command to be run - ]]) - return -end - -if args.cancel then - repeatUtil.cancel(args.cancel) - if args.name then - repeatUtil.cancel(args.name) - end - return -end - -args.time = tonumber(args.time) -if not args.name then - args.name = args.command[1] -end - -if not args.timeUnits then - args.timeUnits = 'ticks' -end - -local callCommand = function() - dfhack.run_command(table.unpack(args.command)) -end - -repeatUtil.scheduleEvery(args.name,args.time,args.timeUnits,callCommand) - diff --git a/scripts/setfps.lua b/scripts/setfps.lua deleted file mode 100644 index 6deb68bb2..000000000 --- a/scripts/setfps.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Set the FPS cap at runtime. ---[[=begin - -setfps -====== -Run ``setfps `` to set the FPS cap at runtime, in case you want to watch -combat in slow motion or something. - -=end]] - -local cap = ... -local capnum = tonumber(cap) - -if not capnum or capnum < 1 then - qerror('Invalid FPS cap value: '..cap) -end - -df.global.enabler.fps = capnum diff --git a/scripts/show-unit-syndromes.rb b/scripts/show-unit-syndromes.rb deleted file mode 100644 index 112b33474..000000000 --- a/scripts/show-unit-syndromes.rb +++ /dev/null @@ -1,1035 +0,0 @@ -# Show syndromes affecting units, including duration -# original author: drayath, edited by expwnent -=begin - -show-unit-syndromes -=================== -Show syndromes affecting units and the remaining and maximum duration, along -with (optionally) substantial detail on the effects. - -Use one or more of the following options: - -:help: Show the help message -:showall: Show units even if not affected by any syndrome -:showeffects: Show detailed effects of each syndrome -:showdisplayeffects: Show effects that only change the look of the unit -:selected: Show selected unit -:dwarves: Show dwarves -:livestock: Show livestock -:wildanimals: Show wild animals -:hostile: Show hostiles (e.g. invaders, thieves, forgotten beasts etc) -:world: Show all defined syndromes in the world -:export: ``export:`` sends output to the given file, showing all - syndromes affecting each unit with the maximum and present duration. - -=end - -#TODO: When showing effects on a unit, show the actual change to the unit -# E.g. if +150%, +500 strength show actual total bonus based on the unit stats. -# For this also need to know -# how does size_delays affect the start/peak/end time -# how does size_dilute affect the Severity, does it also affect the phy/mental stat adjustments? -# how does peak affect the Severity, does it also affect the phy/mental stat adjustments? -#TODO: Add interaction info needs to display a bit more data, but the required structures are not yet decoded - -#TODO: Several of the unk_xxx fields have been identified here, and can get some more by comparing the raws with the printed interaction and effect information. Pass these onto the dfhack guys. - -def print_help() - puts "Use one or more of the following options:" - puts " showall: Show units even if not affected by any syndrome" - puts " showeffects: shows detailed effects of each syndrome" - puts " showdisplayeffects: show effects that only change the look of the unit" - puts " ignorehiddencurse: Hides syndromes the user should not be able to know about (TODO)" - puts " selected: Show selected unit" - puts " dwarves: Show dwarves" - puts " livestock: Show livestock" - puts " wildanimals: Show wild animals" - puts " hostile: Show hostiles (e.g. invaders, thieves, forgotten beasts etc)" - puts " world: Show all defined syndromes in the world" - puts " export: Write the output to a file instead of the console." - puts "" - puts "Will show all syndromes affecting each units with the maximum and present duration." -end - -class Output - attr_accessor :fileLogger, :indent_level - - def initialize(filename) - indent_level = "" - if filename==nil - @fileLogger = nil - else - @fileLogger = File.new(filename + ".html", "w") - @fileLogger.puts("") - end - end - - RED = "red" - GREEN = "green" - BLUE = "blue" - DEFAULT = "black" - HIGHLIGHT = "black\" size=\"+1" - - def colorize(text, color_code) - if @fileLogger == nil - return text - else - new_text = "#{text}" - if color_code == HIGHLIGHT - new_text = "" + new_text + "" - end - - return new_text - end - end - - def inactive(text) - if @fileLogger == nil - return "###" + text - else - return "#{text}" - end - end - - def indent() - if @fileLogger == nil - @indent_level = "#{@indent_level} - " - else - @fileLogger.puts("
    ") - end - end - - def unindent() - if @fileLogger == nil - @indent_level = @indent_level.chomp(" - ") - else - @fileLogger.puts("
") - end - end - - def break() - if @fileLogger == nil - puts("\n") - else - @fileLogger.puts("

") - end - end - - def close() - if @fileLogger != nil - @fileLogger.puts("") - @fileLogger.flush - @fileLogger.close - @fileLogger = nil - end - end - - def log(text, color=nil) - if @fileLogger == nil - puts("#{@indent_level}#{text}") - elsif color==nil - @fileLogger.puts(text+"
") - elsif @indent_level == "" - @fileLogger.puts(colorize(text, color)) - else - @fileLogger.puts("
  • " + colorize(text, color)+"
  • ") - end - end -end - -def get_mental_att(att_index) - - case att_index - when 0 - return "Analytical Ability" - when 1 - return "Focus" - when 2 - return "Willpower" - when 3 - return "Creativity" - when 4 - return "Intuition" - when 5 - return "Patience" - when 6 - return "Memory" - when 7 - return "Linguistics" - when 8 - return "Spacial Sense" - when 9 - return "Musicality" - when 10 - return "Kinesthetic Sense" - when 11 - return "Empathy" - when 12 - return "Social Awareness" - else - return "Unknown" - end -end - -def get_physical_att(att_index) - - case att_index - when 0 - return "strength" - when 1 - return "agility" - when 2 - return "toughness" - when 3 - return "endurance" - when 4 - return "recuperation" - when 5 - return "disease resistance" - else - return "unknown" - end -end - -def get_effect_target(target) - - values = [] - - limit = target.key.length - 1 - for i in (0..limit) - - if(target.mode[i].to_s() != "") - - items = "" - - #case target.mode[i].to_s() - #when "BY_TYPE" - # item = "type(" - #when "BY_TOKEN" - # item = "token(" - #when "BY_CATEGORY" - # item = "category(" - #end - - if(target.key[i].to_s()!="") - item = "#{item}#{target.key[i].to_s().capitalize()}" - end - - if target.tissue[i].to_s() != "ALL" - if(target.key[i].to_s()!="" and target.tissue[i].to_s()!="") - item = "#{item}:" - end - - if(target.tissue[i].to_s()!="") - item = "#{item}#{target.tissue[i].to_s().capitalize()}" - end - end - - #item = item + ")" - - values.push(item) - end - - end - - if values.length == 0 or (values.length == 1 and values[0] == "All") - return "" - else - return ", target=" + values.join(", ") - end -end - -def get_att_pairs(values, percents, physical) - - items = [] - - color = Output::DEFAULT - - limit = values.length - 1 - for i in (0..limit) - if (values[i] != 0 or percents[i] != 100) - - if physical - item = "#{get_physical_att(i)}(" - else - item = "#{get_mental_att(i)}(" - end - - if(values[i]!=0) - item = item + "%+d" % values[i] - end - - if (values[i]!=0 and percents[i]!=100) - item = item + ", " - end - - if (percents[i]!=100 or values[i]==0) - item = item + "%d" % percents[i] + "%" - end - - item = item + ")" - - if color != Output::RED and values[i] >= 0 and percents[i] > 100 - color = Output::GREEN - elsif values[i] <0 || percents[i] < 100 - color = Output::RED - end - - items.push(item) - end - end - - return items.join(", "), color -end - -def get_display_name(name, verb) - if name != nil and name != "" - return name.capitalize() - end - - if verb == nil or verb == "" - return "Mystery" - end - - if verb.length > 100 - verb = verb.slice(0, 100).capitalize() - end - - pos = verb.index(".") - if pos == nil - return verb.slice(0, verb.rindex(" ")).capitalize() - else - return verb.slice(0, pos).capitalize() - end -end - -def get_interaction(interaction) - - # name, USAGE_HINT, range, wait period are probably all we really want to show. - - #result = "a=#{interaction.unk_6c} b=#{interaction.unk_7c} c=#{interaction.unk_8c} d=#{interaction.unk_a8} e=#{interaction.unk_c4} f=#{interaction.unk_e4} " - #result = result + "g=#{interaction.unk_e0} h=#{interaction.unk_e4} i=#{interaction.unk_100} j=#{interaction.unk_11c} k=#{interaction.unk_138} l=#{interaction.unk_154} " - #result = result + "m=#{interaction.unk_170} n=#{interaction.unk_18c} o=#{interaction.unk_1a8} p=#{interaction.unk_1c4} q=#{interaction.unk_1e8} r=#{interaction.unk_25c} " - #result = result + "s=#{interaction.unk_278}" - - if interaction.name == "" - name = "mystery" - else - name = interaction.name - end - - return "ability=#{get_display_name(interaction.name, interaction.verb[0])}, delay=#{interaction.usage_delay}, actionType=TODO, range=TODO, maxTargets=TODO" -end - -def get_effect_flags(flags) - - values = [] - - if(flags.SIZE_DELAYS) then values.push("size delays") end - if(flags.SIZE_DILUTES) then values.push("size dilutes") end - if(flags.VASCULAR_ONLY) then values.push("vascular only") end - if(flags.MUSCULAR_ONLY) then values.push("muscles only") end - if(flags.RESISTABLE) then values.push("resistable") end - if(flags.LOCALIZED) then values.push("localized") end - - return values.join(",") -end - -def get_tag1_flags(logger, flags, add) - - values = [] - - good = false - bad = false - - if add - good_color = Output::GREEN - bad_color = Output::RED - else - good_color = Output::RED - bad_color = Output::GREEN - end - - if(flags.EXTRAVISION) - values.push(logger.colorize("extravision", good_color)) - good = true - end - - if(flags.OPPOSED_TO_LIFE) - values.push(logger.colorize("attack the living", bad_color)) - bad = true - end - - if(flags.NOT_LIVING) - values.push(logger.colorize("undead", Output::DEFAULT)) - end - - if(flags.NOEXERT) - values.push(logger.colorize("does not tire", good_color)) - good = true - end - - if(flags.NOPAIN) - values.push(logger.colorize("does not feel pain", good_color)) - good = true - end - - if(flags.NOBREATHE) - values.push(logger.colorize("does not breathe", good_color)) - good = true - end - - if(flags.HAS_BLOOD) - values.push(logger.colorize("has blood", Output::DEFAULT)) - end - - if(flags.NOSTUN) - values.push(logger.colorize("can't be stunned", good_color)) - good = true - end - - if(flags.NONAUSEA) - values.push(logger.colorize("does not get nausea", good_color)) - good = true - end - - if(flags.NO_DIZZINESS) - values.push(logger.colorize("does not get dizzy", good_color)) - good = true - end - - if(flags.NO_FEVERS) - values.push(logger.colorize("does not get fever", good_color)) - good = true - end - - if(flags.TRANCES) - values.push(logger.colorize("can enter trance", good_color)) - good = true - end - - if(flags.NOEMOTION) - values.push(logger.colorize("feels no emotion", good_color)) - good = true - end - - if(flags.LIKES_FIGHTING) - values.push(logger.colorize("like fighting", Output::DEFAULT)) - end - - if(flags.PARALYZEIMMUNE) - values.push(logger.colorize("can't be paralyzed", good_color)) - good = true - end - if(flags.NOFEAR) - values.push(logger.colorize("does not feel fear", good_color)) - good = true - end - - if(flags.NO_EAT) - values.push(logger.colorize("does not eat", good_color)) - good = true - end - - if(flags.NO_DRINK) - values.push(logger.colorize("does not drink", good_color)) - good = true - end - - if(flags.NO_SLEEP) - values.push(logger.colorize("does not sleep", good_color)) - good = true - end - if(flags.MISCHIEVOUS) - values.push(logger.colorize("mischievous", Output::DEFAULT)) - end - - if(flags.NO_PHYS_ATT_GAIN) - values.push(logger.colorize("physical stats cant improve", good_color)) - good = true - end - - if(flags.NO_PHYS_ATT_RUST) - values.push(logger.colorize("physical stats do not rust", good_color)) - good = true - end - - if(flags.NOTHOUGHT) - values.push(logger.colorize("stupid", bad_color)) - bad = true - end - - if(flags.NO_THOUGHT_CENTER_FOR_MOVEMENT) - values.push(logger.colorize("no brain needed to move", good_color)) - good = true - end - - if(flags.CAN_SPEAK) - values.push(logger.colorize("can speak", good_color)) - good = true - end - - if(flags.CAN_LEARN) - values.push(logger.colorize("can learn", good_color)) - good = true - end - - if(flags.UTTERANCES) - values.push(logger.colorize("utterances", Output::DEFAULT)) - end - - if(flags.CRAZED) - values.push(logger.colorize("crazed", bad_color)) - bad = true - end - - if(flags.BLOODSUCKER) - values.push(logger.colorize("drinks blood", bad_color)) - bad = true - end - - if(flags.NO_CONNECTIONS_FOR_MOVEMENT) - values.push(logger.colorize("can move without nerves", good_color)) - good = true - end - - if(flags.SUPERNATURAL) - values.push(logger.colorize("supernatural", good_color)) - good = true - end - - if add - if bad - color = Output::RED - elsif good - color = Output::GREEN - else - color = Output::DEFAULT - end - else - if good - color = Output::RED - elsif bad - color = Output::GREEN - else - color = Output::DEFAULT - end - end - - return values.join(", "), color -end - -def get_tag2_flags(logger, flags, add) - values = [] - - good = false - bad = false - - if add - good_color = Output::GREEN - bad_color = Output::RED - else - good_color = Output::RED - bad_color = Output::GREEN - end - - if(flags.NO_AGING) - good = true - values.push(logger.colorize("does not age", good_color)) - end - - if(flags.MORTAL) - bad = true - values.push(logger.colorize("mortal", bad_color)) - end - - if(flags.STERILE) - values.push(logger.colorize("can't have children", Output::DEFAULT)) - end - - if(flags.FIT_FOR_ANIMATION) - values.push(logger.colorize("can be animated", Output::DEFAULT)) - end - - if(flags.FIT_FOR_RESURRECTION) - good = true - values.push(logger.colorize("can be resurrected", Output::DEFAULT)) - end - - if add - if bad - color = Output::RED - elsif good - color = Output::GREEN - else - color = Output::DEFAULT - end - else - if good - color = Output::RED - elsif bad - color = Output::GREEN - else - color = Output::DEFAULT - end - end - - return values.join(", "), color -end - -def find_creature_name(id, casteid) - creature = df.world.raws.creatures.all.find{ |c| c.creature_id == id } - - if creature == nil - return id, casteid - end - - creature_name = creature.name[0].capitalize() - - if casteid == "DEFAULT" - return creature_name, "" - end - - caste = creature.caste.find{ |c| c.caste_id == casteid } - - if caste == nil - return creature_name, casteid - elsif creature.name[0].downcase() == caste.caste_name[0].downcase() - return creature_name, "" - else - castename = caste.caste_name[0].downcase().chomp(creature.name[0].downcase()).strip() - - if castename.start_with?(creature.name[0]) - castename = castename.slice(creature.name[0].length, castename.length - creature.name[0].length).strip() - end - - if castename.start_with?("of the") - castename = castename.slice("of the".length, castename.length - "of the".length).strip() - end - - return creature_name, castename.downcase() - end -end - -def get_effect(logger, ce, ticks, showdisplayeffects) - - flags = get_effect_flags(ce.flags) - if flags != "" - flags = " (#{flags})" - end - - if ce.end == -1 - duration = " [permanent]" - elsif ce.start >= ce.peak or ce.peak <= 1 - duration = " [#{ce.start}-#{ce.end}]" - else - duration = " [#{ce.start}-#{ce.peak}-#{ce.end}]" - end - - case ce.getType().to_s() - when "PAIN" - name = "Pain" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "SWELLING" - name = "Swelling" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "OOZING" - name = "Oozing" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "BRUISING" - name = "Bruising" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "BLISTERS" - name = "Blisters" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "NUMBNESS" - name = "Numbness" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::GREEN - when "PARALYSIS" - name = "Paralysis" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "FEVER" - name = "Fever" - desc = "power=#{ce.sev}" - color = Output::RED - when "BLEEDING" - name = "Bleeding" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "COUGH_BLOOD" - name = "Cough Blood" - desc = "power=#{ce.sev}" - color = Output::RED - when "VOMIT_BLOOD" - name = "Vomit Blood" - desc = "power=#{ce.sev}" - color = Output::RED - when "NAUSEA" - name = "Nausea" - desc = "power=#{ce.sev}" - color = Output::RED - when "UNCONSCIOUSNESS" - name = "Unconsciousness" - desc = "power=#{ce.sev}" - color = Output::RED - when "NECROSIS" - name = "Necrosis" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "IMPAIR_FUNCTION" - name = "Impairs" - desc = "power=#{ce.sev}#{get_effect_target(ce.target)}" - color = Output::RED - when "DROWSINESS" - name = "Drowsiness" - desc = "power=#{ce.sev}" - color = Output::RED - when "DIZZINESS" - name = "Dizziness" - desc = "power=#{ce.sev}" - color = Output::RED - when "ADD_TAG" - name = "Add" - tags1 = get_tag1_flags(logger, ce.tags1, true) - tags2 = get_tag2_flags(logger, ce.tags2, true) - desc = "#{tags1[0]},#{tags2[0]}" - - if tags1[1] == Output::RED || tags2[1] == Output::RED - color = Output::RED - elsif tags1[1] == Output::GREEN || tags2[1] == Output::GREEN - color = Output::GREEN - else - color = Output::DEFAULT - end - when "REMOVE_TAG" - name = "Remove" - tags1 = get_tag1_flags(logger, ce.tags1, true) - tags2 = get_tag2_flags(logger, ce.tags2, true) - desc = "#{tags1[0]},#{tags2[0]}" - - if tags1[1] == Output::RED || tags2[1] == Output::RED - color = Output::RED - elsif tags1[1] == Output::GREEN || tags2[1] == Output::GREEN - color = Output::GREEN - else - color = Output::DEFAULT - end - when "DISPLAY_TILE" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Tile" - desc = "Tile=#{ce.tile}, Colour=#{ce.color}" - color = Output::DEFAULT - when "FLASH_TILE" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Flash" - color = ce.sym_color >> 8 - tile = ce.sym_color - (color * 256) - desc = "tile = #{tile}, colour=#{color}, time=#{ce.period}, period=#{ce.time}" - color = Output::DEFAULT - when "SPEED_CHANGE" - name = "Physical" - desc = "speed(" - - value = ce.bonus_add - percent = ce.bonus_perc - if(value!=0) - desc = desc + "%+d" % value - end - - if (value!=0 and percent!=100) - desc = desc + ", " - end - - if (percent!=100 or value==0) - desc = desc + "%d" % percent + "%" - end - - desc = desc + ")" - - if value < 0 or percent < 100 - color = Output::RED - elsif value >0 or percent >100 - color = Output::GREEN - else - color = Output::DEFAULT - end - - when "CAN_DO_INTERACTION" - name = "Add interaction" - desc = "#{get_interaction(ce)}" - color = Output::GREEN - when "SKILL_ROLL_ADJUST" - name = "Skill check" - desc = "modifier=#{ce.multiplier}%, chance=#{ce.chance}%" - - if ce.multiplier > 100 - color = Output::GREEN - elsif ce.multiplier < 100 - color = Output::RED - else - color = Output::DEFAULT - end - - when "BODY_TRANSFORMATION" - name = "Transformation" - - if ce.chance > 0 - chance = ", chance=#{ce.chance} " - else - chance = "" - end - - creature_name = find_creature_name(ce.race_str, ce.caste_str) - - if creature_name[1] == "" - desc = "#{creature_name[0]}#{chance}" - else - desc = "#{creature_name[0]}(#{creature_name[1]})#{chance}" - end - - color = Output::BLUE - when "PHYS_ATT_CHANGE" - name = "Physical" - data = get_att_pairs(ce.phys_att_add, ce.phys_att_perc, true) - desc = data[0] - color = data[1] - when "MENT_ATT_CHANGE" - name = "Mental" - data = get_att_pairs(ce.ment_att_add, ce.ment_att_perc, false) - desc = data[0] - color = data[1] - when "MATERIAL_FORCE_MULTIPLIER" - name = "Material force multiplier" - desc = "received damage scaled by #{(ce.fraction_mul * 100 / ce.fraction_div * 100)/100}%" - if ce.fraction_div > ce.fraction_mul - color = Output::GREEN - elsif ce.fraction_div < ce.fraction_mul - color = Output::RED - else - color = Output::DEFAULT - end - - if ce.mat_index >=0 - mat = df.decode_mat(ce.mat_type, ce.mat_index ) - elsif ce.mat_type >= 0 - mat = df.decode_mat(ce.mat_type, 0 ) - else - mat = nil - end - - if mat!= nil - token = mat.token - if token.start_with?("INORGANIC:") - token = token.slice("INORGANIC:".length, token.length - "INORGANIC:".length) - end - - desc = "#{desc} vs #{token.capitalize()}" - end - - when "BODY_MAT_INTERACTION" - # interactionId, SundromeTriggerType - name = "Body material interaction" - desc = "a???=#{ce.unk_6c}, b???=#{ce.unk_88}, c???=#{ce.unk_8c}, d???=#{ce.unk_90}, e???=#{ce.unk_94}" - color = Output::DEFAULT - when "BODY_APPEARANCE_MODIFIER" - if !showdisplayeffects then return "", Output::DEFAULT end - # !!! seems to be missing info class !!! - # should be enum and value - name = "Body Appearance" - desc = "" - color = Output::DEFAULT - when "BP_APPEARANCE_MODIFIER" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Body part appearance" - desc = "value=#{ce.value} change_type_enum?=#{ce.unk_6c}#{get_effect_target(ce.target)}" - color = Output::DEFAULT - when "DISPLAY_NAME" - if !showdisplayeffects then return "", Output::DEFAULT end - name = "Set display name" - desc = "#{ce.name}" - color = Output::DEFAULT - else - name = "Unknown" - color = Output::HIGHLIGHT - end - - text = "#{name}#{duration}#{flags} #{desc}" - if ticks > 0 and ((ce.start > 0 and ticks < ce.start) or (ce.end > 0 and ticks > ce.end)) - text = logger.inactive(text) - end - - return text, color -end - -print_syndrome = lambda { |logger, syndrome, showeffects, showdisplayeffects| - rawsyndrome = df.world.raws.syndromes.all[syndrome.type] - duration = rawsyndrome.ce.minmax_by{ |ce| ce.end } - - if duration[0].end == -1 - durationStr = "permanent" - else - if duration[0].end == duration[1].end - durationStr = "#{syndrome.ticks} of #{duration[0].end}" - else - durationStr = "#{syndrome.ticks} of #{duration[0].end}-#{duration[1].end}" - end - end - - effects = rawsyndrome.ce.collect { |effect| get_effect(logger, effect, syndrome.ticks, showdisplayeffects) } - - if effects.any?{ |text, color| color==Output::RED } - color = Output::RED - elsif effects.any?{|text, color| color==Output::GREEN } - color = Output::GREEN - else - color = Output::DEFAULT - end - - logger.indent() - logger.log "#{get_display_name(rawsyndrome.syn_name, "")} [#{durationStr}]", color - - if showeffects - logger.indent() - effects.each{ |text, color| if text!="" then logger.log text, color end } - logger.unindent() - end - logger.unindent() -} - -print_raw_syndrome = lambda { |logger, rawsyndrome, showeffects, showdisplayeffects| - - effects = rawsyndrome.ce.collect { |effect| get_effect(logger, effect, -1, showdisplayeffects) } - - if effects.any?{ |item| item[1]==Output::RED } - color = Output::RED - elsif effects.any?{|item| item[1]==Output::GREEN } - color = Output::GREEN - else - color = Output::DEFAULT - end - - logger.indent() - logger.log get_display_name(rawsyndrome.syn_name, ""), color - - if showeffects - logger.indent() - effects.each{ |text, color| if text!="" then logger.log text, color end } - logger.unindent() - end - logger.unindent() -} - -print_syndromes = lambda { |logger, unit, showrace, showall, showeffects, showhiddencurse, showdisplayeffects| - - if showhiddencurse - syndromes = unit.syndromes.active - else - syndromes = unit.syndromes.active - # TODO: syndromes = unit.syndromes.active.select{ |s| visible_syndrome?(unit, s) } - end - - if !syndromes.empty? or showall - if showrace - logger.log "#{df.world.raws.creatures.all[unit.race].name[0]}#{unit.name == '' ? "" : ": "}#{unit.name}", Output::HIGHLIGHT - else - logger.log "#{unit.name}", Output::HIGHLIGHT - end - end - - syndromes.each { |syndrome| print_syndrome[logger, syndrome, showeffects, showdisplayeffects] } -} - -def starts_with?(str, prefix) - prefix = prefix.to_s - str[0, prefix.length] == prefix -end - -showall = false -showeffects = false -selected = false -dwarves = false -livestock = false -wildanimals = false -hostile = false -world = false -showhiddencurse = false -showdisplayeffects = false - -if $script_args.any?{ |arg| arg == "help" or arg == "?" or arg == "-?" } - print_help() -elsif $script_args.empty? - dwarves = true - showeffects = true -else - if $script_args.any?{ |arg| arg == "showall" } then showall=true end - if $script_args.any?{ |arg| arg == "showeffects" } then showeffects=true end - if $script_args.any?{ |arg| arg == "ignorehiddencurse" } then showhiddencurse=true end - if $script_args.any?{ |arg| arg == "showdisplayeffects" } then showdisplayeffects=true end - if $script_args.any?{ |arg| arg == "selected" } then selected=true end - if $script_args.any?{ |arg| arg == "dwarves" } then dwarves=true end - if $script_args.any?{ |arg| arg == "livestock" } then livestock=true end - if $script_args.any?{ |arg| arg == "wildanimals" } then wildanimals=true end - if $script_args.any?{ |arg| arg == "hostile" } then hostile=true end - if $script_args.any?{ |arg| arg == "world" } then world=true end - if $script_args.any?{ |arg| starts_with?(arg, "export:") } - exportfile = $script_args.find{ |arg| starts_with?(arg, "export:") }.gsub("export:", "") - export=true - end -end - -if export - logger = Output.new(exportfile) -else - logger = Output.new(nil) -end - -if selected - print_syndromes[logger, df.unit_find(), true, showall, showeffects, showhiddencurse, showdisplayeffects] - logger.break() -end - -if dwarves - logger.log "Dwarves", Output::HIGHLIGHT - df.unit_citizens.each { |unit| - print_syndromes[logger, unit, false, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if livestock - logger.log "Livestock", Output::HIGHLIGHT - df.world.units.active.find_all { |u| df.unit_category(u) == :Livestock }.each { |unit| - print_syndromes[logger, unit, true, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if wildanimals - logger.log "Wild animals", Output::HIGHLIGHT - df.world.units.active.find_all { |u| df.unit_category(u) == :Other and df.unit_other_category(u) == :Wild }.each { |unit| - print_syndromes[logger, unit, true, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if hostile - logger.log "Hostile units", Output::HIGHLIGHT - df.unit_hostiles.each { |unit| - print_syndromes[logger, unit, true, showall, showeffects, showhiddencurse, showdisplayeffects] - } - logger.break() -end - -if world - logger.log "All syndromes", Output::HIGHLIGHT - df.world.raws.syndromes.all.each { |syndrome| print_raw_syndrome[logger, syndrome, showeffects, showdisplayeffects] } -end - -logger.close() diff --git a/scripts/siren.lua b/scripts/siren.lua deleted file mode 100644 index 5bf821141..000000000 --- a/scripts/siren.lua +++ /dev/null @@ -1,136 +0,0 @@ --- Wakes up the sleeping, ends breaks and parties ---[[=begin - -siren -===== -Wakes up sleeping units, cancels breaks and stops parties either everywhere, -or in the burrows given as arguments. In return, adds bad thoughts about -noise, tiredness and lack of protection. Also, the units with interrupted -breaks will go on break again a lot sooner. The script is intended for -emergencies, e.g. when a siege appears, and all your military is partying. - -=end]] - -local utils = require 'utils' - -local args = {...} -local burrows = {} -local bnames = {} - -if not dfhack.isMapLoaded() then - qerror('Map is not loaded.') -end - -for _,v in ipairs(args) do - local b = dfhack.burrows.findByName(v) - if not b then - qerror('Unknown burrow: '..v) - end - table.insert(bnames, v) - table.insert(burrows, b) -end - -local in_siege = false - -function is_in_burrows(pos) - if #burrows == 0 then - return true - end - for _,v in ipairs(burrows) do - if dfhack.burrows.isAssignedTile(v, pos) then - return true - end - end -end - -function add_thought(unit, emotion, thought) - unit.status.current_soul.personality.emotions:insert('#', { new = true, - type = emotion, - unk2=1, - strength=1, - thought=thought, - subthought=0, - severity=0, - flags=0, - unk7=0, - year=df.global.cur_year, - year_tick=df.global.cur_year_tick}) -end - -function wake_unit(unit) - local job = unit.job.current_job - if not job or job.job_type ~= df.job_type.Sleep then - return - end - - if job.completion_timer > 0 then - unit.counters.unconscious = 0 - add_thought(unit, df.emotion_type.Grouchiness, df.unit_thought_type.Drowsy) - elseif job.completion_timer < 0 then - add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) - end - - job.pos:assign(unit.pos) - - job.completion_timer = 0 - - unit.path.dest:assign(unit.pos) - unit.path.path.x:resize(0) - unit.path.path.y:resize(0) - unit.path.path.z:resize(0) - - unit.counters.job_counter = 0 -end - -function stop_break(unit) - local counter = dfhack.units.getMiscTrait(unit, df.misc_trait_type.OnBreak) - if counter then - counter.id = df.misc_trait_type.TimeSinceBreak - counter.value = 100800 - 30*1200 - add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) - end -end - --- Check siege for thought purpose -for _,v in ipairs(df.global.ui.invasions.list) do - if v.flags.siege and v.flags.active then - in_siege = true - break - end -end - --- Stop rest -for _,v in ipairs(df.global.world.units.active) do - local x,y,z = dfhack.units.getPosition(v) - if x and dfhack.units.isCitizen(v) and is_in_burrows(xyz2pos(x,y,z)) then - if not in_siege and v.military.squad_id < 0 then - add_thought(v, df.emotion_type.Nervousness, df.unit_thought_type.LackProtection) - end - wake_unit(v) - stop_break(v) - end -end - --- Stop parties -for _,v in ipairs(df.global.ui.parties) do - local pos = utils.getBuildingCenter(v.location) - if is_in_burrows(pos) then - v.timer = 0 - for _, u in ipairs(v.units) do - add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) - end - end -end - -local place = 'the halls and tunnels' -if #bnames > 0 then - if #bnames == 1 then - place = bnames[1] - else - place = table.concat(bnames,', ',1,#bnames-1)..' and '..bnames[#bnames] - end -end -dfhack.gui.showAnnouncement( - 'A loud siren sounds throughout '..place..', waking the sleeping and startling the awake.', - COLOR_BROWN, true -) diff --git a/scripts/source.rb b/scripts/source.rb deleted file mode 100644 index e9d7a64f3..000000000 --- a/scripts/source.rb +++ /dev/null @@ -1,114 +0,0 @@ -# create an infinite source/drain of magma/water -=begin - -source -====== -Create an infinite magma or water source or drain on a tile. -For more complex commands, try the `liquids` plugin. - -This script registers a map tile as a liquid source, and every 12 game ticks -that tile receives or remove 1 new unit of flow based on the configuration. - -Place the game cursor where you want to create the source (must be a -flow-passable tile, and not too high in the sky) and call:: - - source add [magma|water] [0-7] - -The number argument is the target liquid level (0 = drain, 7 = source). - -To add more than 1 unit everytime, call the command again on the same spot. - -To delete one source, place the cursor over its tile and use ``source delete``. -To remove all existing sources, call ``source clear``. - -The ``list`` argument shows all existing sources. - -Examples:: - - source add water - water source - source add magma 7 - magma source - source add water 0 - water drain - -=end - -$sources ||= [] - -cur_source = { - :liquid => 'water', - :amount => 7, - :pos => [df.cursor.x, df.cursor.y, df.cursor.z] -} -cmd = 'help' - -$script_args.each { |a| - case a.downcase - when 'water', 'magma' - cur_source[:liquid] = a.downcase - when /^\d+$/ - cur_source[:amount] = a.to_i - when 'add', 'del', 'delete', 'clear', 'help', 'list' - cmd = a.downcase - else - puts "source: unhandled argument #{a}" - end -} - -case cmd -when 'add' - $sources_onupdate ||= df.onupdate_register('sources', 12) { - # called every 12 game ticks (100x a dwarf day) - $sources.each { |s| - if tile = df.map_tile_at(*s[:pos]) and tile.shape_passableflow - # XXX does not check current liquid_type - des = tile.designation - cur = des.flow_size - if cur != s[:amount] - tile.spawn_liquid((cur > s[:amount] ? cur-1 : cur+1), s[:liquid] == 'magma') - end - end - } - if $sources.empty? - df.onupdate_unregister($sources_onupdate) - $sources_onupdate = nil - end - } - - if cur_source[:pos][0] >= 0 - if tile = df.map_tile_at(*cur_source[:pos]) - if tile.shape_passableflow - $sources << cur_source - else - puts "Impassable tile: I'm afraid I can't do that, Dave" - end - else - puts "Unallocated map block - build something here first" - end - else - puts "Please put the game cursor where you want a source" - end - -when 'del', 'delete' - $sources.delete_if { |s| s[:pos] == cur_source[:pos] } - -when 'clear' - $sources.clear - -when 'list' - puts "Source list:", $sources.map { |s| - " #{s[:pos].inspect} #{s[:liquid]} #{s[:amount]}" - } - puts "Current cursor pos: #{[df.cursor.x, df.cursor.y, df.cursor.z].inspect}" if df.cursor.x >= 0 - -else - puts <= 3 and args[3]:sub(1, 1) ~= '-' then - extend(new_args, {'-nick', args[3]}) - start = 4 - end - for i = start, #args do - table.insert(new_args, args[i]) - end -end -if show_command then - print('modtools/create-unit ' .. table.concat(new_args, ' ')) - return -end -dfhack.run_script('modtools/create-unit', table.unpack(new_args)) diff --git a/scripts/startdwarf.rb b/scripts/startdwarf.rb deleted file mode 100644 index a592d5391..000000000 --- a/scripts/startdwarf.rb +++ /dev/null @@ -1,20 +0,0 @@ -# patch start dwarf count -=begin - -startdwarf -========== -Use at the embark screen to embark with the specified number of dwarves. Eg. -``startdwarf 500`` would lead to a severe food shortage and FPS issues, while -``startdwarf 10`` would just allow a few more warm bodies to dig in. -The number must be 7 or greater. - -=end - -nr = $script_args[0].to_i - -raise 'too low' if nr < 7 - -addr = df.get_global_address('start_dwarf_count') -raise 'patch address not available' if addr == 0 -df.memory_patch(addr, [nr].pack('L')) - diff --git a/scripts/starvingdead.rb b/scripts/starvingdead.rb deleted file mode 100644 index 7ee985a7b..000000000 --- a/scripts/starvingdead.rb +++ /dev/null @@ -1,92 +0,0 @@ -# Weaken and eventually destroy undead over time -=begin - -starvingdead -============ -Somewhere between a "mod" and a "fps booster", with a small impact on -vanilla gameplay. It mostly helps prevent undead cascades in the caverns, -where constant combat leads to hundreds of undead roaming the -caverns and destroying your FPS. - -With this script running, all undead that have been on the map for -one month gradually decay, losing strength, speed, and toughness. -After six months, they collapse upon themselves, never to be reanimated. - -Usage: ``starvingdead (start|stop)`` - -=end - -class StarvingDead - - def initialize - @threshold = 1 - @die_threshold = 6 - end - - def process - return false unless @running - month_length = 67200 - if (@undead_count >= 25) - month_length *= 25 / @undead_count - end - - @undead_count = 0 - df.world.units.active.each { |u| - if (u.enemy.undead and not u.flags1.dead) - @undead_count += 1 - if (u.curse.time_on_site > month_length * @threshold) - u.body.physical_attrs.each { |att| - att.value = att.value - (att.value * 0.02) - } - end - - if (u.curse.time_on_site > month_length * @die_threshold) - u.flags1.dead = true - u.curse.rem_tags2.FIT_FOR_ANIMATION = true - end - end - } - end - - def start - @onupdate = df.onupdate_register('starvingdead', 1200, 1200) { process } - @running = true - @undead_count = 0 - - if ($script_args[1] and $script_args[1].gsub(/[^0-9\.]/,'').to_f > 0) - @threshold = $script_args[1].gsub(/[^0-9\.]/,'').to_f - end - - if ($script_args[2] and $script_args[2].gsub(/[^0-9\.]/,'').to_f > 0) - @die_threshold = $script_args[2].gsub(/[^0-9\.]/,'').to_f - end - - puts "Starving Dead starting...weakness starts at #{@threshold} months, true death at #{@die_threshold} months" - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - def status - @running ? 'Running.' : 'Stopped.' - end -end - -case $script_args[0] -when 'start' - if ($StarvingDead) - $StarvingDead.stop - end - $StarvingDead = StarvingDead.new - $StarvingDead.start - -when 'end', 'stop' - $StarvingDead.stop -else - if $StarvingDead - puts $StarvingDead.status - else - puts 'Not loaded.' - end -end diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb deleted file mode 100644 index 8cb1a0fa4..000000000 --- a/scripts/stripcaged.rb +++ /dev/null @@ -1,227 +0,0 @@ -# mark stuff inside of cages for dumping. -=begin - -stripcaged -========== -For dumping items inside cages. Will mark selected items for dumping, then -a dwarf may come and actually dump them (or you can use `autodump`). - -Arguments: - -:list: display the list of all cages and their item content on the console -:items: dump items in the cage, excluding stuff worn by caged creatures -:weapons: dump equipped weapons -:armor: dump everything worn by caged creatures (including armor and clothing) -:all: dump everything in the cage, on a creature or not - -Without further arguments, all commands work on all cages and animal traps on -the map. With the ``here`` argument, considers only the in-game selected cage -(or the cage under the game cursor). To target only specific cages, you can -alternatively pass cage IDs as arguments:: - - stripcaged weapons 25321 34228 - -=end - -def plural(nr, name) - # '1 cage' / '4 cages' - "#{nr} #{name}#{'s' if nr > 1}" -end - -def cage_dump_items(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) - next if ref.item_tg.flags.dump - count += 1 - ref.item_tg.flags.dump = true - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_armor(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) - ref.unit_tg.inventory.each { |it| - next if it.mode != :Worn - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_weapons(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) - ref.unit_tg.inventory.each { |it| - next if it.mode != :Weapon - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_all(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - case ref - when DFHack::GeneralRefContainsItemst - next if ref.item_tg.flags.dump - count += 1 - ref.item_tg.flags.dump = true - when DFHack::GeneralRefContainsUnitst - ref.unit_tg.inventory.each { |it| - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - end - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" -end - - -def cage_dump_list(list) - count_total = Hash.new(0) - empty_cages = 0 - list.each { |cage| - count = Hash.new(0) - - cage.general_refs.each { |ref| - case ref - when DFHack::GeneralRefContainsItemst - count[ref.item_tg._rtti_classname] += 1 - when DFHack::GeneralRefContainsUnitst - ref.unit_tg.inventory.each { |it| - count[it.item._rtti_classname] += 1 - } - # TODO vermin ? - else - puts "unhandled ref #{ref.inspect}" if $DEBUG - end - } - - type = case cage - when DFHack::ItemCagest; 'Cage' - when DFHack::ItemAnimaltrapst; 'Animal trap' - else cage._rtti_classname - end - - if count.empty? - empty_cages += 1 - else - puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } - end - - count.each { |k, v| count_total[k] += v } - } - - if list.length > 2 - puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } - puts "with #{plural(empty_cages, 'empty cage')}" - end -end - - -# handle magic script arguments -here_only = $script_args.delete 'here' -if here_only - it = df.item_find - list = [it] - if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) - list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) } - end - if list.empty? - puts 'Please select a cage' - throw :script_finished - end - -elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first - list = [] - ids.each { |id| - $script_args.delete id - if not it = df.item_find(id.to_i) - puts "Invalid item id #{id}" - elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) - puts "Item ##{id} is not a cage" - list << it - else - list << it - end - } - if list.empty? - puts 'Please use a valid cage id' - throw :script_finished - end - -else - list = df.world.items.other[:ANY_CAGE_OR_TRAP] -end - - -# act -case $script_args[0] -when /^it/i - cage_dump_items(list) -when /^arm/i - cage_dump_armor(list) -when /^wea/i - cage_dump_weapons(list) -when 'all' - cage_dump_all(list) -when 'list' - cage_dump_list(list) -else - puts < 10000 - u.counters2.sleepiness_timer = 1 - end - - # no break - if b = u.status.misc_traits.find { |t| t.id == :OnBreak } - b.value = 500_000 - end - else - $superdwarf_ids.delete id - end - } - end - } - else - puts "Select a creature using 'v'" - end - -when 'del' - if u = df.unit_find - $superdwarf_ids.delete u.id - else - puts "Select a creature using 'v'" - end - -when 'clear' - $superdwarf_ids.clear - -when 'list' - puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } - -else - puts "Usage:", - " - superdwarf add: give superspeed to currently selected creature", - " - superdwarf del: remove superspeed to current creature", - " - superdwarf clear: remove all superpowers", - " - superdwarf list: list super-dwarves" -end diff --git a/scripts/teleport.lua b/scripts/teleport.lua deleted file mode 100644 index 8c657061c..000000000 --- a/scripts/teleport.lua +++ /dev/null @@ -1,58 +0,0 @@ --- teleports a unit to a location --- author Putnam --- edited by expwnent ---[[=begin - -teleport -======== -Teleports a unit to given coordinates. Examples: - -:teleport -showunitid: prints unitid beneath cursor -:teleport -showpos: prints coordinates beneath cursor -:teleport -unit 1234 -x 56 -y 115 -z 26: - teleports unit 1234 to 56,115,26 - -=end]] - -function teleport(unit,pos) - local unitoccupancy = dfhack.maps.getTileBlock(unit.pos).occupancy[unit.pos.x%16][unit.pos.y%16] - local newoccupancy = dfhack.maps.getTileBlock(pos).occupancy[pos.x%16][pos.y%16] - if newoccupancy.unit then - unit.flags1.on_ground=true - end - unit.pos.x = pos.x - unit.pos.y = pos.y - unit.pos.z = pos.z - if not unit.flags1.on_ground then unitoccupancy.unit = false else unitoccupancy.unit_grounded = false end -end - -utils = require('utils') - -validArgs = validArgs or utils.invert({ - 'unit', - 'x', - 'y', - 'z', - 'showunitid', - 'showpos' -}) - -if moduleMode then - return -end - -local args = utils.processArgs({...}, validArgs) - -if args.showunitid or args.showpos then - if args.showunitid then - print(dfhack.gui.getSelectedUnit(true).id) - else - printall(df.global.cursor) - end -else - local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) - local pos = not(not args.x or not args.y or not args.z) and {x=args.x,y=args.y,z=args.z} or {x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} - if not unit then qerror('A unit needs to be selected or specified. Use teleport -showunitid to get a unit\'s ID.') end - if not pos.x or pos.x==-30000 then qerror('A position needs to be highlighted or specified. Use teleport -showpos to get a position\'s exact xyz values.') end - teleport(unit,pos) -end diff --git a/scripts/tidlers.lua b/scripts/tidlers.lua deleted file mode 100644 index 7df509139..000000000 --- a/scripts/tidlers.lua +++ /dev/null @@ -1,10 +0,0 @@ ---Toggle idlers count ---[[=begin - -tidlers -======= -Toggle between all possible positions where the idlers count can be placed. - -=end]] -df.global.d_init.idlers = df.d_init_idlers:next_item(df.global.d_init.idlers) -print('Toggled the display of idlers.') diff --git a/scripts/twaterlvl.lua b/scripts/twaterlvl.lua deleted file mode 100644 index 50f487686..000000000 --- a/scripts/twaterlvl.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Toggle display of water depth ---[[=begin - -twaterlvl -========= -Toggle between displaying/not displaying liquid depth as numbers. - -=end]] - -df.global.d_init.flags1.SHOW_FLOW_AMOUNTS = not df.global.d_init.flags1.SHOW_FLOW_AMOUNTS -print('Water level display toggled.') diff --git a/scripts/undump-buildings.lua b/scripts/undump-buildings.lua deleted file mode 100644 index f4cfe7909..000000000 --- a/scripts/undump-buildings.lua +++ /dev/null @@ -1,36 +0,0 @@ --- Undesignates building base materials for dumping. ---[[=begin - -undump-buildings -================ -Undesignates building base materials for dumping. - -=end]] - -function undump_buildings() - local buildings = df.global.world.buildings.all - local undumped = 0 - for i = 0, #buildings - 1 do - local building = buildings[i] - -- Zones and stockpiles don't have the contained_items field. - if df.building_actual:is_instance(building) then - local items = building.contained_items - for j = 0, #items - 1 do - local contained = items[j] - if contained.use_mode == 2 and contained.item.flags.dump then - -- print(building, contained.item) - undumped = undumped + 1 - contained.item.flags.dump = false - end - end - end - end - - if undumped > 0 then - local s = "s" - if undumped == 1 then s = "" end - print("Undumped "..undumped.." item"..s..".") - end -end - -undump_buildings() diff --git a/scripts/unsuspend.rb b/scripts/unsuspend.rb deleted file mode 100644 index 666969359..000000000 --- a/scripts/unsuspend.rb +++ /dev/null @@ -1,28 +0,0 @@ -# un-suspend construction jobs, one time only -# same as "resume all" -=begin - -unsuspend -========= -Unsuspend jobs in workshops, on a one-off basis. See `autounsuspend` -for regular use. - -=end - -joblist = df.world.job_list.next -count = 0 - -while joblist - job = joblist.item - joblist = joblist.next - - if job.job_type == :ConstructBuilding - if (job.flags.suspend && job.items && job.items[0]) - item = job.items[0].item - job.flags.suspend = false - count += 1 - end - end -end - -puts "Unsuspended #{count} job(s)." diff --git a/scripts/view-item-info.lua b/scripts/view-item-info.lua deleted file mode 100644 index 39614d3bf..000000000 --- a/scripts/view-item-info.lua +++ /dev/null @@ -1,407 +0,0 @@ --- Extended Item Viewscreens --- Shows information on material properties, weapon or armour stats, and more. --- By PeridexisErrant, adapted from nb_item_info by Raidau ---@ enable = true -local help = [[=begin - -view-item-info -============== -A script to extend the item or unit viewscreen with additional information -including a custom description of each item (when available), and properties -such as material statistics, weapon attacks, armor effectiveness, and more. - -The associated script `item-descriptions`.lua supplies custom descriptions -of items. Individual descriptions can be added or overridden by a similar -script :file:`raw/scripts/more-item-descriptions.lua`. Both work as sparse lists, -so missing items simply go undescribed if not defined in the fallback. - -=end]] - -function isInList(list, item, helper) - if not helper then - helper = function(v) return v end - end - for k,v in pairs (list) do - if item == helper(v) then - return true - end - end -end - -if dfhack_flags and dfhack_flags.enable then - enabled = dfhack_flags.enable_state -end - -local args = {...} -local lastframe = df.global.enabler.frame_last -if isInList(args, "help") or isInList(args, "?") then - print(help) - return -elseif isInList(args, "enable") then - enabled = true -elseif isInList(args, "disable") then - enabled = false -end - -function append (list, str, indent) - local str = str or " " - local indent = indent or 0 - table.insert(list, {str, indent * 4}) -end - -function add_lines_to_list(t1,t2) - for i=1,#t2 do - t1[#t1+1] = t2[i] - end - return t1 -end - -function GetMatPlant (item) - if dfhack.matinfo.decode(item).mode == "plant" then - return dfhack.matinfo.decode(item).plant - end -end - -function get_textid (item) - return string.match(tostring(item._type),""):upper() -end - -function compare_iron (mat_prop, iron_prop) - return " "..mat_prop.." ("..math.floor(mat_prop/iron_prop*100).."% of iron)" -end - -function GetMatPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - local deg_U = item.temperature.whole - local deg_C = math.floor((deg_U-10000)*5/9) - append(list,"Temperature: "..deg_C.."\248C ("..deg_U.."U)") - append(list,"Color: "..df.global.world.raws.language.colors[mat.state_color.Solid].name) - local function GetStrainDescription (number) - if tonumber(number) < 1 then return "crystalline" - elseif tonumber(number) < 1000 then return "very stiff" - elseif tonumber(number) < 5001 then return "stiff" - elseif tonumber(number) < 15001 then return "medium" - elseif tonumber(number) < 50000 then return "elastic" - elseif tonumber(number) >= 50000 then return "very elastic" - else return "unknown" end - end - local mat_properties_for = {"BAR", "SMALLGEM", "BOULDER", "ROUGH", - "WOOD", "GEM", "ANVIL", "THREAD", "SHOES", "CLOTH", "ROCK", "WEAPON", "TRAPCOMP", - "ORTHOPEDIC_CAST", "SIEGEAMMO", "SHIELD", "PANTS", "HELM", "GLOVES", "ARMOR", "AMMO"} - if isInList(mat_properties_for, get_textid (item)) then - append(list,"Material name: "..mat.state_name.Solid) - append(list,"Material properties: ") - append(list,"Solid density: "..mat.solid_density..'kg/m^3',1) - local maxedge = mat.strength.max_edge - append(list,"Maximum sharpness: "..maxedge.." ("..maxedge/standard.strength.max_edge*100 .."%)",1) - if mat.molar_mass > 0 then - append(list,"Molar mass: "..mat.molar_mass,1) - end - append(list,"Shear strength:",1) - append(list, "yield:"..compare_iron(mat.strength.yield.SHEAR, standard.strength.yield.SHEAR), 2) - append(list, "fracture:"..compare_iron(mat.strength.fracture.SHEAR, standard.strength.fracture.SHEAR), 2) - local s_strain = mat.strength.strain_at_yield.SHEAR - append(list, "elasticity: "..s_strain.." ("..GetStrainDescription(s_strain)..")", 2) - append(list,"Impact strength:",1) - append(list, "yield:"..compare_iron(mat.strength.yield.IMPACT, standard.strength.yield.IMPACT), 2) - append(list, "fracture:"..compare_iron(mat.strength.fracture.IMPACT, standard.strength.fracture.IMPACT), 2) - local i_strain = mat.strength.strain_at_yield.IMPACT - append(list, "elasticity: "..i_strain.." ("..GetStrainDescription(i_strain)..")", 2) - end - return list -end - -function GetArmorPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - append(list,"Armor properties: ") - append(list,"Thickness: "..item.subtype.props.layer_size,1) - append(list,"Coverage: "..item.subtype.props.coverage.."%",1) - append(list,"Fit for "..df.creature_raw.find(item.maker_race).name[0],1) - return list -end - -function GetShieldPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - append(list,"Shield properties:") - append(list,"Base block chance: "..item.subtype.blockchance,1) - append(list,"Fit for "..df.creature_raw.find(item.maker_race).name[0],1) - return list -end - -function GetWeaponPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - if item._type == df.item_toolst and #item.subtype.attacks < 1 then - return list - end - append(list,"Weapon properties: ") - if item.sharpness > 0 then - append(list,"Sharpness:"..compare_iron(item.sharpness, standard.strength.max_edge),1) - else - append(list,"Not edged",1) - end - if string.len(item.subtype.ranged_ammo) > 0 then - append(list,"Ranged weapon",1) - append(list,"Ammo: "..item.subtype.ranged_ammo:lower(),2) - if item.subtype.shoot_force > 0 then - append(list,"Shoot force: "..item.subtype.shoot_force,2) - end - if item.subtype.shoot_maxvel > 0 then - append(list,"Maximum projectile velocity: "..item.subtype.shoot_maxvel,2) - end - end - append(list,"Required size: "..item.subtype.minimum_size*10,1) - if item.subtype.two_handed*10 > item.subtype.minimum_size*10 then - append(list,"Used as 2-handed until unit size: "..item.subtype.two_handed*10,2) - end - append(list,"Attacks: ",1) - for k,attack in pairs (item.subtype.attacks) do - local name = attack.verb_2nd - if attack.noun ~= "NO_SUB" then - name = name.." with "..attack.noun - end - if attack.edged then - name = name.." (edged)" - else - name = name.." (blunt)" - end - append(list,name,2) - append(list,"Contact area: "..attack.contact,3) - if attack.edged then - append(list,"Penetration: "..attack.penetration,3) - end - append(list,"Velocity multiplier: "..attack.velocity_mult/1000,3) - if attack.flags.bad_multiattack then - append(list,"Bad multiattack",3) - end - append(list,"Prepare "..attack.prepare.." / recover "..attack.recover,3) - end - return list -end - -function GetAmmoPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {} - if item._type == df.item_toolst and #item.subtype.attacks < 1 then - return list - end - append(list,"Ammo properties: ") - if item.sharpness > 0 then - append(list,"Sharpness: "..item.sharpness,1) - else - append(list,"Not edged",1) - end - append(list,"Attacks: ", 1) - for k,attack in pairs (item.subtype.attacks) do - local name = attack.verb_2nd - if attack.noun ~= "NO_SUB" then - name = name.." with "..attack.noun - end - if attack.edged then - name = name.." (edged)" - else - name = name.." (blunt)" - end - append(list,name,2) - append(list,"Contact area: "..attack.contact,3) - if attack.edged then - append(list,"Penetration: "..attack.penetration,3) - end - append(list,"Velocity multiplier: "..attack.velocity_mult/1000,3) - if attack.flags.bad_multiattack then - append(list,"Bad multiattack",3) - end - append(list,"Prepare "..attack.prepare.." / recover "..attack.recover,3) - end - return list -end - -function edible_string (mat) - local edible_string = "Edible" - if mat.flags.EDIBLE_RAW or mat.flags.EDIBLE_COOKED then - if mat.flags.EDIBLE_RAW then - edible_string = edible_string.." raw" - if mat.flags.EDIBLE_COOKED then - edible_string = edible_string.." and cooked" - end - elseif mat.flags.EDIBLE_COOKED then - edible_string = edible_string.." only when cooked" - end - else - edible_string = "Not edible" - end - return edible_string -end - -function GetReactionProduct (inmat, reaction) - for k,v in pairs (inmat.reaction_product.id) do - if v.value == reaction then - return {inmat.reaction_product.material.mat_type[k], - inmat.reaction_product.material.mat_index[k]} - end - end -end - -function add_react_prod (list, mat, product, str) - local mat_type, mat_index = GetReactionProduct (mat, product) - if mat_type then - local result = dfhack.matinfo.decode(mat_type, mat_index).material.state_name.Liquid - append(list, str..result) - end -end - -function get_plant_reaction_products (mat) - local list = {} - add_react_prod (list, mat, "GROWTH_JUICE_PROD", "Pressed into ") - add_react_prod (list, mat, "PRESS_LIQUID_MAT", "Pressed into ") - add_react_prod (list, mat, "LIQUID_EXTRACTABLE", "Extractable product: ") - add_react_prod (list, mat, "WATER_SOLUTION_PROD", "Can be mixed with water to make ") - add_react_prod (list, mat, "RENDER_MAT", "Rendered into ") - add_react_prod (list, mat, "SOAP_MAT", "Used to make soap") - if GetReactionProduct (mat, "SUGARABLE") then - append(list,"Used to make sugar") - end - if GetReactionProduct (mat, "MILLABLE") or - GetReactionProduct (mat, "GRAIN_MILLABLE") or - GetReactionProduct (mat, "GROWTH_MILLABLE") then - append(list,"Can be milled") - end - if GetReactionProduct(mat, "GRAIN_THRESHABLE") then - append(list,"Grain can be threshed") - end - if GetReactionProduct (mat, "CHEESE_MAT") then - local mat_type, mat_index = GetReactionProduct (mat, "CHEESE_MAT") - append(list,"Used to make "..dfhack.matinfo.decode(mat_type, mat_index).material.state_name.Solid) - end - return list -end - -function GetFoodPropertiesStringList (item) - local mat = dfhack.matinfo.decode(item).material - local list = {{" ", 0}} - append(list,edible_string(mat)) - if item._type == df.item_foodst then - append(list,"This is prepared meal") - return list - end - if mat == dfhack.matinfo.find ("WATER") then - append(list,"Water is drinkable") - return list - end - add_lines_to_list(list, get_plant_reaction_products(mat)) - if item._type == df.item_plantst and GetMatPlant (item) then - local plant = GetMatPlant (item) - for k,v in pairs (plant.material_defs) do - if v ~= -1 and k:find("type_") and not (k:find("type_basic") - or k:find("type_seed") or k:find("type_tree")) then - local targetmat = dfhack.matinfo.decode (v, - plant.material_defs["idx_"..k:match("type_(.+)")]) - local state = "Liquid" - local describe = "Made into " - if k:find("type_mill") - then state = "Powder" describe = "Ground into " - elseif k:find("type_thread") - then state = "Solid" describe = "Woven into " - elseif k:find("type_drink") - then describe = "Brewed into " - elseif k:find("type_extract_barrel") - then describe = "Cask-aged into " - elseif k:find("type_extract_vial") - then describe = "Refined into vials of " - elseif k:find("type_extract_still_vial") - then describe = "Distilled into vials of " end - local st_name = targetmat.material.state_name[state] - append(list,describe..targetmat.material.prefix..''..st_name) - end - end - end - return list -end - -function get_all_uses_strings (item) - local all_lines = {} - local FoodsAndPlants = {df.item_meatst, df.item_globst, - df.item_plantst, df.item_plant_growthst, df.item_liquid_miscst, - df.item_powder_miscst, df.item_cheesest, df.item_foodst} - local ArmourTypes = {df.item_armorst, df.item_pantsst, - df.item_helmst, df.item_glovesst, df.item_shoesst} - if df.global.gamemode == df.game_mode.ADVENTURE and item ~= "COIN" then - add_lines_to_list(all_lines, {{"Value: "..dfhack.items.getValue(item),0}}) - elseif isInList(ArmourTypes, item._type) then - add_lines_to_list(all_lines, GetArmorPropertiesStringList(item)) - elseif item._type == df.item_weaponst or item._type == df.item_toolst then - add_lines_to_list(all_lines, GetWeaponPropertiesStringList(item)) - elseif item._type == df.item_ammost then - add_lines_to_list(all_lines, GetAmmoPropertiesStringList(item)) - elseif item._type == df.item_shieldst then - add_lines_to_list(all_lines, GetShieldPropertiesStringList(item)) - elseif item._type == df.item_seedsst then - local str = math.floor(GetMatPlant(item).growdur/12).." days" - add_lines_to_list(all_lines, {{"Growth time: "..str, 0}}) - elseif isInList(FoodsAndPlants, item._type) then - add_lines_to_list(all_lines, GetFoodPropertiesStringList(item)) - end - if not dfhack.items.isCasteMaterial(df.item_type[get_textid(item)]) then - add_lines_to_list(all_lines, GetMatPropertiesStringList(item)) - end - return all_lines -end - -function get_custom_item_desc (item) - local desc - local ID = get_textid (item) - if ID and dfhack.items.getSubtypeCount(df.item_type[ID]) ~= -1 then - ID = item.subtype.id end - if not ID then return nil end - if dfhack.findScript("item-descriptions") then - desc = dfhack.script_environment("item-descriptions").descriptions[ID] - end - if dfhack.findScript("more-item-descriptions") then - desc = dfhack.script_environment("more-item-descriptions").descriptions[ID] or desc - end - if desc then add_lines_to_list(desc, {""}) end - return desc -end - -function AddUsesString (viewscreen,line,indent) - local str = df.new("string") - str.value = tostring(line) - local indent = indent or 0 - viewscreen.entry_ref:insert('#', nil) - viewscreen.entry_indent:insert('#', indent) - viewscreen.unk_34:insert('#', nil) -- TODO: get this into structures, and fix usage! - viewscreen.entry_string:insert('#', str) - viewscreen.entry_reaction:insert('#', -1) -end - -function dfhack.onStateChange.item_info (code) - vi_label = 'More information (DFHack):' - if not enabled then return end - if code == SC_VIEWSCREEN_CHANGED and dfhack.isWorldLoaded() then - standard = dfhack.matinfo.find("INORGANIC:IRON").material - if not standard then return end - local scr = dfhack.gui.getCurViewscreen() - if scr._type == df.viewscreen_itemst then - if isInList(scr.entry_string, vi_label, function(v) return v.value end) then - return - end - if #scr.entry_string > 0 then - AddUsesString(scr, '') - end - AddUsesString(scr, vi_label) - AddUsesString(scr, '') - local description = get_custom_item_desc (scr.item) or "" - for i = 1, #description do - AddUsesString(scr,description[i]) - end - local all_lines = get_all_uses_strings(scr.item) - for i = 1, #all_lines do - AddUsesString(scr,all_lines[i][1],all_lines[i][2]) - end - scr.caption_uses = true - end - end -end diff --git a/scripts/warn-starving.lua b/scripts/warn-starving.lua deleted file mode 100644 index 12334bf99..000000000 --- a/scripts/warn-starving.lua +++ /dev/null @@ -1,140 +0,0 @@ --- Pause and warn if a unit is starving --- By Meneth32, PeridexisErrant, Lethosor ---@ module = true ---[[=begin - -warn-starving -============= -If any (live) units are starving, very thirsty, or very drowsy, the game will -be paused and a warning shown and logged to the console. Use with the -`repeat` command for regular checks. - -Use ``warn-starving all`` to display a list of all problematic units. - -=end]] - -starvingUnits = starvingUnits or {} -dehydratedUnits = dehydratedUnits or {} -sleepyUnits = sleepyUnits or {} - -function clear() - starvingUnits = {} - dehydratedUnits = {} - sleepyUnits = {} -end - -local gui = require 'gui' -local utils = require 'utils' -local units = df.global.world.units.active - -local args = utils.invert({...}) -if args.all or args.clear then - clear() -end - -warning = defclass(warning, gui.FramedScreen) -warning.ATTRS = { - frame_style = gui.GREY_LINE_FRAME, - frame_title = 'Warning', - frame_width = 20, - frame_height = 18, - frame_inset = 1, - focus_path = 'warn-starving', -} - -function warning:init(args) - self.start = 1 - self.messages = args.messages - self.frame_height = math.min(18, #self.messages) - self.max_start = #self.messages - self.frame_height + 1 - for _, msg in pairs(self.messages) do - self.frame_width = math.max(self.frame_width, #msg + 2) - end - self.frame_width = math.min(df.global.gps.dimx - 2, self.frame_width) -end - -function warning:onRenderBody(p) - for i = self.start, math.min(self.start + self.frame_height - 1, #self.messages) do - p:string(self.messages[i]):newline() - end - if #self.messages > self.frame_height then - if self.start > 1 then - p:seek(self.frame_width - 1, 0):string(string.char(24), COLOR_LIGHTCYAN) -- up - end - if self.start < self.max_start then - p:seek(self.frame_width - 1, self.frame_height - 1):string(string.char(25), COLOR_LIGHTCYAN) -- down - end - end -end - -function warning:onInput(keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - elseif keys.CURSOR_UP or keys.STANDARDSCROLL_UP then - self.start = math.max(1, self.start - 1) - elseif keys.CURSOR_DOWN or keys.STANDARDSCROLL_DOWN then - self.start = math.min(self.start + 1, self.max_start) - end -end - -local function findRaceCaste(unit) - local rraw = df.creature_raw.find(unit.race) - return rraw, safe_index(rraw, 'caste', unit.caste) -end - -local function getSexString(sex) - local sexStr = "" - if sex==0 then sexStr=string.char(12) - elseif sex==1 then sexStr=string.char(11) - end - return string.char(40)..sexStr..string.char(41) -end - -local function nameOrSpeciesAndNumber(unit) - if unit.name.has_name then - return dfhack.TranslateName(dfhack.units.getVisibleName(unit))..' '..getSexString(unit.sex),true - else - return 'Unit #'..unit.id..' ('..df.creature_raw.find(unit.race).caste[unit.caste].caste_name[0]..' '..getSexString(unit.sex)..')',false - end -end - -local function checkVariable(var, limit, description, map, unit) - local rraw = findRaceCaste(unit) - local species = rraw.name[0] - local profname = dfhack.units.getProfessionName(unit) - if #profname == 0 then profname = nil end - local name = nameOrSpeciesAndNumber(unit) - if var > limit then - if not map[unit.id] then - map[unit.id] = true - return name .. ", " .. (profname or species) .. " is " .. description .. "!" - end - else - map[unit.id] = false - end - return nil -end - -function doCheck() - local messages = {} - for i=#units-1, 0, -1 do - local unit = units[i] - local rraw = findRaceCaste(unit) - if rraw and not unit.flags1.dead and not dfhack.units.isOpposedToLife(unit) then - table.insert(messages, checkVariable(unit.counters2.hunger_timer, 75000, 'starving', starvingUnits, unit)) - table.insert(messages, checkVariable(unit.counters2.thirst_timer, 50000, 'dehydrated', dehydratedUnits, unit)) - table.insert(messages, checkVariable(unit.counters2.sleepiness_timer, 150000, 'very drowsy', sleepyUnits, unit)) - end - end - if #messages > 0 then - dfhack.color(COLOR_LIGHTMAGENTA) - for _, msg in pairs(messages) do - print(dfhack.df2console(msg)) - end - dfhack.color() - df.global.pause_state = true - warning{messages=messages}:show() - end -end - -if not moduleMode then doCheck() end diff --git a/scripts/weather.lua b/scripts/weather.lua deleted file mode 100644 index a61ab48bd..000000000 --- a/scripts/weather.lua +++ /dev/null @@ -1,46 +0,0 @@ --- Print the weather map or change weather. -local helpstr = [[=begin - -weather -======= -Prints a map of the local weather, or with arguments ``clear``, -``rain``, and ``snow`` changes the weather. - -=end]] - -local args = {...} -local cmd -local val_override = tonumber(args[1]) -if args[1] then - cmd = args[1]:sub(1, 1) -end -if cmd == "h" or cmd == "?" then - print("The current weather is "..df.weather_type[dfhack.world.ReadCurrentWeather()]) - print((helpstr:gsub('=[a-z]+', ''))) -elseif cmd == "c" then - dfhack.world.SetCurrentWeather(df.weather_type.None) - print("The weather has cleared.") -elseif cmd == "r" then - dfhack.world.SetCurrentWeather(df.weather_type.Rain) - print("It is now raining.") -elseif cmd == "s" then - dfhack.world.SetCurrentWeather(df.weather_type.Snow) - print("It is now snowing.") -elseif val_override then - dfhack.world.SetCurrentWeather(val_override) - print("Set weather to " .. val_override) -elseif args[1] then - qerror("Unrecognized argument: " .. args[1]) -else - -- df.global.current_weather is arranged in columns, not rows - kind = {[0]="C", "R", "S"} - print("Weather map (C = clear, R = rain, S = snow):") - for y=0, 4 do - s = "" - for x=0, 4 do - local cur = df.global.current_weather[x][y] - s = s .. (kind[cur] or cur) .. ' ' - end - print(s) - end -end diff --git a/test/main.lua b/test/main.lua new file mode 100644 index 000000000..0085a9711 --- /dev/null +++ b/test/main.lua @@ -0,0 +1,10 @@ +function set_test_stage(stage) + local f = io.open('test_stage.txt', 'w') + f:write(stage) + f:close() +end + +print('running tests') + +set_test_stage('done') +dfhack.run_command('die') diff --git a/travis/build-lua.sh b/travis/build-lua.sh new file mode 100644 index 000000000..e29d7ce87 --- /dev/null +++ b/travis/build-lua.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +set -e + +LUA_ROOT="$HOME/lua53" +LUA_URL="http://www.lua.org/ftp/lua-5.3.3.tar.gz" +LUA_TAR=$(basename "$LUA_URL") +LUA_DIR="$LUA_ROOT/${LUA_TAR%.tar.*}" +LUA_SHA1="a0341bc3d1415b814cc738b2ec01ae56045d64ef" + +echo LUA_ROOT $LUA_ROOT +echo LUA_TAR $LUA_TAR +echo LUA_DIR $LUA_DIR + +sha1() { + python -c 'import hashlib, sys; print(hashlib.sha1(open(sys.argv[1],"rb").read()).hexdigest())' "$1" +} + +download() { + echo "Downloading $LUA_URL" + wget -O "$LUA_ROOT/$LUA_TAR" "$LUA_URL" + tar xvf "$LUA_ROOT/$LUA_TAR" +} + +build() { + cd "$LUA_DIR/src" + make generic +} + +main() { + mkdir -p "$LUA_ROOT" + cd "$LUA_ROOT" + mkdir -p bin + + if [ "$(sha1 "$LUA_ROOT/$LUA_TAR" 2>/dev/null)" != "$LUA_SHA1" ]; then + download + build + else + echo "Already downloaded" + + if [ -x "$LUA_DIR/src/luac" ]; then + echo "Already built" + else + build + fi + fi + + echo "Linking" + ln -sf "$LUA_DIR/src/lua" "$LUA_ROOT/bin/lua5.3" + ln -sf "$LUA_DIR/src/luac" "$LUA_ROOT/bin/luac5.3" + echo "Done" +} + +main diff --git a/travis/dfhack_travis.init b/travis/dfhack_travis.init new file mode 100644 index 000000000..d9bc3e5ba --- /dev/null +++ b/travis/dfhack_travis.init @@ -0,0 +1,2 @@ +:lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR')) +test/main diff --git a/travis/download-df.sh b/travis/download-df.sh new file mode 100755 index 000000000..20dc3dbd4 --- /dev/null +++ b/travis/download-df.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +tardest="df.tar.bz2" + +which md5sum && alias md5=md5sum +selfmd5=$(openssl md5 < "$0") +echo $selfmd5 + +cd "$(dirname "$0")" +echo "DF_VERSION: $DF_VERSION" +echo "DF_FOLDER: $DF_FOLDER" +mkdir -p "$DF_FOLDER" +cd "$DF_FOLDER" + +if [ -f receipt ]; then + if [ "$selfmd5" != "$(cat receipt)" ]; then + echo "download-df.sh changed; removing DF" + else + echo "Already downloaded $DF_VERSION" + exit 0 + fi +fi + +rm -rif "$tardest" df_linux + +minor=$(echo "$DF_VERSION" | cut -d. -f2) +patch=$(echo "$DF_VERSION" | cut -d. -f3) +url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" + +echo Downloading +wget "$url" -O "$tardest" +echo Extracting +tar xf "$tardest" --strip-components=1 +echo Changing settings +echo '' >> "$DF_FOLDER/data/init/init.txt" +echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" +echo '[SOUND:NO]' >> "$DF_FOLDER/data/init/init.txt" +echo Done + +echo "$selfmd5" > receipt diff --git a/travis/get-df-version.sh b/travis/get-df-version.sh new file mode 100755 index 000000000..13d317d2e --- /dev/null +++ b/travis/get-df-version.sh @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +cd .. +grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' diff --git a/travis/run-tests.py b/travis/run-tests.py new file mode 100644 index 000000000..a8265088c --- /dev/null +++ b/travis/run-tests.py @@ -0,0 +1,30 @@ +import os, subprocess, sys + +MAX_TRIES = 5 + +dfhack = 'Dwarf Fortress.exe' if sys.platform == 'win32' else './dfhack' +test_stage = 'test_stage.txt' + +def get_test_stage(): + if os.path.isfile(test_stage): + return open(test_stage).read().strip() + return '0' + +os.chdir(sys.argv[1]) +if os.path.exists(test_stage): + os.remove(test_stage) + +tries = 0 +while True: + tries += 1 + stage = get_test_stage() + print('Run #%i: stage=%s' % (tries, get_test_stage())) + if stage == 'done': + print('Done!') + os.remove(test_stage) + sys.exit(0) + if tries > MAX_TRIES: + print('Too many tries - aborting') + sys.exit(1) + + os.system(dfhack) diff --git a/travis/script-in-readme.py b/travis/script-docs.py similarity index 69% rename from travis/script-in-readme.py rename to travis/script-docs.py index 72b7db86a..33b2818c8 100644 --- a/travis/script-in-readme.py +++ b/travis/script-docs.py @@ -4,6 +4,7 @@ import os from os.path import basename, dirname, join, splitext import sys +SCRIPT_PATH = sys.argv[1] if len(sys.argv) > 1 else 'scripts' def expected_cmd(path): """Get the command from the name of a script.""" @@ -17,24 +18,23 @@ def check_ls(fname, line): """Check length & existence of leading comment for "ls" builtin command.""" line = line.strip() comment = '--' if fname.endswith('.lua') else '#' - if line.endswith('=begin') or not line.startswith(comment): + if '[====[' in line or not line.startswith(comment): print('Error: no leading comment in ' + fname) return 1 - if len(line.replace(comment, '').strip()) > 53: - print('Error: leading comment too long in ' + fname) - return 1 return 0 def check_file(fname): errors, doclines = 0, [] + tok1, tok2 = ('=begin', '=end') if fname.endswith('.rb') else \ + ('[====[', ']====]') with open(fname, errors='ignore') as f: lines = f.readlines() errors += check_ls(fname, lines[0]) for l in lines: - if doclines or l.strip().endswith('=begin'): + if doclines or l.strip().endswith(tok1): doclines.append(l.rstrip()) - if l.startswith('=end'): + if l.startswith(tok2): break else: if doclines: @@ -42,7 +42,13 @@ def check_file(fname): else: print('Error: no documentation in: ' + fname) return 1 - title, underline = [d for d in doclines if d and '=begin' not in d][:2] + + if not doclines: + print('Error: missing or malformed documentation in: ' + fname) + return 1 + + title, underline = [d for d in doclines + if d and '=begin' not in d and '[====[' not in d][:2] if underline != '=' * len(title): print('Error: title/underline mismatch:', fname, title, underline) errors += 1 @@ -54,16 +60,14 @@ def check_file(fname): def main(): - """Check that all DFHack scripts include documentation (not 3rdparty)""" + """Check that all DFHack scripts include documentation""" err = 0 - for root, _, files in os.walk('scripts'): + for root, _, files in os.walk(SCRIPT_PATH): for f in files: - # TODO: remove 3rdparty exemptions from checks - # Requires reading their CMakeLists to only apply to used scripts - if f[-3:] in {'.rb', 'lua'} and '3rdparty' not in root: + if f[-3:] in {'.rb', 'lua'}: err += check_file(join(root, f)) return err if __name__ == '__main__': - sys.exit(bool(main())) + sys.exit(min(100, main()))