diff --git a/.gitignore b/.gitignore index 70ccb0445..5ef3ce759 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,9 @@ # linux backup files *~ -#Kdevelop project file +#Kdevelop project files *.kdev4 - -# compiled binaries -output/* - -# this one is important, track it -!output/Memory.xml - -# a file generated by cmake -dfhack/include/config.h -library/private/config.h +.kdev4 # any build folders build*/ @@ -20,13 +11,13 @@ nix buntu build/VC2010 +#except for the real one +!build/ + # Sphinx generated documentation docs/_* docs/html/ -#except for the real one -!build/ - # in-place build build/Makefile build/CMakeCache.txt @@ -35,27 +26,17 @@ build/CMakeFiles build/doc build/lua build/bin +build/depends build/library -build/tools build/plugins -build/depends +build/scripts build/install_manifest.txt -build/README.html -build/LUA_API.html -build/COMPILE.html - -#ignore Kdevelop stuff -.kdev4 - -#fake curses header -examples/fake-curses.h +build/_CPack_Packages +build/dfhack-*.zip +build/dfhack-*.bz2 # Python binding binaries *.pyc -dfhack/python/pydfhack/_pydfhack.so -dfhack/python/PyDFHack.egg-info -dfhack/python/build -dfhack/python/dist # CPack stuff build/CPack*Config.cmake @@ -71,6 +52,3 @@ tags # Mac OS X .DS_Store files .DS_Store - -# autogenerated include-all.rst files -**include-all.rst \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 4afad1704..da2c36a43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,11 +16,13 @@ script: - sh travis/git-info.sh - 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/ - mkdir build-travis - cd build-travis -- cmake .. && make -j3 +- cmake .. -DBUILD_DOCS:BOOL=ON +- make -j3 notifications: email: false diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py new file mode 100644 index 000000000..a45f07372 --- /dev/null +++ b/.ycm_extra_conf.py @@ -0,0 +1,130 @@ +'''This file provides editor completions while working on DFHack using ycmd: +https://github.com/Valloric/ycmd +''' + +# pylint: disable=import-error,invalid-name,missing-docstring,unused-argument + +import os +import ycm_core + +def DirectoryOfThisScript(): + return os.path.dirname(os.path.abspath(__file__)) + +# We need to tell YouCompleteMe how to compile this project. We do this using +# clang's "Compilation Database" system, which essentially just dumps a big +# json file into the build folder. +# More details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# We don't use clang, but luckily CMake supports generating a database on its +# own, using: +# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) + +for potential_build_folder in ['build', 'build-osx']: + if os.path.exists(DirectoryOfThisScript() + os.path.sep + potential_build_folder + + os.path.sep + 'compile_commands.json'): + database = ycm_core.CompilationDatabase(potential_build_folder) + break +else: + raise RuntimeError("Can't find dfhack build folder: not one of build, build-osx") + + +def MakeRelativePathsInFlagsAbsolute(flags, working_directory): + if not working_directory: + return list(flags) + new_flags = [] + make_next_absolute = False + path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith('/'): + new_flag = os.path.join(working_directory, flag) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith(path_flag): + path = flag[len(path_flag):] + new_flag = path_flag + os.path.join(working_directory, path) + break + + if new_flag: + new_flags.append(new_flag) + return new_flags + + +def IsHeaderFile(filename): + extension = os.path.splitext(filename)[1] + return extension in ['.h', '.hxx', '.hpp', '.hh'] + + +SOURCE_EXTENSIONS = ['.cpp', '.cxx', '.cc', '.c', '.m', '.mm'] + +def PotentialAlternatives(header): + dirname, filename = os.path.split(header) + basename, _ = os.path.splitext(filename) + + source_dirs = [dirname] + + if dirname.endswith(os.path.sep + 'include'): + # if we're in a folder 'include', also look in its parent + parent = os.path.abspath(os.path.join(dirname, os.path.pardir)) + source_dirs.append(parent) + # and ../src (used by lua dependency) + source_dirs.append(os.path.join(parent, 'src')) + + include_idx = dirname.rfind(os.path.sep + 'include' + os.path.sep) + if include_idx != -1: + # we're in a subfolder of a parent '/include/' + # .../include/subdir/path + # look in .../subdir/path + source_dirs.append( + dirname[:include_idx] + + os.path.sep + + dirname[include_idx + len('include') + 2*len(os.path.sep):] + ) + + for source_dir in source_dirs: + for ext in SOURCE_EXTENSIONS: + yield source_dir + os.path.sep + basename + ext + + +def GetCompilationInfoForFile(filename): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile(filename): + for alternative in PotentialAlternatives(filename): + if os.path.exists(alternative): + compilation_info = database.GetCompilationInfoForFile( + alternative + ) + + if compilation_info.compiler_flags_: + return compilation_info + return None + else: + return database.GetCompilationInfoForFile(filename) + + +def FlagsForFile(filename, **kwargs): + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile(filename) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ + ) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/CMakeLists.txt b/CMakeLists.txt index b6228a9e1..7da735015 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ else(CMAKE_CONFIGURATION_TYPES) endif (NOT CMAKE_BUILD_TYPE) endif(CMAKE_CONFIGURATION_TYPES) -OPTION(BUILD_DOCS "Choose whether to build the documentation (requires python and Sphinx)." ON) +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) @@ -24,8 +24,13 @@ 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" OR ${GCC_VERSION_OUT} VERSION_GREATER "4.9.9") - message(SEND_ERROR "${COMPILER_PATH} version ${GCC_VERSION_OUT} cannot be used - use GCC 4.5 through 4.9") + 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") + 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. + # http://developerblog.redhat.com/2015/02/05/gcc5-and-the-c11-abi/ + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) endif() endmacro() @@ -53,6 +58,11 @@ if(MSVC) add_definitions( "/wd4819" ) 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() + # 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! @@ -81,6 +91,9 @@ ${dfhack_SOURCE_DIR}/CMake/Modules ${CMAKE_MODULE_PATH} ) +# generates compile_commands.json, used for autocompletion by some editors +SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # mixing the build system with the source code is ugly and stupid. enforce the opposite :) if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR "In-source builds are not allowed.") @@ -88,12 +101,13 @@ endif() # make sure all the necessary submodules have been set up if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt) - message(SEND_ERROR "One or more required submodules could not be found! Run 'git submodule update --init' from the root DFHack directory. (See the section 'Getting the Code' in Compile.rst or Compile.html)") + message(SEND_ERROR "One or more required submodules could not be found! Run 'git submodule update --init' from the root DFHack directory. (See the section 'Getting the Code' in docs/Compile.rst)") endif() # set up versioning. -set(DF_VERSION "0.40.24") -SET(DFHACK_RELEASE "r4") +set(DF_VERSION "0.42.05") +SET(DFHACK_RELEASE "alpha0") +SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") @@ -164,6 +178,8 @@ endif() # find and make available libz if(NOT UNIX) SET(ZLIB_ROOT depends/zlib/) +else() + set(ZLIB_ROOT /usr/lib/i386-linux-gnu) endif() find_package(ZLIB REQUIRED) include_directories(depends/protobuf) @@ -176,9 +192,9 @@ include_directories(${ZLIB_INCLUDE_DIRS}) include_directories(depends/clsocket/src) add_subdirectory(depends) -find_package(Git) +find_package(Git REQUIRED) if(NOT GIT_FOUND) - message(FATAL_ERROR "could not find git") + message(SEND_ERROR "could not find git") endif() # build the lib itself @@ -196,7 +212,7 @@ endif() add_subdirectory(scripts) -find_package(Sphinx) +find_package(Sphinx QUIET) if (BUILD_DOCS) if (NOT SPHINX_FOUND) message(SEND_ERROR "Sphinx not found but BUILD_DOCS enabled") diff --git a/NEWS.rst b/NEWS.rst index fb16ab3f8..7fbfdd56a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,7 +9,7 @@ Internals Lua - New [Internal Commands | Plugins | Scripts | Tweaks] + New [Internal Commands | Plugins | Scripts | Features] Fixes Misc Improvements Removed @@ -29,6 +29,84 @@ Changelog .. contents:: :depth: 2 +DFHack future +============= + +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 + +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 + +New Features +------------ +- `buildingplan`: Support for floodgates, grates, and bars +- `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 + +Fixes +----- +- `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42 +- `showmood`: Fixed name display on OS X/Linux + +Misc Improvements +----------------- +- `weather`: now implemented by a script + + +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 ================= diff --git a/depends/jsoncpp/jsoncpp-ex.h b/depends/jsoncpp/jsoncpp-ex.h index fc1174133..7d621dd72 100644 --- a/depends/jsoncpp/jsoncpp-ex.h +++ b/depends/jsoncpp/jsoncpp-ex.h @@ -1,7 +1,7 @@ #include "jsoncpp.h" #pragma once -namespace JsonEx { +namespace Json { template bool is (const Json::Value &val) { return false; } template T as (const Json::Value &val); @@ -11,7 +11,7 @@ namespace JsonEx { template<> inline bool is (const Json::Value &val) { return val.is_func(); } \ template<> inline type as (const Json::Value &val) { return val.as_func(); } \ template<> inline type get (const Json::Value &val, const std::string &key, const type &default_) \ - { Json::Value x = val[key]; return is(x) ? as(x) : default_; } + { const Json::Value &x = val[key]; return is(x) ? as(x) : default_; } define_helpers(bool, isBool, asBool); define_helpers(Json::Int, isInt, asInt); define_helpers(Json::UInt, isUInt, asUInt); diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 9ec13e6ca..4fec34125 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -21,8 +21,13 @@ IF(CMAKE_COMPILER_IS_GNUCC) 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_SOURCE_DIR}/testHashMap.cpp") - TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + 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}) @@ -40,8 +45,8 @@ IF(CMAKE_COMPILER_IS_GNUCC) 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_SOURCE_DIR}/testHashMap.cpp") - TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + 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}) @@ -227,18 +232,22 @@ install(TARGETS protobuf-lite LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) -# Protobuf compiler shared library +IF(NOT CMAKE_CROSSCOMPILING) + # Protobuf compiler shared library + + ADD_LIBRARY(protoc SHARED ${LIBPROTOC_SRCS} ${LIBPROTOC_HDRS}) + IDE_FOLDER(protoc "Depends") -ADD_LIBRARY(protoc SHARED ${LIBPROTOC_SRCS} ${LIBPROTOC_HDRS}) -IDE_FOLDER(protoc "Depends") + SET_TARGET_PROPERTIES(protoc PROPERTIES COMPILE_DEFINITIONS LIBPROTOC_EXPORTS) + TARGET_LINK_LIBRARIES(protoc protobuf) -SET_TARGET_PROPERTIES(protoc PROPERTIES COMPILE_DEFINITIONS LIBPROTOC_EXPORTS) -TARGET_LINK_LIBRARIES(protoc protobuf) + # Protobuf compiler executable -# Protobuf compiler executable + ADD_EXECUTABLE(protoc-bin google/protobuf/compiler/main.cc google/protobuf/compiler/command_line_interface.h google/protobuf/compiler/cpp/cpp_generator.h) + IDE_FOLDER(protoc-bin "Depends") -ADD_EXECUTABLE(protoc-bin google/protobuf/compiler/main.cc google/protobuf/compiler/command_line_interface.h google/protobuf/compiler/cpp/cpp_generator.h) -IDE_FOLDER(protoc-bin "Depends") + SET_TARGET_PROPERTIES(protoc-bin PROPERTIES OUTPUT_NAME protoc) + TARGET_LINK_LIBRARIES(protoc-bin protoc) -SET_TARGET_PROPERTIES(protoc-bin PROPERTIES OUTPUT_NAME protoc) -TARGET_LINK_LIBRARIES(protoc-bin protoc) + EXPORT(TARGETS protoc-bin FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake ) +ENDIF() diff --git a/dfhack.init-example b/dfhack.init-example index 578161c3f..f2cf6b156 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -179,12 +179,15 @@ tweak farm-plot-select tweak import-priority-category # Misc. UI tweaks +tweak block-labors # Prevents labors that can't be used from being toggled tweak civ-view-agreement tweak fps-min +tweak hide-priority tweak kitchen-keys tweak kitchen-prefs-empty tweak max-wheelbarrow tweak shift-8-scroll +tweak title-start-rename tweak tradereq-pet-gender ########################### @@ -208,6 +211,7 @@ enable \ confirm \ dwarfmonitor \ mousequery \ + autogems \ automelt \ autotrade \ buildingplan \ @@ -216,15 +220,13 @@ 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. # You cannot extend a commented line. # You can comment out the extension of a line. -# allow the fortress bookkeeper to queue jobs through the manager -enable stockflow - # enable mouse controls and sand indicator in embark screen embark-tools enable sand mouse diff --git a/docs/Authors.rst b/docs/Authors.rst index 05997b3d6..f6c74d8c1 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -15,6 +15,7 @@ Alexander Gavrilov angavrilov ag AndreasPK AndreasPK Angus Mezick amezick Antalia tamarakorr +Anuradha Dissanayake falconne belal jimhester Ben Lubar BenLubar Caldfir caldfir @@ -36,6 +37,7 @@ IndigoFenix James Logsdon jlogsdon Japa JapaMala Jared Adams +Jim Lisi stonetoad jj jjyg jj`` John Beisley huin John Shade gsvslto @@ -57,6 +59,7 @@ Mike Stewart thewonderidiot Mikko Juola Noeda Adeon MithrilTuxedo MithrilTuxedo mizipzor mizipzor +moversti moversti Neil Little nmlittle Nick Rart nickrart comestible Omniclasm @@ -66,14 +69,15 @@ potato Priit Laes plaes Putnam Putnam3145 Quietust quietust _Q +Raidau Raidau Ramblurr Ramblurr rampaging-poet Raoul van Putten Raoul XQ raoulxq reverb -Raidau Raidau Rinin Rinin Robert Heinrich rh73 +Robert Janetzko robertjanetzko rofl0r rofl0r root Roses Pheosics @@ -83,13 +87,16 @@ Rumrusher rumrusher RusAnon RusAnon sami scamtank scamtank +Sebastian Wolfertz Enkrod Seth Woodworth sethwoodworth simon Simon Jackson sizeak Tacomagic Tim Walberg twalberg Timothy Collett danaris +Tom Jobbins TheBloke Tom Prince +txtsd txtsd U-glouglou\\simon Valentin Ochs Cat-Ion Vjek diff --git a/docs/Compile.rst b/docs/Compile.rst index f4655d8bf..81719c902 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -10,24 +10,43 @@ and `install the latest release instead `. .. contents:: :depth: 2 +.. _compile-how-to-get-the-code: How to get the code =================== DFHack doesn't have any kind of system of code snapshots in place, so you will have to -get code from the github repository using git. How to get git is described under +get code from the GitHub repository using Git. How to get Git is described under the instructions for each platform. -To get the code:: +To get the latest release code (master branch):: git clone --recursive https://github.com/DFHack/dfhack cd dfhack -If your version of git does not support the ``--recursive`` flag, you will need to omit it and run +If your version of Git does not support the ``--recursive`` flag, you will need to omit it and run ``git submodule update --init`` after entering the dfhack directory. +To get the latest development code (develop branch), clone as above and then:: + + git checkout develop + git submodule update + +**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. + + +Contributing to DFHack +====================== If you want to get involved with the development, create an account on -Github, make a clone there and then use that as your remote repository instead. -We'd love that; join us on IRC (#dfhack channel on freenode) if you need help. +GitHub, make a clone there and then use that as your remote repository instead. + +We'd love that; join us on IRC (#dfhack channel on freenode) for discussion, +and whenever you need help. + +For lots more details on contributing to DFHack, including pull requests, code format, +and more, please see `contributing-code`. Build types @@ -40,7 +59,8 @@ Without specifying a build type or 'None', cmake uses the ``CMAKE_CXX_FLAGS`` variable for building. Valid and useful build types include 'Release', 'Debug' and -'RelWithDebInfo'. 'Debug' is not available on Windows. +'RelWithDebInfo'. +'Debug' is not available on Windows, use 'RelWithDebInfo' instead. Linux @@ -51,14 +71,27 @@ 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. +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. -To build DFHack you need a 32-bit version of GCC. GCC 4.5 is easiest to work -with due to avoiding libstdc++ issues (see below), but any later 4.x version -should work as well. GCC 5.x may work but is discouraged for releases because it -won't work on systems without GCC 5.x installed. On 64-bit distributions, you'll -need the multilib development tools and libraries (``gcc-multilib`` or -``gcc-4.x-multilib`` on Debian). Alternatively, you might be able to use ``lxc`` +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. + +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>`. @@ -68,13 +101,24 @@ Before you can build anything, you'll also need ``cmake``. It is advisable to al 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. -* On Arch linux, ``perl-xml-libxml`` and ``perl-xml-libxslt`` (or through ``cpan``) -* On 64-bit Ubuntu, ``apt-get install zlib1g-dev:i386 libxml-libxml-perl libxml-libxslt-perl``. -* On 32-bit Ubuntu, ``apt-get install zlib1g-dev libxml-libxml-perl libxml-libxslt-perl``. -* Debian-derived distros should have similar requirements. - To build Stonesense, you'll also need OpenGL headers. +Here are some package install commands for various platforms: + +* On Arch linux: + + * For the required Perl modules: ``perl-xml-libxml`` and ``perl-xml-libxslt`` (or through ``cpan``) + +* On 64-bit Ubuntu: + + * ``apt-get install gcc cmake git gcc-multilib g++-multilib zlib1g-dev:i386 libxml-libxml-perl libxml-libxslt-perl``. + +* On 32-bit Ubuntu: + + * ``apt-get install gcc cmake git gcc-multilib g++-multilib zlib1g-dev libxml-libxml-perl libxml-libxslt-perl``. + +* Debian-derived distros should have similar requirements. + Build ----- @@ -82,11 +126,12 @@ Building is fairly straightforward. Enter the ``build`` folder (or create an empty folder in the DFHack directory to use instead) and start the build like this:: cd build - cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF - make install + cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX= + make install # or make -jX install on multi-core systems to compile with X parallel processes -Obviously, replace the install path with path to your DF. This will build the library -along with the normal set of plugins and install them into your DF folder. + should be a path to a copy of Dwarf Fortress, of the appropriate +version for the DFHack you are building. This will build the library along +with the normal set of plugins and install them into your DF folder. Alternatively, you can use ccmake instead of cmake:: @@ -95,21 +140,20 @@ Alternatively, you can use ccmake instead of cmake:: make install This will show a curses-based interface that lets you set all of the -extra options. You can also use a cmake-friendly IDE like KDevelop 4 +extra options. You can also use a cmake-friendly IDE like KDevelop 4 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 the error message:: +This manifests itself with 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) -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.5 or remove the libstdc++ shipped with DF, which causes DF to use your system libstdc++ instead:: cd /path/to/DF/ @@ -118,7 +162,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.8 is discouraged. +with GCC versions newer than 4.5 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. Mac OS X ======== @@ -131,12 +177,41 @@ following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 +Note for El Capitan (OSX 10.11) and XCode 7.x users +--------------------------------------------------- + +* 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: + + * 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:: + + 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 . + + * Install XCode 6, which is available as a free download from the Apple + Developer Center. + + * 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. + +Dependencies and system set-up +------------------------------ + #. Download and unpack a copy of the latest DF #. Install Xcode from Mac App Store -#. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. + +#. Install the XCode Command Line Tools by running the following command:: + + xcode-select --install + #. Install dependencies - Using `Homebrew `_:: + Using `Homebrew `_ (recommended):: brew tap homebrew/versions brew install git @@ -150,46 +225,70 @@ following environment variable:: Macports will take some time - maybe hours. At some point it may ask you to install a Java environment; let it do so. -#. Install perl dependencies + 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. - 1. ``sudo cpan`` +#. Install Perl dependencies - If this is the first time you've run cpan, you will need to go through the setup - process. Just stick with the defaults for everything and you'll be fine. + * Using system Perl - If you are running OS X 10.6 (Snow Leopard) or earlier, good luck! - You'll need to open a separate Terminal window and run:: + * ``sudo cpan`` - sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml + If this is the first time you've run cpan, you will need to go through the setup + process. Just stick with the defaults for everything and you'll be fine. - 2. ``install XML::LibXML`` - 3. ``install XML::LibXSLT`` + If you are running OS X 10.6 (Snow Leopard) or earlier, good luck! + You'll need to open a separate Terminal window and run:: -#. Get the dfhack source:: + sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml - git clone --recursive https://github.com/DFHack/dfhack.git - cd dfhack + * ``install XML::LibXML`` + * ``install XML::LibXSLT`` + + * In a separate, local Perl install + + Rather than using system Perl, you might also want to consider + the Perl manager, `Perlbrew `_. + + This manages Perl 5 locally under ``~/perl5/``, providing an easy + way to install Perl and run CPAN against it without ``sudo``. + It can maintain multiple Perl installs and being local has the + benefit of easy migration and insulation from OS issues and upgrades. + + See http://perlbrew.pl/ for more details. + +Building +-------- -#. Set environment variables: +* Get the DFHack source as per section `compile-how-to-get-the-code`, above. +* Set environment variables - Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: + Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: export CC=/usr/local/bin/gcc-4.5 export CXX=/usr/local/bin/g++-4.5 - Macports:: + Macports:: export CC=/opt/local/bin/gcc-mp-4.5 export CXX=/opt/local/bin/g++-mp-4.5 -#. Build dfhack:: + Change the version numbers appropriately if you installed a different version of GCC. + +* Build dfhack:: mkdir build-osx cd build-osx - cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory - make - make install + cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX= + make install # or make -j X install on multi-core systems to compile with X parallel processes + should be a path to a copy of Dwarf Fortress, of the appropriate + version for the DFHack you are building. + +.. _compile-windows: Windows ======= @@ -197,39 +296,150 @@ On Windows, DFHack replaces the SDL library distributed with DF. Dependencies ------------ -You will need some sort of Windows port of git, or a GUI. Some examples: +You will need the following: + +* Microsoft Visual Studio 2010 SP1, 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. + +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.) + +If not, you can download it directly `from this Microsoft Download link `_. + +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 +\*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 +guaranteed to have the correct setup (especially PATH). + +To install Chocolatey and the required dependencies: + +* Go to https://chocolatey.org in a web browser +* At the top of the page it will give you the install command to copy + + * Copy the first one, which starts ``@powershell ...`` + * It won't be repeated here in case it changes in future Chocolatey releases. + +* Open an elevated (Admin) ``cmd.exe`` window + + * On Windows 8 and later this can be easily achieved by: + + * right-clicking on the Start Menu, or pressing Win+X. + * choosing "Command Prompt (Admin)" + + * On earlier Windows: find ``cmd.exe`` in Start Menu, right click + and choose Open As Administrator. + +* Paste in the Chocolatey install command and hit enter +* Close this ``cmd.exe`` window and open another Admin ``cmd.exe`` in the same way +* Run the following command:: + + choco install git cmake.portable strawberryperl -y + +* Close the Admin ``cmd.exe`` window; you're done! + +You can now use all of these utilities from any normal ``cmd.exe`` window. +You only need Admin/elevated ``cmd.exe`` for running ``choco install`` commands; +for all other purposes, including compiling DFHack, you should use +a normal ``cmd.exe`` (or, better, an improved terminal like `Cmder `_; +details below, under Build.) + +**NOTE**: you can run the above ``choco install`` command even if you already have +Git, CMake or StrawberryPerl installed. Chocolatey will inform you if any software +is already installed and won't re-install it. In that case, please check the PATHs +are correct for that utility as listed in the manual instructions below. Or, better, +manually uninstall the version you have already and re-install via Chocolatey, +which will ensure the PATH are set up right and will allow Chocolatey to manage +that program for you in future. + +Additional dependencies: installing manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you prefer to install manually rather than using Chocolatey, details and +requirements are as below. If you do install manually, please ensure you +have all PATHs set up correctly. + +Git +^^^ +Some examples: * `Git for Windows `_ (command-line and GUI) * `tortoisegit `_ (GUI and File Explorer integration) -You need ``cmake``. Get the win32 installer version from +CMake +^^^^^ +You can get the win32 installer version from `the official site `_. It has the usual installer wizard. Make sure you let it add its binary folder to your binary search PATH so the tool can be later run from anywhere. -You'll need a copy of Microsoft Visual C++ 2010. The Express version is sufficient. -Grab it from `Microsoft's site `_. -You'll also need the Visual Studio 2010 SP1 update. +Perl / Strawberry Perl +^^^^^^^^^^^^^^^^^^^^^^ +For the code generation parts you'll need Perl 5 with XML::LibXML and XML::LibXSLT. +`Strawberry Perl `_ is recommended as it includes +all of the required packages in a single, easy install. + +After install, ensure Perl is in your user's PATH. This can be edited from +``Control Panel -> System -> Advanced System Settings -> Environment Variables``. + +The following three directories must be in PATH, in this order: -For the code generation parts, you'll need perl with XML::LibXML and XML::LibXSLT. -`Strawberry Perl `_ works nicely for this and includes -all of the required packages. +* ``\c\bin`` +* ``\perl\site\bin`` +* ``\perl\bin`` -If you already have a different version of perl (for example the one from cygwin), -you can run into some trouble. Either remove the other perl install from PATH, or -install libxml and libxslt for it instead. +Be sure to close and re-open any existing ``cmd.exe`` windows after updating +your PATH. + +If you already have a different version of Perl (for example the one from Cygwin), +you can run into some trouble. Either remove the other Perl install from PATH, or +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. -First, run ``set_df_path.vbs`` and point the dialog that pops up at your -DF folder that you want to use for development. -Next, run one of the scripts with ``generate`` prefix. These create the MSVC solution file(s): +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 +you are compiling. The result is the creation of the file ``DF_PATH.txt`` in +the build directory. It contains the full path to the destination directory. +You could therefore also create this file manually - or copy in a pre-prepared +version - if you prefer. + +Next, run one of the scripts with ``generate`` prefix. These create the MSVC +solution file(s): * ``all`` will create a solution with everything enabled (and the kitchen sink). -* ``gui`` will pop up the cmake gui and let you pick and choose what to build. +* ``gui`` will pop up the CMake GUI and let you choose what to build. This is probably what you want most of the time. Set the options you are interested 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 - @@ -237,12 +447,174 @@ Next, run one of the scripts with ``generate`` prefix. These create the MSVC sol Then you can either open the solution with MSVC or use one of the msbuild scripts: +Building/installing from the command line: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In the build directory you will find several ``.bat`` files: + * Scripts with ``build`` prefix will only build DFHack. * Scripts with ``install`` prefix will build DFHack and install it to the previously selected DF path. * Scripts with ``package`` prefix will build and create a .zip package of DFHack. -When you open the solution in MSVC, make sure you never use the Debug builds. Those aren't -binary-compatible with DF. If you try to use a debug build with DF, you'll only get crashes. -For this reason the Windows "debug" scripts actually do RelWithDebInfo builds, -so pick either Release or RelWithDebInfo build and build the INSTALL target. +Compiling from the command line is generally the quickest and easiest option. +However be aware that due to the limitations of ``cmd.exe`` - especially in +versions of Windows prior to Windows 10 - it can be very hard to see what happens +during a build. If you get a failure, you may miss important errors or warnings +due to the tiny window size and extremely limited scrollback. For that reason you +may prefer to compile in the IDE which will always show all build output. + +Alternatively (or additionally), consider installing an improved Windows terminal +such as `Cmder `_. Easily installed through Chocolatey with: +``choco install cmder -y``. + +**Note for Cygwin/msysgit users**: It is also possible to compile DFHack from a +Bash command line. This has three potential benefits: + +* When you've installed Git and are using its Bash, but haven't added Git to your path: + + * You can load Git's Bash and as long as it can access Perl and CMake, you can + use it for compile without adding Git to your system path. + +* When you've installed Cygwin and its SSH server: + + * You can now SSH in to your Windows install and compile from a remote terminal; + very useful if your Windows installation is a local VM on a \*nix host OS. + +* In general: you can use Bash as your compilation terminal, meaning you have a decent + sized window, scrollback, etc. + + * Whether you're accessing it locally as with Git's Bash, or remotely through + Cygwin's SSH server, this is far superior to using ``cmd.exe``. + +You don't need to do anything special to compile from Bash. As long as your PATHs +are set up correctly, you can run the same generate- and build/install/package- bat +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. + +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. +If you try to use a debug build with DF, you'll only get crashes and for this +reason the Windows "debug" scripts actually do RelWithDebInfo builds. +After loading the Solution, change the Build Type to either ``Release`` +or ``RelWithDebInfo``. + +Then build the ``INSTALL`` target listed under ``CMakePredefinedTargets``. + + +########################## +Building the documentation +########################## + +DFHack documentation, like the file you are reading now, is created as .rst files, +which are in `reStructuredText (reST) `_ format. +This is a documenation format that has come from the Python community. It is very +similar in concept - and in syntax - to Markdown, as found on GitHub and many other +places. However it is more advanced than Markdown, with more features available when +compiled to HTML, such as automatic tables of contents, cross-linking, special +external links (forum, wiki, etc) and more. The documentation is compiled by a +Python tool, `Sphinx `_. + +The DFHack build process will compile the documentation but this has been disabled +by default. You only need to build the docs if you're changing them, or perhaps +if you want a local HTML copy; otherwise, read them easily online at +`ReadTheDoc's DFHack hosted documentation `_. + +(Note that even if you do want a local copy, it is certainly not necesesary to +compile the documentation in order to read it. Like Markdown, reST documents are +designed to be just as readable in a plain-text editor as they are in HTML format. +The main thing you lose in plain text format is hyperlinking.) + + +Enabling documentation building +=============================== +First, make sure you have followed all the necessary steps for your platform as +outlined in the rest of this document. + +To compile documentation with DFHack, add the following flag to your ``cmake`` command:: + + -DBUILD_DOCS:bool=ON + +For example:: + + cmake .. -DCMAKE_BUILD_TYPE:string=Release -DBUILD_DOCS:bool=ON -DCMAKE_INSTALL_PREFIX= + +Alternatively you can use the CMake GUI which allows options to be changed easily. + +On Windows you should either use ``generate-msvc-gui.bat`` and set the option +through the GUI, or else if you want to use an alternate file, such as +``generate-msvc-all.bat``, you will need to edit it to add the flag. +Or you could just run ``cmake`` on the command line like in other platforms. + +Required dependencies +===================== +In order to build the documentation, you must have Python with Sphinx +version 1.3.1 or later. Both Python 2.x and 3.x are supported. + +When installing Sphinx from OS package managers, be aware that there is +another program called Sphinx, completely unrelated to documentation management. +Be sure you are installing the right Sphinx; it may be called ``python-sphinx``, +for example. To avoid doubt, ``pip`` can be used instead as detailed below. + + +Linux +----- +Most Linux distributions will include Python as standard. + +Check your package manager to see if Sphinx 1.3.1 or later is available, +but at the time of writing Ubuntu for example only has 1.2.x. + +You can instead install Sphinx with the pip package manager. This may need +to be installed from your OS package manager; this is the case on Ubuntu. +On Ubuntu/Debian, use the following to first install pip:: + + sudo apt-get install python-pip + +Once pip is available, you can then install the Python Sphinx module with:: + + pip install sphinx + +If you run this as a normal user it will install a local copy for your user only. +Run it with sudo if you want a system-wide install. Either is fine for DFHack, +however if installing locally do check that ``sphinx-build`` is in your path. +It may be installed in a directory such as ``~/.local/bin/``, so after pip +install, find ``sphinx-build`` and ensure its directory is in your local ``$PATH``. + + +Mac OS X +-------- +OS X has Python 2.7 installed by default, but it does not have the pip package manager. + +You can install Homebrew's Python 3, which includes pip, and then install the +latest Sphinx using pip:: + + brew install python3 + pip3 install sphinx + +Alternatively, you can simply install Sphinx 1.3.x directly from Homebrew:: + + brew install sphinx-doc + +This will install Sphinx for OS X's system Python 2.7, without needing pip. + +Either method works; if you plan to use Python for other purposes, it might best +to install Homebrew's Python 3 so that you have the latest Python as well as pip. +If not, just installing sphinx-doc for OS X's system Python 2.7 is fine. + + +Windows +------- +Use the Chocolatey package manager to install Python and pip, +then use pip to install Sphinx. + +Run the following commands from an elevated (Admin) ``cmd.exe``, after installing +Chocolatey as outlined in the `Windows section `:: + + choco install python pip -y + +Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run:: + pip install sphinx diff --git a/docs/Contributing.rst b/docs/Contributing.rst index cd2495922..085949700 100644 --- a/docs/Contributing.rst +++ b/docs/Contributing.rst @@ -5,6 +5,8 @@ How to contribute to DFHack .. contents:: +.. _contributing-code: + Contributing Code ================= Several things should be kept in mind when contributing code to DFHack. diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 8a82e0f10..64b6b36a5 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -37,8 +37,8 @@ allows easier development of new tools. As an open-source project under Installing DFHack ================= -DFHack is available for the SDL version of Dwarf Frtress on Windows, -any modern Linux distribution, and OS X (10.6.8 to 10.9). +DFHack is available for the SDL version of Dwarf Fortress on Windows, +any modern Linux distribution, and Mac OS X (10.6.8 and later). It is possible to use Windows DF+DFHack under Wine on Linux or OS X. Most releases only support the version of DF mentioned in their title - for diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2a3db9b16..c090b8a9d 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1908,6 +1908,14 @@ and are only documented here for completeness: The table used by ``dfhack.run_script()`` to give every script its own global environment, persistent between calls to the script. +* ``dfhack.internal.getPE()`` + + Returns the PE timestamp of the DF executable (only on Windows) + +* ``dfhack.internal.getMD5()`` + + Returns the MD5 of the DF executable (only on OS X and Linux) + * ``dfhack.internal.getAddress(name)`` Returns the global address ``name``, or *nil*. diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 0cd16a075..33db4d8c3 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -182,10 +182,6 @@ Shows all items needed for the currently active strange mood. Bugfixes ======== -drybuckets -========== -Removes water from all buckets in your fortress, allowing them to be used for making lye. - fixdiplomats ============ Adds a Diplomat position to all Elven civilizations, allowing them to negotiate @@ -284,6 +280,7 @@ Subcommands that persist until disabled or DF quits: in advmode. The issue is that the screen tries to force you to select the contents separately from the container. This forcefully skips child reagents. +:block-labors: Prevents labors that can't be used from being toggled :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :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 @@ -298,6 +295,7 @@ Subcommands that persist until disabled or DF quits: :fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select 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 :import-priority-category: Allows changing the priority of all goods in a category when discussing an import agreement with the liaison @@ -319,6 +317,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. +: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 .. _fix-armory: @@ -876,6 +875,13 @@ job-duplicate In :kbd:`q` mode, when a job is highlighted within a workshop or furnace building, calling ``job-duplicate`` instantly duplicates the job. +.. _autogems: + +autogems +======== +Creates a new Workshop Order setting, automatically cutting rough gems +when `enabled `. + .. _stockflow: stockflow @@ -1026,6 +1032,13 @@ Maintain 10-100 locally-made crafts of exceptional quality:: workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 +.. _fix-job-postings: + +fix-job-postings +---------------- +This command fixes crashes caused by previous versions of workflow, mostly in +DFHack 0.40.24-r4, and should be run automatically when loading a world (but can +also be run manually if desired). .. _clean: @@ -1494,7 +1507,7 @@ The only undo option is to restore your save from backup. alltraffic ========== Set traffic designations for every single tile of the map - useful for resetting -traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquid`. +traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. Options: @@ -1791,29 +1804,12 @@ Usage: :digFlood digAll1: ignore the monitor list and dig any vein :digFlood digAll0: disable digAll mode -.. _feature: - -feature -======= -Enables management of map features. - -* Discovering a magma feature (magma pool, volcano, magma sea, or curious - underground structure) permits magma workshops and furnaces to be built. -* Discovering a cavern layer causes plants (trees, shrubs, and grass) from - that cavern to grow within your fortress. - -Options: - -:list: Lists all map features in your current embark by index. -:show X: Marks the selected map feature as discovered. -:hide X: Marks the selected map feature as undiscovered. - .. _filltraffic: filltraffic =========== Set traffic designations using flood-fill starting at the cursor. -See also `alltraffic`, `restrictice`, and `restrictliquid`. Options: +See also `alltraffic`, `restrictice`, and `restrictliquids`. Options: :H: High Traffic :N: Normal Traffic @@ -1998,12 +1994,12 @@ Regrows all the grass. Not much to it ;) restrictice =========== Restrict traffic on all tiles on top of visible ice. -See also `alltraffic`, `filltraffic`, and `restrictliquid`. +See also `alltraffic`, `filltraffic`, and `restrictliquids`. -.. _restrictliquid: +.. _restrictliquids: -restrictliquid -============== +restrictliquids +=============== Restrict traffic on all visible tiles with liquid. See also `alltraffic`, `filltraffic`, and `restrictice`. @@ -2118,11 +2114,6 @@ Options: Beware that filling in hollow veins will trigger a demon invasion on top of your miner when you dig into the region that used to be hollow. -weather -======= -Prints the current weather, and lets you change the weather to 'clear', 'rain' -or 'snow', with those words as commands (eg ``weather rain``). - ================= @@ -2162,8 +2153,6 @@ twice. createitem ========== -Use `modtools/create-item` - this plugin is deprecated and will be removed soon. - 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. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 3cd415780..5071d9e71 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -119,6 +119,7 @@ include/modules/Units.h include/modules/Engravings.h include/modules/EventManager.h include/modules/Gui.h +include/modules/GuiHooks.h include/modules/Items.h include/modules/Job.h include/modules/kitchen.h @@ -262,6 +263,11 @@ SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS DF_VERSION="${DF_VERSION}" DFHACK_RELEASE="${DFHACK_RELEASE}" ) +IF(DFHACK_PRERELEASE) + SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS + DFHACK_PRERELEASE=1 + ) +ENDIF() ADD_CUSTOM_TARGET(git-describe COMMAND ${CMAKE_COMMAND} @@ -303,10 +309,12 @@ ENDIF() SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) IF(APPLE) - SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) - IF(NOT EXISTS ${SDL_LIBRARY}) + SET(DF_SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework/Versions/A/SDL) + IF(NOT EXISTS ${DF_SDL_LIBRARY}) MESSAGE(SEND_ERROR "SDL framework not found. Make sure CMAKE_INSTALL_PREFIX is specified and correct.") ENDIF() + SET(SDL_LIBRARY ${CMAKE_BINARY_DIR}/SDL) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DF_SDL_LIBRARY} ${SDL_LIBRARY}) SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) diff --git a/library/Core.cpp b/library/Core.cpp index 130ec222d..00447fe72 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -62,6 +62,7 @@ using namespace std; using namespace DFHack; #include "df/ui.h" +#include "df/ui_sidebar_menus.h" #include "df/world.h" #include "df/world_data.h" #include "df/interfacest.h" @@ -261,7 +262,12 @@ static std::string getScriptHelp(std::string path, std::string helpprefix) std::string help; if (getline(script, help) && help.substr(0,helpprefix.length()) == helpprefix) - return help.substr(helpprefix.length()); + { + help = help.substr(helpprefix.length()); + while (help.size() && help[0] == ' ') + help = help.substr(1); + return help; + } } return "No help available."; @@ -276,13 +282,13 @@ static void listScripts(PluginManager *plug_mgr, std::map &pset, { if (hasEnding(files[i], ".lua")) { - std::string help = getScriptHelp(path + files[i], "-- "); + std::string help = getScriptHelp(path + files[i], "--"); pset[prefix + files[i].substr(0, files[i].size()-4)] = help; } else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && hasEnding(files[i], ".rb")) { - std::string help = getScriptHelp(path + files[i], "# "); + std::string help = getScriptHelp(path + files[i], "#"); pset[prefix + files[i].substr(0, files[i].size()-3)] = help; } @@ -649,14 +655,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } string file = findScript(parts[0] + ".lua"); if ( file != "" ) { - string help = getScriptHelp(file, "-- "); + string help = getScriptHelp(file, "--"); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); return CR_OK; } if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) { file = findScript(parts[0] + ".rb"); if ( file != "" ) { - string help = getScriptHelp(file, "# "); + string help = getScriptHelp(file, "#"); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); return CR_OK; } @@ -1172,9 +1178,25 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) res = runRubyScript(con, plug_mgr, first, parts); else if ( try_autocomplete(con, first, completed) ) - return CR_NOT_IMPLEMENTED; + res = CR_NOT_IMPLEMENTED; else con.printerr("%s is not a recognized command.\n", first.c_str()); + if (res == CR_NOT_IMPLEMENTED) + { + Plugin *p = plug_mgr->getPluginByName(first); + if (p) + { + con.printerr("%s is a plugin ", first.c_str()); + if (p->getState() == Plugin::PS_UNLOADED) + con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", + first.c_str()); + else if (p->size()) + con.printerr("that implements %i commands - see \"ls %s\" for details\n", + p->size(), first.c_str()); + else + con.printerr("but does not implement any commands\n"); + } + } } else if (res == CR_NEEDS_CONSOLE) con.printerr("%s needs interactive console to work.\n", first.c_str()); @@ -1234,7 +1256,7 @@ static void run_dfhack_init(color_ostream &out, Core *core) std::vector prefixes(1, "dfhack"); size_t count = loadScriptFiles(core, out, prefixes, "."); - if (!count) + if (!count || !Filesystem::isfile("dfhack.init")) { core->runCommand(out, "gui/no-dfhack-init"); core->loadScriptFile(out, "dfhack.init-example", true); @@ -1304,7 +1326,7 @@ void fIOthread(void * iodata) if(clueless_counter == 3) { - con.print("Do 'help' or '?' for the list of available commands.\n"); + con.print("Run 'help' or '?' for the list of available commands.\n"); clueless_counter = 0; } } @@ -1528,6 +1550,67 @@ bool Core::Init() if (!server->listen(RemoteClient::GetDefaultPort())) cerr << "TCP listen failed.\n"; + if (df::global::ui_sidebar_menus) + { + vector args; + const string & raw = df::global::ui_sidebar_menus->command_line.raw; + size_t offset = 0; + while (offset < raw.size()) + { + if (raw[offset] == '"') + { + offset++; + size_t next = raw.find("\"", offset); + args.push_back(raw.substr(offset, next - offset)); + offset = next + 2; + } + else + { + size_t next = raw.find(" ", offset); + if (next == string::npos) + { + args.push_back(raw.substr(offset)); + offset = raw.size(); + } + else + { + args.push_back(raw.substr(offset, next - offset)); + offset = next + 1; + } + } + } + for (auto it = args.begin(); it != args.end(); ) + { + const string & first = *it; + if (first.length() > 0 && first[0] == '+') + { + vector cmd; + for (it++; it != args.end(); it++) { + const string & arg = *it; + if (arg.length() > 0 && arg[0] == '+') + { + break; + } + cmd.push_back(arg); + } + + if (runCommand(con, first.substr(1), cmd) != CR_OK) + { + cerr << "Error running command: " << first.substr(1); + for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) + { + cerr << " \"" << *it2 << "\""; + } + cerr << "\n"; + } + } + else + { + it++; + } + } + } + cerr << "DFHack is running.\n"; return true; } @@ -1716,6 +1799,14 @@ void Core::doUpdate(color_ostream &out, bool first_update) screen = screen->child; } + // detect if the viewscreen changed, and trigger events later + bool vs_changed = false; + if (screen != top_viewscreen) + { + top_viewscreen = screen; + vs_changed = true; + } + bool is_load_save = strict_virtual_cast(screen) || strict_virtual_cast(screen) || @@ -1765,12 +1856,8 @@ void Core::doUpdate(color_ostream &out, bool first_update) } } - // detect if the viewscreen changed - if (screen != top_viewscreen) - { - top_viewscreen = screen; + if (vs_changed) onStateChange(out, SC_VIEWSCREEN_CHANGED); - } if (df::global::pause_state) { @@ -2032,6 +2119,12 @@ void Core::onStateChange(color_ostream &out, state_change_event event) break; } + if (event == SC_WORLD_LOADED && Version::is_prerelease()) + { + runCommand(out, "gui/prerelease-warning"); + std::cerr << "loaded map in prerelease build" << std::endl; + } + EventManager::onStateChange(out, event); buildings_onStateChange(out, event); diff --git a/library/DFHackVersion.cpp b/library/DFHackVersion.cpp index d3200a42f..744f3183b 100644 --- a/library/DFHackVersion.cpp +++ b/library/DFHackVersion.cpp @@ -33,5 +33,14 @@ namespace DFHack { return false; #endif } + + bool is_prerelease() + { + #ifdef DFHACK_PRERELEASE + return true; + #else + return false; + #endif + } } } diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index e72372fd4..3e020f9be 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -226,9 +226,9 @@ DFhackCExport void * SDL_GetVideoSurface(void) static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) { - if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) + DFHack::Core & c = DFHack::Core::getInstance(); + if ( c.isValid() && dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) { - DFHack::Core & c = DFHack::Core::getInstance(); DFHack::Graphic* g = c.getGraphic(); DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); diff --git a/library/Hooks-windows.cpp b/library/Hooks-windows.cpp index 552d9c186..7ea1385a7 100644 --- a/library/Hooks-windows.cpp +++ b/library/Hooks-windows.cpp @@ -271,9 +271,9 @@ DFhackCExport vPtr SDL_SetVideoMode(int width, int height, int bpp, uint32_t fla static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) { - if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) + DFHack::Core & c = DFHack::Core::getInstance(); + if ( c.isValid() && dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) { - DFHack::Core & c = DFHack::Core::getInstance(); DFHack::Graphic* g = c.getGraphic(); DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 62f274902..ba9591719 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1418,6 +1418,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP_VERSION_FUNC(getGitDescription, git_description), WRAP_VERSION_FUNC(getGitCommit, git_commit), WRAP_VERSION_FUNC(isRelease, is_release), + WRAP_VERSION_FUNC(isPrerelease, is_prerelease), { NULL, NULL } }; @@ -1464,6 +1465,7 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,isSuitableMaterial), WRAPM(Job,getName), WRAPM(Job,linkIntoWorld), + WRAPM(Job,removePostings), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } @@ -2302,6 +2304,24 @@ static const LuaWrapper::FunctionReg dfhack_internal_module[] = { { NULL, NULL } }; +static int internal_getmd5(lua_State *L) +{ + auto p = Core::getInstance().p; + if (p->getDescriptor()->getOS() == OS_WINDOWS) + luaL_error(L, "process MD5 not available on Windows"); + lua_pushstring(L, p->getMD5().c_str()); + return 1; +} + +static int internal_getPE(lua_State *L) +{ + auto p = Core::getInstance().p; + if (p->getDescriptor()->getOS() != OS_WINDOWS) + luaL_error(L, "process PE timestamp not available on non-Windows"); + lua_pushinteger(L, p->getPE()); + return 1; +} + static int internal_getAddress(lua_State *L) { const char *name = luaL_checkstring(L, 1); @@ -2681,6 +2701,8 @@ static int internal_findScript(lua_State *L) } static const luaL_Reg dfhack_internal_funcs[] = { + { "getPE", internal_getPE }, + { "getMD5", internal_getmd5 }, { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, { "getVTable", internal_getVTable }, diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index af0375963..774d19143 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -24,6 +24,7 @@ distribution. #include "modules/EventManager.h" #include "modules/Filesystem.h" +#include "modules/Screen.h" #include "Internal.h" #include "Core.h" #include "MemAccess.h" @@ -272,23 +273,13 @@ bool Plugin::load(color_ostream &con) plugin_abort_load; \ return false; \ } - #define plugin_check_symbols(sym1,sym2) \ - if (!LookupPlugin(plug, sym1) && !LookupPlugin(plug, sym2)) \ - { \ - con.printerr("Plugin %s: missing symbols: %s & %s\n", name.c_str(), sym1, sym2); \ - plugin_abort_load; \ - return false; \ - } - plugin_check_symbols("plugin_name", "name") // allow r3 plugins - plugin_check_symbols("plugin_version", "version") // allow r3 plugins + plugin_check_symbol("plugin_name") + plugin_check_symbol("plugin_version") plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_globals") const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name"); - if (!plug_name) // allow r3 plugin naming - plug_name = (const char ** )LookupPlugin(plug, "name"); - if (name != *plug_name) { con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name); @@ -296,9 +287,6 @@ bool Plugin::load(color_ostream &con) return false; } const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version"); - if (!plug_version) // allow r3 plugin version - plug_version =(const char ** ) LookupPlugin(plug, "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(); @@ -353,7 +341,6 @@ bool Plugin::load(color_ostream &con) plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable"); plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled"); plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); - plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports"); index_lua(plug); plugin_lib = plug; commands.clear(); @@ -386,6 +373,12 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + if (Screen::hasActiveScreens(this)) + { + con.printerr("Cannot unload plugin %s: has active viewscreens\n", name.c_str()); + access->unlock(); + return false; + } EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && @@ -611,16 +604,6 @@ Plugin::plugin_state Plugin::getState() const return state; } -PluginExports *Plugin::getExports() -{ - if (!plugin_get_exports) - return NULL; - PluginExports *exports = plugin_get_exports(); - if (!exports->bind(plugin_lib)) - return NULL; - return exports; -}; - void Plugin::index_lua(DFLibrary *lib) { if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands")) @@ -793,19 +776,6 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) lua_pushcclosure(state, lua_fun_wrapper, 4); } -bool PluginExports::bind(DFLibrary *lib) -{ - for (auto it = bindings.begin(); it != bindings.end(); ++it) - { - std::string name = it->first; - void** dest = it->second; - *dest = LookupPlugin(lib, name.c_str()); - if (!*dest) - return false; - } - return true; -} - PluginManager::PluginManager(Core * core) : core(core) { plugin_mutex = new recursive_mutex(); @@ -960,16 +930,6 @@ Plugin *PluginManager::getPluginByCommand(const std::string &command) return NULL; } -void *PluginManager::getPluginExports(const std::string &name) -{ - Plugin *plug = getPluginByName(name); - if (!plug) - return NULL; - if (plug->getState() != Plugin::plugin_state::PS_LOADED) - return NULL; - return plug->getExports(); -} - // FIXME: handle name collisions... command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters) { diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index a485e196d..6f159e5fe 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -60,15 +60,16 @@ Process::Process(VersionInfoFactory * known_versions) identified = false; my_descriptor = 0; + my_pe = 0; md5wrapper md5; uint32_t length; uint8_t first_kb [1024]; memset(first_kb, 0, sizeof(first_kb)); // get hash of the running DF process - string hash = md5.getHashFromFile(real_path, length, (char *) first_kb); + my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb); // create linux process, add it to the vector - VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash); + VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { my_descriptor = new VersionInfo(*vinfo); @@ -79,7 +80,7 @@ Process::Process(VersionInfoFactory * known_versions) char * wd = getcwd(NULL, 0); cerr << "Unable to retrieve version information.\n"; cerr << "File: " << real_path << endl; - cerr << "MD5: " << hash << endl; + cerr << "MD5: " << my_md5 << endl; cerr << "working dir: " << wd << endl; cerr << "length:" << length << endl; cerr << "1KB hexdump follows:" << endl; diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 1b430e5a8..1a1e754d0 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -55,15 +55,25 @@ Process::Process(VersionInfoFactory * known_versions) identified = false; my_descriptor = 0; + my_pe = 0; + + // valgrind replaces readlink for /proc/self/exe, but not open. + char self_exe[1024]; + memset(self_exe, 0, sizeof(self_exe)); + std::string self_exe_name; + if (readlink(exe_link_name, self_exe, sizeof(self_exe) - 1) < 0) + self_exe_name = exe_link_name; + else + self_exe_name = self_exe; md5wrapper md5; uint32_t length; uint8_t first_kb [1024]; memset(first_kb, 0, sizeof(first_kb)); // get hash of the running DF process - string hash = md5.getHashFromFile(exe_link_name, length, (char *) first_kb); + my_md5 = md5.getHashFromFile(self_exe_name, length, (char *) first_kb); // create linux process, add it to the vector - VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash); + VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { my_descriptor = new VersionInfo(*vinfo); @@ -74,7 +84,7 @@ Process::Process(VersionInfoFactory * known_versions) char * wd = getcwd(NULL, 0); cerr << "Unable to retrieve version information.\n"; cerr << "File: " << exe_link_name << endl; - cerr << "MD5: " << hash << endl; + cerr << "MD5: " << my_md5 << endl; cerr << "working dir: " << wd << endl; cerr << "length:" << length << endl; cerr << "1KB hexdump follows:" << endl; diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index c923441e3..5474d7cfb 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -95,7 +95,8 @@ Process::Process(VersionInfoFactory * factory) { return; } - VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(d->pe_header.FileHeader.TimeDateStamp); + my_pe = d->pe_header.FileHeader.TimeDateStamp; + VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe); if(vinfo) { identified = true; @@ -105,8 +106,7 @@ Process::Process(VersionInfoFactory * factory) } else { - fprintf(stderr, "Unable to retrieve version information.\nPE timestamp: 0x%x\n", - d->pe_header.FileHeader.TimeDateStamp); + fprintf(stderr, "Unable to retrieve version information.\nPE timestamp: 0x%x\n", my_pe); fflush(stderr); } } diff --git a/library/include/BitArray.h b/library/include/BitArray.h index a507a38a4..1469091d1 100644 --- a/library/include/BitArray.h +++ b/library/include/BitArray.h @@ -164,7 +164,6 @@ namespace DFHack out << sstr.str(); return out; } - //private: uint8_t * bits; uint32_t size; }; @@ -233,4 +232,4 @@ namespace DFHack m_data[idx] = item; } }; -} \ No newline at end of file +} diff --git a/library/include/DFHackVersion.h b/library/include/DFHackVersion.h index f035e616a..67bf35df1 100644 --- a/library/include/DFHackVersion.h +++ b/library/include/DFHackVersion.h @@ -7,6 +7,7 @@ namespace DFHack { const char *git_description(); const char *git_commit(); bool is_release(); + bool is_prerelease(); } } @@ -17,4 +18,5 @@ namespace DFHack { #define DFHACK_GIT_DESCRIPTION (DFHack::Version::git_description()) #define DFHACK_GIT_COMMIT (DFHack::Version::git_commit()) #define DFHACK_IS_RELEASE (DFHack::Version::is_release()) + #define DFHACK_IS_PRERELEASE (DFHack::Version::is_prerelease()) #endif diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index f34b9cc73..cd178263c 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -197,6 +197,13 @@ INSTANTIATE_WRAPPERS(10, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10), #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 INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11)) +INSTANTIATE_WRAPPERS(11, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11), + 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);) #undef FW_TARGS #undef FW_TARGSC @@ -227,4 +234,4 @@ INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11)) // bah, but didn't have any idea how to allocate statically return new function_identity(ptr, vararg); } -} \ No newline at end of file +} diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 80844ae5f..36100b400 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -287,6 +287,9 @@ namespace DFHack EXEC = 4 }; + uint32_t getPE() { return my_pe; } + std::string getMD5() { return my_md5; } + private: VersionInfo * my_descriptor; PlatformSpecific *d; @@ -294,6 +297,8 @@ namespace DFHack uint32_t my_pid; uint32_t base; std::map classNameCache; + uint32_t my_pe; + std::string my_md5; }; class DFHACK_EXPORT ClassNameCheck diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index aba6aaa0f..42bebb2d3 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -50,7 +50,6 @@ namespace df namespace DFHack { class Core; - class PluginExports; class PluginManager; class virtual_identity; class RPCService; @@ -170,7 +169,6 @@ namespace DFHack command_result invoke(color_ostream &out, const std::string & command, std::vector & parameters); bool can_invoke_hotkey(const std::string & command, df::viewscreen *top ); plugin_state getState () const; - PluginExports *getExports(); RPCService *rpc_connect(color_ostream &out); @@ -237,16 +235,7 @@ namespace DFHack command_result (*plugin_enable)(color_ostream &, bool); RPCService* (*plugin_rpcconnect)(color_ostream &); command_result (*plugin_eval_ruby)(color_ostream &, const char*); - PluginExports* (*plugin_get_exports)(void); }; - class DFHACK_EXPORT PluginExports { - protected: - friend class Plugin; - std::map bindings; - bool bind(DFLibrary* lib); - }; - #define PLUGIN_EXPORT_BIND(sym) bindings.insert(std::pair(#sym, (void**)&this->sym)) - #define PLUGIN_EXPORT_BINDN(sym, name) bindings.insert(std::pair(name, (void**)&this->sym)) class DFHACK_EXPORT PluginManager { // PRIVATE METHODS @@ -275,7 +264,6 @@ namespace DFHack Plugin *getPluginByName (const std::string &name) { return (*this)[name]; } Plugin *getPluginByCommand (const std::string &command); - void *getPluginExports(const std::string &name); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); Plugin* operator[] (const std::string name); @@ -324,17 +312,6 @@ namespace DFHack DFhackDataExport bool plugin_is_enabled = false; \ bool &varname = plugin_is_enabled; -#define DFHACK_PLUGIN_EXPORTS(clsname) \ - DFhackCExport PluginExports* plugin_get_exports() \ - { \ - static clsname* instance = NULL; \ - if (!instance) \ - instance = new clsname; \ - return (PluginExports*)instance; \ - } -#define GET_PLUGIN_EXPORTS(plugname, clsname) \ - (clsname*)DFHack::Core::getInstance().getPluginManager()->getPluginExports(plugname) - #define DFHACK_PLUGIN_LUA_COMMANDS \ DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = #define DFHACK_PLUGIN_LUA_FUNCTIONS \ diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 645a0fac2..ae53e7603 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -38,6 +38,8 @@ distribution. #include "df/announcement_flags.h" #include "df/unit_report_type.h" +#include "modules/GuiHooks.h" + namespace df { struct viewscreen; struct job; @@ -129,6 +131,7 @@ namespace DFHack struct DwarfmodeDims { int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; int y1, y2; + int map_y1, map_y2; bool menu_on, area_on, menu_forced; rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); } @@ -150,6 +153,10 @@ namespace DFHack DFHACK_EXPORT bool setDesignationCoords (const int32_t x, const int32_t y, const int32_t z); DFHACK_EXPORT bool getMousePos (int32_t & x, int32_t & y); + + // 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 + DFHACK_EXPORT int getDepthAt (int32_t x, int32_t y); /* * Gui screens */ @@ -183,5 +190,10 @@ namespace DFHack DFHACK_EXPORT bool getMenuWidth(uint8_t & menu_width, uint8_t & area_map_width); DFHACK_EXPORT bool setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width); + + namespace Hooks { + GUI_HOOK_DECLARE(depth_at, int, (int32_t x, int32_t y)); + GUI_HOOK_DECLARE(dwarfmode_view_dims, DwarfmodeDims, ()); + } } } diff --git a/library/include/modules/GuiHooks.h b/library/include/modules/GuiHooks.h new file mode 100644 index 000000000..62f412961 --- /dev/null +++ b/library/include/modules/GuiHooks.h @@ -0,0 +1,71 @@ +#pragma once +#include +namespace DFHack { + #define GUI_HOOK_DECLARE(name, rtype, args) DFHACK_EXPORT extern DFHack::GuiHooks::Hook name + #define GUI_HOOK_DEFINE(name, base_func) DFHack::GuiHooks::Hook name(base_func) + #define GUI_HOOK_TOP(name) name.top() + #define GUI_HOOK_CALLBACK(hook, name, callback) DFHack::GuiHooks::Hook::Callback name(&hook, callback) + namespace GuiHooks { + template + class Hook { + typedef Hook T_hook; + friend class Callback; + T_func *base_func; + std::vector funcs; + void add(T_func *func) + { + if (std::find(funcs.begin(), funcs.end(), func) == funcs.end()) + funcs.push_back(func); + } + void remove(T_func *func) + { + auto it = std::find(funcs.begin(), funcs.end(), func); + if (it != funcs.end()) + funcs.erase(it); + } + public: + Hook(T_func* base) : base_func(base) + { } + T_func* top() + { + return funcs.empty() ? base_func : funcs[funcs.size() - 1]; + } + T_func* next(T_func* cur) + { + if (funcs.size()) + { + auto it = std::find(funcs.begin(), funcs.end(), cur); + if (it != funcs.end() && it != funcs.begin()) + return *(it - 1); + } + return base_func; + } + + class Callback { + T_func *func; + T_hook *hook; + bool enabled; + public: + Callback(T_hook *hook, T_func *func) : hook(hook), func(func) + { } + ~Callback() + { + disable(); + } + inline T_func *next() { return hook->next(func); } + void apply (bool enable) + { + if (enable) + hook->add(func); + else + hook->remove(func); + enabled = enable; + } + inline void enable() { apply(true); } + inline void disable() { apply(false); } + inline bool is_enabled() { return enabled; } + inline void toggle() { apply(!enabled); } + }; + }; + } +} diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 4b3950ebd..fbbed9ff3 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -72,6 +72,11 @@ namespace DFHack DFHACK_EXPORT bool linkIntoWorld(df::job *job, bool new_id = true); + // Flag this job's posting as "dead" and set its posting_index to -1 + // If remove_all is true, flag all postings pointing to this job + // Returns true if any postings were removed + DFHACK_EXPORT bool removePostings(df::job *job, bool remove_all = false); + // lists jobs with ids >= *id_var, and sets *id_var = *job_next_id; DFHACK_EXPORT bool listNewlyCreated(std::vector *pvec, int *id_var); diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index ab032c4e3..2c4a29062 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -36,6 +36,8 @@ distribution. #include "df/graphic.h" #include "df/viewscreen.h" +#include "modules/GuiHooks.h" + namespace df { struct job; @@ -52,6 +54,7 @@ namespace df namespace DFHack { class Core; + class Plugin; typedef std::set interface_key_set; @@ -182,16 +185,16 @@ namespace DFHack DFHACK_EXPORT bool inGraphicsMode(); /// Paint one screen tile with the given pen - DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y); + 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); /// 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); + DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text, bool map = false); /// Fills a rectangle with one pen. Possibly more efficient than a loop over paintTile. - DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2); + DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2, bool map = false); /// Draws a standard dark gray window border with a title string DFHACK_EXPORT bool drawBorder(const std::string &title); @@ -206,9 +209,12 @@ namespace DFHack DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL); // Push and remove viewscreens - DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); + DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL, Plugin *p = NULL); + inline bool show(df::viewscreen *screen, Plugin *p) + { return show(screen, NULL, p); } DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); + DFHACK_EXPORT bool hasActiveScreens(Plugin *p); /// Retrieve the string representation of the bound key. DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); @@ -259,35 +265,40 @@ namespace DFHack return *this; } - Painter &fill(const rect2d &area, const Pen &pen) { + Painter &fill(const rect2d &area, const Pen &pen, bool map = false) { rect2d irect = intersect(area, clip); - fillRect(pen, irect.first.x, irect.first.y, irect.second.x, irect.second.y); + fillRect(pen, irect.first.x, irect.first.y, irect.second.x, irect.second.y, map); return *this; } - Painter &fill(const rect2d &area) { return fill(area, cur_pen); } + Painter &fill(const rect2d &area, bool map = false) { return fill(area, cur_pen, map); } - Painter &tile(const Pen &pen) { - if (isValidPos()) paintTile(pen, gcursor.x, gcursor.y); + Painter &tile(const Pen &pen, bool map = false) { + if (isValidPos()) paintTile(pen, gcursor.x, gcursor.y, map); return advance(1); } - Painter &tile() { return tile(cur_pen); } - Painter &tile(char ch) { return tile(cur_pen.chtile(ch)); } - Painter &tile(char ch, int tileid) { return tile(cur_pen.chtile(ch, tileid)); } + Painter &tile(bool map = false) { return tile(cur_pen, map); } + Painter &tile(char ch, bool map = false) { return tile(cur_pen.chtile(ch), map); } + Painter &tile(char ch, int tileid, bool map = false) { return tile(cur_pen.chtile(ch, tileid), map); } - Painter &string(const std::string &str, const Pen &pen) { - do_paint_string(str, pen); return advance(str.size()); + Painter &string(const std::string &str, const Pen &pen, bool map = false) { + do_paint_string(str, pen, map); return advance(str.size()); } - Painter &string(const std::string &str) { return string(str, cur_pen); } - Painter &string(const std::string &str, int8_t fg) { return string(str, cur_pen.color(fg)); } + Painter &string(const std::string &str, bool map = false) { return string(str, cur_pen, map); } + Painter &string(const std::string &str, int8_t fg, bool map = false) { return string(str, cur_pen.color(fg), map); } - Painter &key(df::interface_key kc, const Pen &pen) { - return string(getKeyDisplay(kc), pen); + Painter &key(df::interface_key kc, const Pen &pen, bool map = false) { + return string(getKeyDisplay(kc), pen, map); } - Painter &key(df::interface_key kc) { return key(kc, cur_key_pen); } + Painter &key(df::interface_key kc, bool map = false) { return key(kc, cur_key_pen, map); } private: - void do_paint_string(const std::string &str, const Pen &pen); + void do_paint_string(const std::string &str, const Pen &pen, bool map = false); }; + + namespace Hooks { + GUI_HOOK_DECLARE(set_tile, void, (const Pen &pen, int x, int y, bool map)); + } + } class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index d706778b6..e5210013b 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -31,11 +31,13 @@ distribution. #include "Export.h" #include "modules/Items.h" #include "DataDefs.h" -#include "df/unit.h" + +#include "df/caste_raw_flags.h" +#include "df/job_skill.h" +#include "df/mental_attribute_type.h" #include "df/misc_trait_type.h" #include "df/physical_attribute_type.h" -#include "df/mental_attribute_type.h" -#include "df/job_skill.h" +#include "df/unit.h" namespace df { @@ -219,6 +221,7 @@ DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT bool isHidingCurse(df::unit *unit); DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr); DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr); +DFHACK_EXPORT bool casteFlagSet(int race, int caste, df::caste_raw_flags flag); DFHACK_EXPORT bool isCrazed(df::unit *unit); DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 0ceaeb819..a6589bac1 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -49,30 +49,35 @@ using namespace std; using namespace DFHack; #include "DataDefs.h" -#include "df/world.h" -#include "df/ui.h" -#include "df/ui_look_list.h" -#include "df/d_init.h" -#include "df/item.h" -#include "df/unit.h" -#include "df/job.h" -#include "df/job_item.h" -#include "df/general_ref_building_holderst.h" -#include "df/buildings_other_id.h" -#include "df/building_design.h" -#include "df/building_def.h" #include "df/building_axle_horizontalst.h" -#include "df/building_trapst.h" +#include "df/building_bars_floorst.h" +#include "df/building_bars_verticalst.h" #include "df/building_bridgest.h" -#include "df/building_coffinst.h" #include "df/building_civzonest.h" -#include "df/building_stockpilest.h" +#include "df/building_coffinst.h" +#include "df/building_def.h" +#include "df/building_design.h" +#include "df/building_floodgatest.h" #include "df/building_furnacest.h" -#include "df/building_workshopst.h" +#include "df/building_grate_floorst.h" +#include "df/building_grate_wallst.h" +#include "df/building_rollersst.h" #include "df/building_screw_pumpst.h" +#include "df/building_stockpilest.h" +#include "df/building_trapst.h" #include "df/building_water_wheelst.h" #include "df/building_wellst.h" -#include "df/building_rollersst.h" +#include "df/building_workshopst.h" +#include "df/buildings_other_id.h" +#include "df/d_init.h" +#include "df/general_ref_building_holderst.h" +#include "df/item.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/ui.h" +#include "df/ui_look_list.h" +#include "df/unit.h" +#include "df/world.h" using namespace df::enums; using df::global::ui; @@ -345,30 +350,62 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in switch (type) { case building_type::Well: - { - auto obj = (df::building_wellst*)bld; + { + if (VIRTUAL_CAST_VAR(obj, df::building_wellst, bld)) obj->bucket_z = bld->z; - break; - } + break; + } case building_type::Furnace: - { - auto obj = (df::building_furnacest*)bld; + { + if (VIRTUAL_CAST_VAR(obj, df::building_furnacest, bld)) obj->melt_remainder.resize(df::inorganic_raw::get_vector().size(), 0); - break; - } + break; + } case building_type::Coffin: - { - auto obj = (df::building_coffinst*)bld; + { + if (VIRTUAL_CAST_VAR(obj, df::building_coffinst, bld)) obj->initBurialFlags(); // DF has this copy&pasted - break; - } + break; + } case building_type::Trap: + { + if (VIRTUAL_CAST_VAR(obj, df::building_trapst, bld)) { - auto obj = (df::building_trapst*)bld; if (obj->trap_type == trap_type::PressurePlate) obj->ready_timeout = 500; - break; } + break; + } + case building_type::Floodgate: + { + if (VIRTUAL_CAST_VAR(obj, df::building_floodgatest, bld)) + obj->gate_flags.bits.closed = true; + break; + } + case building_type::GrateWall: + { + if (VIRTUAL_CAST_VAR(obj, df::building_grate_wallst, bld)) + obj->gate_flags.bits.closed = true; + break; + } + case building_type::GrateFloor: + { + if (VIRTUAL_CAST_VAR(obj, df::building_grate_floorst, bld)) + obj->gate_flags.bits.closed = true; + break; + } + case building_type::BarsVertical: + { + if (VIRTUAL_CAST_VAR(obj, df::building_bars_verticalst, bld)) + obj->gate_flags.bits.closed = true; + break; + } + case building_type::BarsFloor: + { + if (VIRTUAL_CAST_VAR(obj, df::building_bars_floorst, bld)) + obj->gate_flags.bits.closed = true; + break; + } default: break; } @@ -887,6 +924,21 @@ static int getMaxStockpileId() return max_id; } +static int getMaxCivzoneId() +{ + auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE]; + int max_id = 0; + + for (size_t i = 0; i < vec.size(); i++) + { + auto bld = strict_virtual_cast(vec[i]); + if (bld) + max_id = std::max(max_id, bld->zone_num); + } + + return max_id; +} + bool Buildings::constructAbstract(df::building *bld) { CHECK_NULL_POINTER(bld); @@ -903,6 +955,11 @@ bool Buildings::constructAbstract(df::building *bld) stock->stockpile_number = getMaxStockpileId() + 1; break; + case building_type::Civzone: + if (auto zone = strict_virtual_cast(bld)) + zone->zone_num = getMaxCivzoneId() + 1; + break; + default: break; } diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 0a17bbac8..ae7c97236 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -62,6 +62,7 @@ using namespace DFHack; #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_locationsst.h" #include "df/viewscreen_petst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_storesst.h" @@ -89,6 +90,7 @@ using namespace DFHack; #include "df/route_stockpile_link.h" #include "df/game_mode.h" #include "df/unit.h" +#include "df/occupation.h" using namespace df::enums; using df::global::gview; @@ -542,6 +544,11 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile) } } +DEFINE_GET_FOCUS_STRING_HANDLER(locations) +{ + focus += "/" + enum_item_key(screen->menu); +} + std::string Gui::getFocusString(df::viewscreen *top) { if (!top) @@ -835,6 +842,24 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) } } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_locationsst, top)) + { + switch (screen->menu) + { + case df::viewscreen_locationsst::AssignOccupation: + return vector_get(screen->units, screen->unit_idx); + case df::viewscreen_locationsst::Occupations: + { + auto occ = vector_get(screen->occupations, screen->occupation_idx); + if (occ) + return df::unit::find(occ->unit_id); + return NULL; + } + default: + return NULL; + } + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_petst, top)) { switch (screen->mode) @@ -1358,9 +1383,9 @@ df::coord Gui::getCursorPos() return df::coord(cursor->x, cursor->y, cursor->z); } -Gui::DwarfmodeDims Gui::getDwarfmodeViewDims() +Gui::DwarfmodeDims getDwarfmodeViewDims_default() { - DwarfmodeDims dims; + Gui::DwarfmodeDims dims; auto ws = Screen::getWindowSize(); dims.y1 = 1; @@ -1403,6 +1428,12 @@ Gui::DwarfmodeDims Gui::getDwarfmodeViewDims() return dims; } +GUI_HOOK_DEFINE(Gui::Hooks::dwarfmode_view_dims, getDwarfmodeViewDims_default); +Gui::DwarfmodeDims Gui::getDwarfmodeViewDims() +{ + return GUI_HOOK_TOP(Gui::Hooks::dwarfmode_view_dims)(); +} + void Gui::resetDwarfmodeView(bool pause) { using df::global::cursor; @@ -1524,6 +1555,17 @@ bool Gui::getMousePos (int32_t & x, int32_t & y) return (x == -1) ? false : true; } +int getDepthAt_default (int32_t x, int32_t y) +{ + return 0; +} + +GUI_HOOK_DEFINE(Gui::Hooks::depth_at, getDepthAt_default); +int Gui::getDepthAt (int32_t x, int32_t y) +{ + return GUI_HOOK_TOP(Gui::Hooks::depth_at)(x, y); +} + bool Gui::getWindowSize (int32_t &width, int32_t &height) { if (gps) { diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 0c29d851b..b4539abdd 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1366,12 +1366,13 @@ int32_t Items::createItem(df::item_type item_type, int16_t item_subtype, int16_t } //makeItem + vector out_products; vector out_items; vector in_reag; vector in_items; df::enums::game_type::game_type type = *df::global::gametype; - prod->produce(unit, &out_items, &in_reag, &in_items, 1, job_skill::NONE, + 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); if ( out_items.size() != 1 ) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 1cfc0fa78..af2fcb4e8 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -377,6 +377,34 @@ bool DFHack::Job::linkIntoWorld(df::job *job, bool new_id) } } +bool DFHack::Job::removePostings(df::job *job, bool remove_all) +{ + using df::global::world; + CHECK_NULL_POINTER(job); + bool removed = false; + if (!remove_all) + { + if (job->posting_index >= 0 && job->posting_index < world->job_postings.size()) + { + world->job_postings[job->posting_index]->flags.bits.dead = true; + removed = true; + } + } + else + { + for (auto it = world->job_postings.begin(); it != world->job_postings.end(); ++it) + { + if ((**it).job == job) + { + (**it).flags.bits.dead = true; + removed = true; + } + } + } + job->posting_index = -1; + return removed; +} + bool DFHack::Job::listNewlyCreated(std::vector *pvec, int *id_var) { using df::global::world; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 517414766..71103ffda 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -32,6 +32,7 @@ distribution. using namespace std; #include "modules/Screen.h" +#include "modules/GuiHooks.h" #include "MemAccess.h" #include "VersionInfo.h" #include "Types.h" @@ -93,8 +94,9 @@ bool Screen::inGraphicsMode() return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS); } -static void doSetTile(const Pen &pen, int index) +static void doSetTile_default(const Pen &pen, int x, int y, bool map) { + int index = ((x * gps->dimy) + y); auto screen = gps->screen + index*4; screen[0] = uint8_t(pen.ch); screen[1] = uint8_t(pen.fg) & 15; @@ -107,14 +109,20 @@ static void doSetTile(const Pen &pen, int index) gps->screentexpos_cbr[index] = pen.tile_bg; } -bool Screen::paintTile(const Pen &pen, int x, int y) +GUI_HOOK_DEFINE(Screen::Hooks::set_tile, doSetTile_default); +static void doSetTile(const Pen &pen, int x, int y, bool map) +{ + 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*dim.y + y); + doSetTile(pen, x, y, map); return true; } @@ -153,7 +161,7 @@ Pen Screen::readTile(int x, int y) return pen; } -bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) +bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text, bool map) { auto dim = getWindowSize(); if (!gps || y < 0 || y >= dim.y) return false; @@ -168,14 +176,14 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) tmp.ch = text[i]; tmp.tile = (pen.tile ? pen.tile + uint8_t(text[i]) : 0); - paintTile(tmp, x+i, y); + paintTile(tmp, x+i, y, map); ok = true; } return ok; } -bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) +bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2, bool map) { auto dim = getWindowSize(); if (!gps || !pen.valid()) return false; @@ -188,10 +196,8 @@ bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) for (int x = x1; x <= x2; x++) { - int index = x*dim.y; - for (int y = y1; y <= y2; y++) - doSetTile(pen, index+y); + doSetTile(pen, x, y, map); } return true; @@ -208,13 +214,13 @@ bool Screen::drawBorder(const std::string &title) for (int x = 0; x < dim.x; x++) { - doSetTile(border, x * dim.y + 0); - doSetTile(border, x * dim.y + dim.y - 1); + doSetTile(border, x, 0, false); + doSetTile(border, x, dim.y - 1, false); } for (int y = 0; y < dim.y; y++) { - doSetTile(border, 0 * dim.y + y); - doSetTile(border, (dim.x - 1) * dim.y + y); + doSetTile(border, 0, y, false); + doSetTile(border, dim.x - 1, y, false); } paintString(signature, dim.x-8, dim.y-1, "DFHack"); @@ -241,7 +247,7 @@ bool Screen::invalidate() const Pen Screen::Painter::default_pen(0,COLOR_GREY,0); const Pen Screen::Painter::default_key_pen(0,COLOR_LIGHTGREEN,0); -void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen) +void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen, bool map) { if (gcursor.y < clip.first.y || gcursor.y > clip.second.y) return; @@ -250,7 +256,7 @@ void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen) int len = std::min((int)str.size(), int(clip.second.x - gcursor.x + 1)); if (len > dx) - paintString(pen, gcursor.x + dx, gcursor.y, str.substr(dx, len-dx)); + paintString(pen, gcursor.x + dx, gcursor.y, str.substr(dx, len-dx), map); } bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) @@ -276,7 +282,9 @@ bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *pt return false; } -bool Screen::show(df::viewscreen *screen, df::viewscreen *before) +static std::map plugin_screens; + +bool Screen::show(df::viewscreen *screen, df::viewscreen *before, Plugin *plugin) { CHECK_NULL_POINTER(screen); CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child); @@ -300,6 +308,9 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before) if (dfhack_viewscreen::is_instance(screen)) static_cast(screen)->onShow(); + if (plugin) + plugin_screens[screen] = plugin; + return true; } @@ -307,6 +318,10 @@ void Screen::dismiss(df::viewscreen *screen, bool to_first) { CHECK_NULL_POINTER(screen); + auto it = plugin_screens.find(screen); + if (it != plugin_screens.end()) + plugin_screens.erase(it); + if (screen->breakdown_level != interface_breakdown_types::NONE) return; @@ -326,6 +341,21 @@ bool Screen::isDismissed(df::viewscreen *screen) return screen->breakdown_level != interface_breakdown_types::NONE; } +bool Screen::hasActiveScreens(Plugin *plugin) +{ + if (plugin_screens.empty()) + return false; + df::viewscreen *screen = &gview->view; + while (screen) + { + auto it = plugin_screens.find(screen); + if (it != plugin_screens.end() && it->second == plugin) + return true; + screen = screen->child; + } + return false; +} + #ifdef _LINUX // Link to the libgraphics class directly: class DFHACK_EXPORT enabler_inputst { diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 4bd2b49aa..08c2191ec 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -680,7 +680,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) return std::max(0, value); } -static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) +bool Units::casteFlagSet(int race, int caste, df::caste_raw_flags flag) { auto creature = df::creature_raw::find(race); if (!creature) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 66390da6e..3be400515 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -78,7 +78,9 @@ uint32_t World::ReadCurrentYear() uint32_t World::ReadCurrentTick() { - return DF_GLOBAL_VALUE(cur_year_tick, 0); + // prevent this from returning anything less than 0, + // to avoid day/month calculations with 0xffffffff + return std::max(0, DF_GLOBAL_VALUE(cur_year_tick, 0)); } bool World::ReadGameMode(t_gamemodes& rd) diff --git a/library/xml b/library/xml index 378a580f7..49eff8dca 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 378a580f7e333607a64a301d598e3885954a5d9d +Subproject commit 49eff8dca662bff83ff2191ac5b611504c72ae1c diff --git a/package/linux/dfhack b/package/linux/dfhack index 09fc52c15..2b2d04465 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -15,6 +15,7 @@ # DF_VALGRIND_OPTS: Options to pass to valgrind, if it's being run # DF_HELGRIND_OPTS: Options to pass to helgrind, if it's being run # DF_POST_CMD: Shell command to be run at very end of script +# DFHACK_NO_RENAME_LIBSTDCXX: Non-empty to prevent automatically renaming libstdc++ DF_DIR=$(dirname "$0") cd "${DF_DIR}" @@ -31,9 +32,31 @@ if [ -r "./$RC" ]; then . "./$RC" fi +# Disable bundled libstdc++ +libcxx_orig="libs/libstdc++.so.6" +libcxx_backup="libs/libstdc++.so.6.backup" +if [ -z "${DFHACK_NO_RENAME_LIBSTDCXX:-}" ] && [ -e "$libcxx_orig" ] && [ ! -e "$libcxx_backup" ]; then + mv "$libcxx_orig" "$libcxx_backup" + cat < *out_items, + (df::unit *unit, + std::vector *out_products, + std::vector *out_items, std::vector *in_reag, std::vector *in_items, int32_t quantity, df::job_skill skill, @@ -293,7 +295,7 @@ struct product_hook : improvement_product { return; } - INTERPOSE_NEXT(produce)(unit, 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, site); } }; diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index f7ba3d300..cbb5db0b3 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -600,7 +600,7 @@ struct autochop_hook : public df::viewscreen_dwarfmodest if (isInDesignationMenu() && input->count(interface_key::CUSTOM_C)) { sendKey(interface_key::LEAVESCREEN); - Screen::show(new ViewscreenAutochop()); + Screen::show(new ViewscreenAutochop(), plugin_self); } else { @@ -643,7 +643,7 @@ command_result df_autochop (color_ostream &out, vector & parameters) return CR_WRONG_USAGE; } if (Maps::IsValid()) - Screen::show(new ViewscreenAutochop()); + Screen::show(new ViewscreenAutochop(), plugin_self); return CR_OK; } diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp new file mode 100644 index 000000000..1079dff44 --- /dev/null +++ b/plugins/autogems.cpp @@ -0,0 +1,301 @@ +/* + * Autogems plugin. + * Creates a new Workshop Order setting, automatically cutting rough gems. + * For best effect, include "enable autogems" in your dfhack.init configuration. + */ + +#include "uicommon.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "df/building_workshopst.h" +#include "df/buildings_other_id.h" +#include "df/builtin_mats.h" +#include "df/general_ref_building_holderst.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/viewscreen_dwarfmodest.h" + +#define CONFIG_KEY "autogems/config" +#define DELTA_TICKS 1200 +#define MAX_WORKSHOP_JOBS 10 + +using namespace DFHack; + +DFHACK_PLUGIN("autogems"); +DFHACK_PLUGIN_IS_ENABLED(enabled); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); + +typedef int32_t item_id; +typedef int32_t mat_index; +typedef std::map gem_map; + +bool running = false; +decltype(world->frame_counter) last_frame_count = 0; +const char *tagline = "Creates a new Workshop Order setting, automatically cutting rough gems."; +const char *usage = ( + " enable autogems\n" + " Enable the plugin.\n" + " disable autogems\n" + " Disable the plugin.\n" + "\n" + "While enabled, the Current Workshop Orders screen (o-W) have a new option:\n" + " g: Auto Cut Gems\n" + "\n" + "While this option is enabled, jobs will be created in Jeweler's Workshops\n" + "to cut any accessible rough gems.\n" +); + +void add_task(mat_index gem_type, df::building_workshopst *workshop) { + // Create a single task in the specified workshop. + // Partly copied from Buildings::linkForConstruct(); perhaps a refactor is in order. + + auto ref = df::allocate(); + if (!ref) { + std::cerr << "Could not allocate general_ref_building_holderst" << std::endl; + return; + } + + ref->building_id = workshop->id; + + auto item = new df::job_item(); + if (!item) { + std::cerr << "Could not allocate job_item" << std::endl; + return; + } + + item->item_type = df::item_type::ROUGH; + item->mat_type = df::builtin_mats::INORGANIC; + item->mat_index = gem_type; + item->quantity = 1; + item->vector_id = df::job_item_vector_id::ROUGH; + + auto job = new df::job(); + if (!job) { + std::cerr << "Could not allocate job" << std::endl; + return; + } + + job->job_type = df::job_type::CutGems; + job->pos = df::coord(workshop->centerx, workshop->centery, workshop->z); + job->mat_type = df::builtin_mats::INORGANIC; + job->mat_index = gem_type; + job->general_refs.push_back(ref); + job->job_items.push_back(item); + + workshop->jobs.push_back(job); + Job::linkIntoWorld(job); +} + +void add_tasks(gem_map &gem_types, df::building_workshopst *workshop) { + int slots = MAX_WORKSHOP_JOBS - workshop->jobs.size(); + if (slots <= 0) { + return; + } + + for (auto g = gem_types.begin(); g != gem_types.end() && slots > 0; ++g) { + while (g->second > 0 && slots > 0) { + add_task(g->first, workshop); + g->second -= 1; + slots -= 1; + } + } +} + +void create_jobs() { + // Creates jobs in Jeweler's Workshops as necessary. + // Todo: Consider path availability? + std::set stockpiled; + std::set unlinked; + gem_map available; + auto workshops = &world->buildings.other[df::buildings_other_id::WORKSHOP_JEWELER]; + + for (auto w = workshops->begin(); w != workshops->end(); ++w) { + auto workshop = virtual_cast(*w); + auto links = workshop->links.take_from_pile; + + if (workshop->construction_stage < 3) { + // Construction in progress. + continue; + } + + if (workshop->jobs.size() == 1 && workshop->jobs[0]->job_type == df::job_type::DestroyBuilding) { + // Queued for destruction. + continue; + } + + if (links.size() > 0) { + for (auto l = links.begin(); l != links.end() && workshop->jobs.size() <= MAX_WORKSHOP_JOBS; ++l) { + auto stockpile = virtual_cast(*l); + gem_map piled; + + Buildings::StockpileIterator stored; + for (stored.begin(stockpile); !stored.done(); ++stored) { + auto item = *stored; + if (item->getType() == item_type::ROUGH && item->getMaterial() == builtin_mats::INORGANIC) { + stockpiled.insert(item->id); + piled[item->getMaterialIndex()] += 1; + } + } + + // Decrement current jobs from all linked workshops, not just this one. + auto outbound = stockpile->links.give_to_workshop; + for (auto ws = outbound.begin(); ws != outbound.end(); ++ws) { + auto shop = virtual_cast(*ws); + for (auto j = shop->jobs.begin(); j != shop->jobs.end(); ++j) { + auto job = *j; + if (job->job_type == df::job_type::CutGems) { + if (job->flags.bits.repeat) { + piled[job->mat_index] = 0; + } else { + piled[job->mat_index] -= 1; + } + } + } + } + + add_tasks(piled, workshop); + } + } else { + // Note which gem types have already been ordered to be cut. + for (auto j = workshop->jobs.begin(); j != workshop->jobs.end(); ++j) { + auto job = *j; + if (job->job_type == df::job_type::CutGems) { + available[job->mat_index] -= job->flags.bits.repeat? 100: 1; + } + } + + if (workshop->jobs.size() <= MAX_WORKSHOP_JOBS) { + unlinked.insert(workshop); + } + } + } + + if (unlinked.size() > 0) { + // Count how many gems of each type are available to be cut. + // Gems in stockpiles linked to specific workshops don't count. + auto gems = world->items.other[items_other_id::ROUGH]; + for (auto g = gems.begin(); g != gems.end(); ++g) { + auto item = *g; + // ROUGH also includes raw glass; the INORGANIC check filters that out. + if (item->getMaterial() == builtin_mats::INORGANIC && !stockpiled.count(item->id)) { + available[item->getMaterialIndex()] += 1; + } + } + + for (auto w = unlinked.begin(); w != unlinked.end(); ++w) { + add_tasks(available, *w); + } + } +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (running && (world->frame_counter - last_frame_count >= DELTA_TICKS)) { + last_frame_count = world->frame_counter; + create_jobs(); + } + + return CR_OK; +} + + +/* + * Interface hooks + */ +struct autogem_hook : public df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + bool in_menu() { + // Determines whether we're looking at the Workshop Orders screen. + return ui->main.mode == ui_sidebar_mode::OrdersWorkshop; + } + + bool handleInput(std::set *input) { + if (!in_menu()) { + return false; + } + + if (input->count(interface_key::CUSTOM_G)) { + // Toggle whether gems are auto-cut for this fort. + auto config = World::GetPersistentData(CONFIG_KEY, NULL); + if (config.isValid()) { + config.ival(0) = running; + } + + running = !running; + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { + if (!handleInput(input)) { + INTERPOSE_NEXT(feed)(input); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + if (in_menu()) { + auto dims = Gui::getDwarfmodeViewDims(); + int x = dims.menu_x1 + 1; + int y = dims.y1 + 12; + Screen::Pen pen = Screen::readTile(x, y); + + while (pen.valid() && pen.ch != ' ') { + pen = Screen::readTile(x, ++y); + } + + if (pen.valid()) { + OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED); + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render); + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_MAP_LOADED) { + if (enabled && World::isFortressMode()) { + // Determine whether auto gem cutting has been disabled for this fort. + auto config = World::GetPersistentData(CONFIG_KEY); + running = !(config.isValid() && config.ival(0)); + last_frame_count = world->frame_counter; + } + } else if (event == DFHack::SC_MAP_UNLOADED) { + running = false; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { + if (enable != enabled) { + if (!INTERPOSE_HOOK(autogem_hook, feed).apply(enable) || !INTERPOSE_HOOK(autogem_hook, render).apply(enable)) { + out.printerr("Could not %s autogem hooks!\n", enable? "insert": "remove"); + return CR_FAILURE; + } + + enabled = enable; + running = enabled && World::isFortressMode(); + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + return plugin_enable(out, false); +} diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index 9814b678e..d7b233195 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -521,7 +521,9 @@ static const struct labor_default default_labor_infos[] = { /* HAUL_WATER */ {HAULERS, 0}, /* GELD */ {ALLOW, 0}, /* BUILD_ROAD */ {HAULERS, 0}, - /* BUILD_CONSTRUCTION */ {HAULERS, 0} + /* BUILD_CONSTRUCTION */ {HAULERS, 0}, + /* PAPERMAKING */ {ALLOW, 0}, + /* BOOKBINDING */ {ALLOW, 0} }; /** @@ -1134,7 +1136,7 @@ command_result autohauler (color_ostream &out, std::vector & param out << "All labors reset." << endl; return CR_OK; } - else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") + else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) { if (!enable_autohauler) { diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 21b979416..070744de0 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -489,7 +489,9 @@ static const struct labor_default default_labor_infos[] = { /* HAUL_WATER */ {HAULERS, false, 1, 200, 0}, /* GELD */ {AUTOMATIC, false, 1, 200, 0}, /* BUILD_ROAD */ {AUTOMATIC, false, 1, 200, 0}, - /* BUILD_CONSTRUCTION */ {AUTOMATIC, false, 1, 200, 0} + /* BUILD_CONSTRUCTION */ {AUTOMATIC, false, 1, 200, 0}, + /* PAPERMAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* BOOKBINDING */ {AUTOMATIC, false, 1, 200, 0} }; static const int responsibility_penalties[] = { @@ -1509,7 +1511,7 @@ command_result autolabor (color_ostream &out, std::vector & parame out << "All labors reset." << endl; return CR_OK; } - else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") + else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) { if (!enable_autolabor) { diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index b9047a14e..badc094c9 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -2,6 +2,8 @@ //By cdombroski //Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort +#include + #include #include diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index c933ce11b..93efd60f6 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -241,13 +241,13 @@ void ViewscreenChooseMaterial::render() int32_t y = gps->dimy - 3; int32_t x = 2; - OutputHotkeyString(x, y, "Toggle", "Enter"); + OutputHotkeyString(x, y, "Toggle", interface_key::SELECT); x += 3; - OutputHotkeyString(x, y, "Save", "Shift-Enter"); + OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT); x += 3; - OutputHotkeyString(x, y, "Clear", "C"); + OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C); x += 3; - OutputHotkeyString(x, y, "Cancel", "Esc"); + OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN); } // START Room Reservation @@ -538,13 +538,12 @@ void Planner::initialize() add_building_type(Chair, CHAIR); add_building_type(Coffin, COFFIN); add_building_type(Door, DOOR); - // add_building_type(Floodgate, FLOODGATE); not displayed before or after being built + add_building_type(Floodgate, FLOODGATE); add_building_type(Hatch, HATCH_COVER); - // not displayed before or after being built: - // add_building_type(GrateWall, GRATE); - // add_building_type(GrateFloor, GRATE); - // add_building_type(BarsVertical, BAR); - // add_building_type(BarsFloor, BAR); + add_building_type(GrateWall, GRATE); + add_building_type(GrateFloor, GRATE); + add_building_type(BarsVertical, BAR); + add_building_type(BarsFloor, BAR); add_building_type(Cabinet, CABINET); add_building_type(Box, BOX); // skip kennels, farm plot diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ddc804897..6f833ba6d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -161,7 +161,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (input->count(interface_key::CUSTOM_SHIFT_M)) { - Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type))); + Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type)), plugin_self); } else if (input->count(interface_key::CUSTOM_SHIFT_Q)) { diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index 2ac4d3af9..042d5f642 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -56,7 +56,7 @@ public: df::building* getSelectedBuilding() { return Gui::getAnyBuilding(parent); } std::string getFocusString() { return "commandprompt"; } - viewscreen_commandpromptst(std::string entry):is_response(false) + viewscreen_commandpromptst(std::string entry):is_response(false), submitted(false) { show_fps=gps->display_frames; gps->display_frames=0; @@ -127,6 +127,7 @@ protected: std::list > responses; int cursor_pos; int history_idx; + bool submitted; bool is_response; bool show_fps; int frame; @@ -194,6 +195,9 @@ void viewscreen_commandpromptst::submit() Screen::dismiss(this); return; } + if(submitted) + return; + submitted = true; prompt_ostream out(this); Core::getInstance().runCommand(out, get_entry()); if(out.empty() && responses.empty()) @@ -314,7 +318,7 @@ command_result show_prompt(color_ostream &out, std::vector & param std::string params; for(size_t i=0;i #include +#include +#include + #include "Console.h" #include "Core.h" #include "DataDefs.h" +#include "Error.h" #include "Export.h" +#include "LuaTools.h" +#include "LuaWrapper.h" #include "PluginManager.h" #include "VTableInterpose.h" -#include "uicommon.h" #include "modules/Gui.h" +#include "uicommon.h" #include "df/building_tradedepotst.h" #include "df/general_ref.h" #include "df/general_ref_contained_in_itemst.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_locationsst.h" #include "df/viewscreen_tradegoodsst.h" using namespace DFHack; @@ -30,51 +36,208 @@ typedef std::set ikey_set; command_result df_confirm (color_ostream &out, vector & parameters); struct conf_wrapper; -static std::map confirmations; +static std::map confirmations; +string active_id; +std::queue cmds; template -bool in_vector (std::vector &vec, FT item) +inline bool in_vector (std::vector &vec, FT item) { return std::find(vec.begin(), vec.end(), item) != vec.end(); } -#define goods_selected_func(list) \ -static bool list##_goods_selected(df::viewscreen_tradegoodsst *screen) \ -{ \ - for (auto it = screen->list##_selected.begin(); it != screen->list##_selected.end(); ++it) \ - if (*it) return true; \ - return false; \ +string char_replace (string s, char a, char b) +{ + string res = s; + size_t i = res.size(); + while (i--) + if (res[i] == a) + res[i] = b; + return res; } -goods_selected_func(trader); -goods_selected_func(broker); -#undef goods_selected_func -#define goods_all_selected_func(list) \ -static bool list##_goods_all_selected(df::viewscreen_tradegoodsst *screen) \ -{ \ - for (size_t i = 0; i < screen->list##_selected.size(); ++i) \ - { \ - if (!screen->list##_selected[i]) \ - { \ - std::vector *refs = &screen->list##_items[i]->general_refs; \ - bool in_container = false; \ - for (auto it = refs->begin(); it != refs->end(); ++it) \ - { \ - if (virtual_cast(*it)) \ - { \ - in_container = true; \ - break; \ - } \ - } \ - if (!in_container) \ - return false; \ - } \ - } \ - return true; \ +bool set_conf_state (string name, bool state); + +struct conf_wrapper { +private: + bool enabled; + std::set hooks; +public: + conf_wrapper() + :enabled(false) + {} + void add_hook(VMethodInterposeLinkBase *hook) + { + if (!hooks.count(hook)) + hooks.insert(hook); + } + bool apply (bool state) { + if (state == enabled) + return true; + for (auto h = hooks.begin(); h != hooks.end(); ++h) + { + if (!(**h).apply(state)) + return false; + } + enabled = state; + return true; + } + inline bool is_enabled() { return enabled; } +}; + +namespace trade { + static bool goods_selected (const std::vector &selected) + { + for (auto it = selected.begin(); it != selected.end(); ++it) + if (*it) + return true; + return false; + } + inline bool trader_goods_selected (df::viewscreen_tradegoodsst *screen) + { + CHECK_NULL_POINTER(screen); + return goods_selected(screen->trader_selected); + } + inline bool broker_goods_selected (df::viewscreen_tradegoodsst *screen) + { + CHECK_NULL_POINTER(screen); + return goods_selected(screen->broker_selected); + } + + static bool goods_all_selected(const std::vector &selected, const std::vector &items) \ + { + for (size_t i = 0; i < selected.size(); ++i) + { + if (!selected[i]) + { + // 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) + { + if (virtual_cast(*it)) + { + in_container = true; + break; + } + } + if (!in_container) + return false; + } + } + return true; + } + inline bool trader_goods_all_selected(df::viewscreen_tradegoodsst *screen) + { + CHECK_NULL_POINTER(screen); + return goods_all_selected(screen->trader_selected, screen->trader_items); + } + inline bool broker_goods_all_selected(df::viewscreen_tradegoodsst *screen) + { + CHECK_NULL_POINTER(screen); + return goods_all_selected(screen->broker_selected, screen->broker_items); + } +} + +namespace conf_lua { + static color_ostream_proxy *out; + static lua_State *l_state; + bool init (color_ostream &dfout) + { + out = new color_ostream_proxy(Core::getInstance().getConsole()); + l_state = Lua::Open(*out); + return l_state; + } + void cleanup() + { + if (out) + { + delete out; + out = NULL; + } + lua_close(l_state); + } + bool call (const char *func, int nargs = 0, int nres = 0) + { + if (!Lua::PushModulePublic(*out, l_state, "plugins.confirm", func)) + return false; + if (nargs > 0) + lua_insert(l_state, lua_gettop(l_state) - nargs); + return Lua::SafeCall(*out, l_state, nargs, nres); + } + bool simple_call (const char *func) + { + Lua::StackUnwinder top(l_state); + return call(func, 0, 0); + } + template + void push (T val) + { + 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); + return 1; + } + int get_conf_data (lua_State *L) + { + lua_newtable(L); + int i = 1; + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + { + Lua::Push(L, i++); + lua_newtable(L); + table_set(L, "id", it->first); + table_set(L, "enabled", it->second->is_enabled()); + lua_settable(L, -3); + } + return 1; + } + int get_active_id (lua_State *L) + { + if (active_id.size()) + Lua::Push(L, active_id); + else + lua_pushnil(L); + return 1; + } + } +} + +#define CONF_LUA_FUNC(ns, name) {#name, df::wrap_function(ns::name, true)} +DFHACK_PLUGIN_LUA_FUNCTIONS { + CONF_LUA_FUNC( , set_conf_state), + CONF_LUA_FUNC(trade, broker_goods_selected), + CONF_LUA_FUNC(trade, broker_goods_all_selected), + CONF_LUA_FUNC(trade, trader_goods_selected), + CONF_LUA_FUNC(trade, trader_goods_all_selected), + DFHACK_LUA_END +}; + +#define CONF_LUA_CMD(name) {#name, conf_lua::api::name} +DFHACK_PLUGIN_LUA_COMMANDS { + CONF_LUA_CMD(get_ids), + CONF_LUA_CMD(get_conf_data), + CONF_LUA_CMD(get_active_id), + DFHACK_LUA_END +}; + +void show_options() +{ + cmds.push("gui/confirm-opts"); } -goods_all_selected_func(trader); -goods_all_selected_func(broker); -#undef goods_all_selected_func template class confirmation { @@ -82,6 +245,14 @@ public: enum cstate { INACTIVE, ACTIVE, SELECTED }; typedef T screen_type; screen_type *screen; + void set_state (cstate s) + { + state = s; + if (s == INACTIVE) + active_id = ""; + else + active_id = get_id(); + } bool feed (ikey_set *input) { if (state == INACTIVE) { @@ -90,7 +261,7 @@ public: if (intercept_key(*it)) { last_key = *it; - state = ACTIVE; + set_state(ACTIVE); return true; } } @@ -99,9 +270,11 @@ public: else if (state == ACTIVE) { if (input->count(df::interface_key::LEAVESCREEN)) - state = INACTIVE; + set_state(INACTIVE); else if (input->count(df::interface_key::SELECT)) - state = SELECTED; + set_state(SELECTED); + else if (input->count(df::interface_key::CUSTOM_S)) + show_options(); return true; } return false; @@ -123,7 +296,7 @@ public: if (state == ACTIVE) { split_string(&lines, get_message(), "\n"); - size_t max_length = 30; + size_t max_length = 40; for (auto it = lines.begin(); it != lines.end(); ++it) max_length = std::max(max_length, it->size()); int width = max_length + 4; @@ -152,11 +325,15 @@ public: Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (gps->dimx / 2) - (title.size() / 2), y1, title); int x = x1 + 2; - OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); - OutputString(COLOR_WHITE, x, y2, ": Cancel"); + int y = y2; + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); + OutputString(COLOR_WHITE, x, y, ": Cancel"); + x = (gps->dimx - (Screen::getKeyDisplay(df::interface_key::CUSTOM_S) + ": Settings").size()) / 2 + 1; + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S)); + OutputString(COLOR_WHITE, x, y, ": Settings"); x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); - OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); - OutputString(COLOR_WHITE, x, y2, ": Ok"); + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT)); + OutputString(COLOR_WHITE, x, y, ": Ok"); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); for (size_t i = 0; i < lines.size(); i++) { @@ -168,45 +345,64 @@ public: ikey_set tmp; tmp.insert(last_key); screen->feed(&tmp); - state = INACTIVE; + set_state(INACTIVE); } } - virtual bool intercept_key (df::interface_key key) = 0; - virtual string get_title() { return "Confirm"; } - virtual string get_message() = 0; - virtual UIColor get_color() { return COLOR_YELLOW; } + virtual string get_id() = 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) + { + CONF_LUA_START; + push(key); + if (call("intercept_key", 3, 1)) + return lua_toboolean(l_state, -1); + else + return false; + }; + string get_title() + { + CONF_LUA_START; + if (call("get_title", 2, 1) && lua_isstring(l_state, -1)) + return lua_tostring(l_state, -1); + else + return "Confirm"; + } + string get_message() + { + CONF_LUA_START; + if (call("get_message", 2, 1) && lua_isstring(l_state, -1)) + return lua_tostring(l_state, -1); + else + return ""; + }; + UIColor get_color() + { + CONF_LUA_START; + if (call("get_color", 2, 1) && lua_isnumber(l_state, -1)) + return lua_tointeger(l_state, -1) % 16; + else + return COLOR_YELLOW; + } + #undef CONF_LUA_START protected: cstate state; df::interface_key last_key; }; -struct conf_wrapper { - bool enabled; - std::set hooks; - - conf_wrapper() - :enabled(false) - {} - void add_hook(VMethodInterposeLinkBase *hook) - { - if (!hooks.count(hook)) - hooks.insert(hook); - } - bool apply (bool state) { - if (state == enabled) - return true; - for (auto h = hooks.begin(); h != hooks.end(); ++h) - { - if (!(**h).apply(state)) - return false; - } - enabled = state; - return true; - } -}; +template +int conf_register(confirmation *c, ...) +{ + 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*)) + w->add_hook(hook); + va_end(args); + return 0; +} -#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) -#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ +#define IMPLEMENT_CONFIRMATION_HOOKS(cls, prio) \ static cls cls##_instance; \ struct cls##_hooks : cls::screen_type { \ typedef cls::screen_type interpose_base; \ @@ -229,200 +425,41 @@ 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); - -class trade_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return key == df::interface_key::TRADE_TRADE; - } - virtual string get_id() { return "trade"; } - virtual string get_title() { return "Confirm trade"; } - virtual string get_message() - { - if (trader_goods_selected(screen) && broker_goods_selected(screen)) - return "Are you sure you want to trade the selected goods?"; - else if (trader_goods_selected(screen)) - return "You are not giving any items. This is likely\n" - "to irritate the merchants.\n" - "Attempt to trade anyway?"; - else if (broker_goods_selected(screen)) - return "You are not receiving any items. You may want to\n" - "offer these items instead or choose items to receive.\n" - "Attempt to trade anyway?"; - else - return "No items are selected. This is likely\n" - "to irritate the merchants.\n" - "Attempt to trade anyway?"; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation); - -class trade_cancel_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return key == df::interface_key::LEAVESCREEN && - (trader_goods_selected(screen) || broker_goods_selected(screen)); - } - virtual string get_id() { return "trade-cancel"; } - virtual string get_title() { return "Cancel trade"; } - virtual string get_message() { return "Are you sure you want leave this screen?\nSelected items will not be saved."; } -}; -IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1); - -class trade_seize_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return trader_goods_selected(screen) && key == df::interface_key::TRADE_SEIZE; - } - virtual string get_id() { return "trade-seize"; } - virtual string get_title() { return "Confirm seize"; } - virtual string get_message() { return "Are you sure you want to sieze these goods?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); - -class trade_offer_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return broker_goods_selected(screen) && key == df::interface_key::TRADE_OFFER; - } - virtual string get_id() { return "trade-offer"; } - virtual string get_title() { return "Confirm offer"; } - virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); - -class trade_select_all_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - if (key == df::interface_key::SEC_SELECT) - { - if (screen->in_right_pane && broker_goods_selected(screen) && !broker_goods_all_selected(screen)) - return true; - else if (!screen->in_right_pane && trader_goods_selected(screen) && !trader_goods_all_selected(screen)) - return true; - } - return false; - } - virtual string get_id() { return "trade-select-all"; } - virtual string get_title() { return "Confirm selection"; } - virtual string get_message() - { - return "Selecting all goods will overwrite your current selection\n" - "and cannot be undone. Continue?"; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_select_all_confirmation); - -class hauling_route_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - if (ui->main.mode == ui_sidebar_mode::Hauling && ui->hauling.view_routes.size()) - return key == df::interface_key::D_HAULING_REMOVE; - return false; - } - virtual string get_id() { return "haul-delete"; } - virtual string get_title() { return "Confirm deletion"; } - virtual string get_message() - { - std::string type = (ui->hauling.view_stops[ui->hauling.cursor_top]) ? "stop" : "route"; - return std::string("Are you sure you want to delete this ") + type + "?"; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(hauling_route_delete_confirmation); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); \ +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); -class depot_remove_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - df::building_tradedepotst *depot = virtual_cast(Gui::getAnyBuilding(screen)); - if (depot && key == df::interface_key::DESTROYBUILDING) - { - for (auto it = ui->caravans.begin(); it != ui->caravans.end(); ++it) - { - if ((**it).time_remaining) - return true; - } - } - return false; - } - virtual string get_id() { return "depot-remove"; } - virtual string get_title() { return "Confirm depot removal"; } - virtual string get_message() - { - return "Are you sure you want to remove this depot?\n" - "Merchants are present and will lose profits."; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation); +#define DEFINE_CONFIRMATION(cls, screen, prio) \ + class confirmation_##cls : public confirmation { \ + virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ + }; \ + IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, prio); -class squad_disband_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return screen->num_squads && key == df::interface_key::D_MILITARY_DISBAND_SQUAD; - } - virtual string get_id() { return "squad-disband"; } - virtual string get_title() { return "Disband squad"; } - virtual string get_message() { return "Are you sure you want to disband this squad?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(squad_disband_confirmation); - -class note_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return ui->main.mode == ui_sidebar_mode::NotesPoints && key == df::interface_key::D_NOTE_DELETE; - } - virtual string get_id() { return "note-delete"; } - virtual string get_title() { return "Delete note"; } - virtual string get_message() { return "Are you sure you want to delete this note?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(note_delete_confirmation); - -class route_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE; - } - virtual string get_id() { return "route-delete"; } - virtual string get_title() { return "Delete route"; } - virtual string get_message() { return "Are you sure you want to delete this route?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); - -#define CHOOK(cls) \ - HOOK_ACTION(cls, render) \ - HOOK_ACTION(cls, feed) \ - HOOK_ACTION(cls, key_conflict) - -#define CHOOKS \ - CHOOK(trade_confirmation) \ - CHOOK(trade_cancel_confirmation) \ - CHOOK(trade_seize_confirmation) \ - CHOOK(trade_offer_confirmation) \ - CHOOK(trade_select_all_confirmation) \ - CHOOK(hauling_route_delete_confirmation) \ - CHOOK(depot_remove_confirmation) \ - CHOOK(squad_disband_confirmation) \ - CHOOK(note_delete_confirmation) \ - CHOOK(route_delete_confirmation) +/* 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); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { -#define HOOK_ACTION(cls, method) \ - if (confirmations.find(cls##_instance.get_id()) == confirmations.end()) \ - confirmations[cls##_instance.get_id()] = new conf_wrapper; \ - confirmations[cls##_instance.get_id()]->add_hook(&INTERPOSE_HOOK(cls##_hooks, method)); - CHOOKS -#undef HOOK_ACTION + if (!conf_lua::init(out)) + return CR_FAILURE; commands.push_back(PluginCommand( "confirm", "Confirmation dialogs", @@ -446,6 +483,10 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) } is_enabled = enable; } + if (is_enabled) + { + conf_lua::simple_call("check"); + } return CR_OK; } @@ -453,10 +494,21 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) { if (plugin_enable(out, false) != CR_OK) return CR_FAILURE; + conf_lua::cleanup(); return CR_OK; } -void enable_conf (color_ostream &out, string name, bool state) +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + while (!cmds.empty()) + { + Core::getInstance().runCommand(out, cmds.front()); + cmds.pop(); + } + return CR_OK; +} + +bool set_conf_state (string name, bool state) { bool found = false; for (auto it = confirmations.begin(); it != confirmations.end(); ++it) @@ -467,7 +519,12 @@ void enable_conf (color_ostream &out, string name, bool state) it->second->apply(state); } } - if (!found) + return found; +} + +void enable_conf (color_ostream &out, string name, bool state) +{ + if (!set_conf_state(name, state)) out.printerr("Unrecognized option: %s\n", name.c_str()); } @@ -475,13 +532,11 @@ command_result df_confirm (color_ostream &out, vector & parameters) { CoreSuspender suspend; bool state = true; - if (in_vector(parameters, "help") || in_vector(parameters, "status")) + 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 << " " << it->first << ": " << (it->second->enabled ? "enabled" : "disabled") << std::endl; - } + 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) @@ -493,9 +548,7 @@ command_result df_confirm (color_ostream &out, vector & parameters) else if (*it == "all") { for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - { it->second->apply(state); - } } else enable_conf(out, *it, state); diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index da793ac0d..33cd05f70 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -70,6 +70,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_item = false) { + vector out_products; vector out_items; vector in_reag; vector in_items; @@ -83,7 +84,7 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it if (dest_building != -1) building = df::building::find(dest_building); - prod->produce(unit, &out_items, &in_reag, &in_items, 1, job_skill::NONE, + 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); if (!out_items.size()) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 79bf55ff6..1f3ff6f75 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -5,6 +5,7 @@ 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) DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) diff --git a/plugins/devel/autolabor2.cpp b/plugins/devel/autolabor2.cpp index aca30ad3e..62bcc98d0 100644 --- a/plugins/devel/autolabor2.cpp +++ b/plugins/devel/autolabor2.cpp @@ -1222,7 +1222,6 @@ public: 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::MakeInstrument] = 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; diff --git a/plugins/devel/color-dfhack-text.cpp b/plugins/devel/color-dfhack-text.cpp new file mode 100644 index 000000000..eb5e9c659 --- /dev/null +++ b/plugins/devel/color-dfhack-text.cpp @@ -0,0 +1,145 @@ +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" + +using namespace DFHack; + +DFHACK_PLUGIN("color-dfhack-text"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); + +struct { + bool flicker; + uint8_t color; + short tick; +} config; + +void 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) +{ + Screen::Pen pen2 = pen; + uint8_t color = config.flicker ? config.tick % 8 : config.color; + if (map) + { + pen2.fg = color % 8; + pen2.bg = (color % 8) + 8; + pen2.bold = false; + } + else + { + pen2.fg = color; + pen2.bg = color; + pen2.bold = true; + } + return color_text_hook.next()(pen2, x, y, map); +} + +void 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) +{ + 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); +} + +void 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) +{ + x = (x + 1) % gps->dimx; + shift_set_tile_hook.next()(pen, x, y, map); +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + color_text_hook.apply(enable); + if (!enable) + { + shift_set_tile_hook.disable(); + aaaaa_set_tile_hook.disable(); + } + is_enabled = enable; + return CR_OK; +} + +command_result color(color_ostream &out, std::vector ¶ms) +{ + if (params.empty()) + return plugin_enable(out, true); + for (auto it = params.begin(); it != params.end(); ++it) + { + std::string p = toLower(*it); + if (!p.size()) + continue; + #define CHECK_COLOR(color_name) else if (p == toLower(std::string(#color_name))) \ + { config.flicker = false; config.color = COLOR_##color_name % 8; plugin_enable(out, true); } + CHECK_COLOR(RED) + CHECK_COLOR(GREEN) + CHECK_COLOR(BLUE) + CHECK_COLOR(YELLOW) + CHECK_COLOR(BROWN) + CHECK_COLOR(CYAN) + CHECK_COLOR(MAGENTA) + CHECK_COLOR(WHITE) + CHECK_COLOR(GREY) + CHECK_COLOR(BLACK) + #undef CHECK_COLOR + else if (p == "flicker") + { + config.flicker = true; + plugin_enable(out, true); + } + else if (p.size() >= 3 && p.substr(0, 3) == "aaa") + { + aaaaa_set_tile_hook.toggle(); + } + else if (p == "shift") + { + shift_set_tile_hook.toggle(); + } + else if (p == "disable") + { + plugin_enable(out, false); + } + else if (p != "enable") + { + out.printerr("Unrecognized option: %s\n", p.c_str()); + return CR_WRONG_USAGE; + } + } + return CR_OK; +} + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "color-dfhack-text", + "color |flicker|enable|disable|shift|aaaaa", + color, + false + )); + config.flicker = false; + config.color = COLOR_RED; + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + ++config.tick; + return CR_OK; +} diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index 57aa6ed6f..b7ca6f161 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -252,10 +252,11 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapcount = 1; prod->product_dimension = 1; + vector out_products; vector out_items; vector in_reag; vector in_items; - prod->produce(firstInvader, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE, + 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)); diff --git a/plugins/drybuckets.cpp b/plugins/drybuckets.cpp deleted file mode 100644 index 711a05928..000000000 --- a/plugins/drybuckets.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Dry Buckets : Remove all "water" objects from buckets scattered around the fortress - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/item.h" -#include "df/builtin_mats.h" - -using std::string; -using std::vector; -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("drybuckets"); -REQUIRE_GLOBAL(world); - -command_result df_drybuckets (color_ostream &out, vector & parameters) -{ - if (!parameters.empty()) - return CR_WRONG_USAGE; - - CoreSuspender suspend; - - int dried_total = 0; - for (size_t i = 0; i < world->items.all.size(); i++) - { - df::item *item = world->items.all[i]; - if ((item->getType() == item_type::LIQUID_MISC) && (item->getMaterial() == builtin_mats::WATER)) - { - item->flags.bits.garbage_collect = 1; - dried_total++; - } - } - if (dried_total) - out.print("Done. %d buckets of water marked for emptying.\n", dried_total); - return CR_OK; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand("drybuckets", "Removes water from buckets.", df_drybuckets)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index ca61ddb2e..8e2a88611 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -706,7 +706,6 @@ public: case job_type::MakeChain: case job_type::MakeFlask: case job_type::MakeGoblet: - case job_type::MakeInstrument: case job_type::MakeToy: case job_type::MakeAnimalTrap: case job_type::MakeBarrel: @@ -1058,7 +1057,7 @@ public: { df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr; Screen::dismiss(this); - Screen::show(new ViewscreenDwarfStats(selected_unit)); + Screen::show(new ViewscreenDwarfStats(selected_unit), plugin_self); } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { @@ -1666,7 +1665,7 @@ private: static void open_stats_srceen() { - Screen::show(new ViewscreenFortStats()); + Screen::show(new ViewscreenFortStats(), plugin_self); } static void add_work_history(df::unit *unit, activity_type type) @@ -1916,12 +1915,12 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector & par else if (cmd == 's' || cmd == 'S') { if(Maps::IsValid()) - Screen::show(new ViewscreenFortStats()); + Screen::show(new ViewscreenFortStats(), plugin_self); } else if (cmd == 'p' || cmd == 'P') { if(Maps::IsValid()) - Screen::show(new ViewscreenPreferences()); + Screen::show(new ViewscreenPreferences(), plugin_self); } else if (cmd == 'r' || cmd == 'R') { diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 7fa9cafca..d2bdf4909 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -21,6 +21,9 @@ using namespace DFHack; using df::global::enabler; using df::global::gps; +DFHACK_PLUGIN("embark-tools"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + #define FOR_ITER_TOOLS(iter) for(auto iter = tools.begin(); iter != tools.end(); iter++) void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen) @@ -684,7 +687,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest void display_settings() { - Screen::show(new embark_tools_settings); + Screen::show(new embark_tools_settings, plugin_self); } inline bool is_valid_page() @@ -734,9 +737,6 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render); -DFHACK_PLUGIN("embark-tools"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - command_result embark_tools_cmd (color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) @@ -783,14 +783,6 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt) { - if (evt == SC_BEGIN_UNLOAD) - { - if (Gui::getCurFocus() == "dfhack/embark-tools/options") - { - out.printerr("Settings screen active.\n"); - return CR_FAILURE; - } - } return CR_OK; } diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 1b9d228bc..7ad10a3ba 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -309,7 +309,9 @@ struct product_hook : item_product { DEFINE_VMETHOD_INTERPOSE( void, produce, - (df::unit *unit, std::vector *out_items, + (df::unit *unit, + std::vector *out_products, + std::vector *out_items, std::vector *in_reag, std::vector *in_items, int32_t quantity, df::job_skill skill, @@ -318,7 +320,7 @@ struct product_hook : item_product { color_ostream_proxy out(Core::getInstance().getConsole()); auto product = products[this]; if ( !product ) { - INTERPOSE_NEXT(produce)(unit, 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, site); return; } df::reaction* this_reaction=product->react; @@ -329,7 +331,7 @@ struct product_hook : item_product { return; size_t out_item_count = out_items->size(); - INTERPOSE_NEXT(produce)(unit, 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, site); if ( out_items->size() == out_item_count ) return; //if it produced something, call the scripts diff --git a/plugins/feature.cpp b/plugins/feature.cpp deleted file mode 100644 index 30c71cda2..000000000 --- a/plugins/feature.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// Map feature manager - list features and discover/undiscover individual ones - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/feature_init.h" - -#include - -using std::vector; -using std::string; -using std::endl; -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("feature"); -REQUIRE_GLOBAL(world); - - -static command_result feature(color_ostream &out, vector ¶meters) -{ - CoreSuspender suspend; - - if (parameters.empty()) - return CR_WRONG_USAGE; - - string cmd = parameters[0]; - - if (cmd == "list") - { - if (parameters.size() != 1) - return CR_WRONG_USAGE; - for (size_t i = 0; i < world->features.map_features.size(); i++) - { - df::feature_init *feature_init = world->features.map_features[i]; - string name; - feature_init->getName(&name); - out.print("Feature #%i (\"%s\", type %s) is %s\n", - i, name.c_str(), ENUM_KEY_STR(feature_type, feature_init->getType()).c_str(), - feature_init->flags.is_set(feature_init_flags::Discovered) ? "discovered" : "hidden"); - } - } - else if(cmd == "show") - { - if (parameters.size() != 2) - return CR_WRONG_USAGE; - size_t i = atoi(parameters[1].c_str()); - if ((i < 0) || (i >= world->features.map_features.size())) - { - out.print("No such feature!\n"); - return CR_FAILURE; - } - df::feature_init *feature_init = world->features.map_features[i]; - if (feature_init->flags.is_set(feature_init_flags::Discovered)) - { - out.print("Selected feature is already discovered!\n"); - return CR_OK; - } - feature_init->flags.set(feature_init_flags::Discovered); - string name; - feature_init->getName(&name); - out.print("Feature #%i (\"%s\", type %s) is now discovered\n", - i, name.c_str(), ENUM_KEY_STR(feature_type, feature_init->getType()).c_str()); - } - else if(cmd == "hide") - { - if (parameters.size() != 2) - return CR_WRONG_USAGE; - size_t i = atoi(parameters[1].c_str()); - if ((i < 0) || (i >= world->features.map_features.size())) - { - out.print("No such feature!\n"); - return CR_FAILURE; - } - df::feature_init *feature_init = world->features.map_features[i]; - if (!feature_init->flags.is_set(feature_init_flags::Discovered)) - { - out.print("Selected feature is already hidden!\n"); - return CR_OK; - } - feature_init->flags.clear(feature_init_flags::Discovered); - string name; - feature_init->getName(&name); - out.print("Feature #%i (\"%s\", type %s) is now hidden\n", - i, name.c_str(), ENUM_KEY_STR(feature_type, feature_init->getType()).c_str()); - } - else return CR_WRONG_USAGE; - - return CR_OK; -} - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "feature", "List or manage map features.", feature, false, - " feature list\n" - " Lists all map features in the region.\n" - " feature show \n" - " Marks the specified map feature as discovered.\n" - " feature hide \n" - " Marks the specified map feature as undiscovered.\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown (color_ostream &out) -{ - return CR_OK; -} \ No newline at end of file diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index 4675b5cfb..8b7be0c0f 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -4,10 +4,12 @@ #include "Export.h" #include "PluginManager.h" +#include "modules/Maps.h" #include "modules/Units.h" #include "modules/Translation.h" #include "modules/World.h" +#include "df/creature_raw.h" #include "df/map_block.h" #include "df/unit.h" #include "df/world.h" @@ -39,15 +41,56 @@ static std::string get_unit_description(df::unit *unit) return desc; } -df::unit *findUnit(int x, int y, int z) -{ - for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) +struct uo_buf { + uint32_t dim_x, dim_y, dim_z; + size_t size; + uint8_t *buf; + uo_buf() : size(0), buf(NULL) + { } + ~uo_buf() { - if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z) - return *u; + if (buf) + free(buf); } - return NULL; -} + void resize() + { + Maps::getSize(dim_x, dim_y, dim_z); + dim_x *= 16; + dim_y *= 16; + size = dim_x * dim_y * dim_z; + buf = (uint8_t*)realloc(buf, size); + clear(); + } + inline void clear() + { + memset(buf, 0, size); + } + inline size_t offset (uint32_t x, uint32_t y, uint32_t z) + { + return (dim_x * dim_y * z) + (dim_x * y) + x; + } + inline uint8_t get (uint32_t x, uint32_t y, uint32_t z) + { + size_t off = offset(x, y, z); + if (off < size) + return buf[off]; + return 0; + } + inline void set (uint32_t x, uint32_t y, uint32_t z, uint8_t val) + { + size_t off = offset(x, y, z); + if (off < size) + buf[off] = val; + } + inline void get_coords (size_t off, uint32_t &x, uint32_t &y, uint32_t &z) + { + x = off % dim_x; + y = (off / dim_x) % dim_y; + z = off / (dim_x * dim_y); + } +}; + +static uo_buf uo_buffer; struct uo_opts { bool dry_run; @@ -64,10 +107,21 @@ unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts) if (!Core::getInstance().isWorldLoaded()) return 0; + if (!World::isFortressMode() && !opts.use_cursor) + { + out.printerr("Can only scan entire map in fortress mode\n"); + return 0; + } + if (opts.use_cursor && cursor->x < 0) + { out.printerr("No cursor\n"); + return 0; + } + uo_buffer.resize(); unsigned count = 0; + float time1 = getClock(); for (size_t i = 0; i < world->map.map_blocks.size(); i++) { @@ -85,33 +139,48 @@ unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts) int map_y = y + block->map_pos.y; if (opts.use_cursor && (map_x != cursor->x || map_y != cursor->y)) continue; - bool cur_occupancy = block->occupancy[x][y].bits.unit; - bool fixed_occupancy = cur_occupancy; - df::unit *cur_unit = findUnit(map_x, map_y, map_z); - if (cur_occupancy && !cur_unit) - { - out.print("%sFixing occupancy at (%i, %i, %i) - no unit found\n", - opts.dry_run ? "(Dry run) " : "", - map_x, map_y, map_z); - fixed_occupancy = false; - } - // else if (!cur_occupancy && cur_unit) - // { - // out.printerr("Unit found at (%i, %i, %i): %s\n", map_x, map_y, map_z, get_unit_description(cur_unit).c_str()); - // fixed_occupancy = true; - // } - if (cur_occupancy != fixed_occupancy && !opts.dry_run) - { - ++count; - block->occupancy[x][y].bits.unit = fixed_occupancy; - } + if (block->occupancy[x][y].bits.unit) + uo_buffer.set(map_x, map_y, map_z, 1); + } + } + } + + for (auto it = world->units.active.begin(); it != world->units.active.end(); ++it) + { + df::unit *u = *it; + if (!u || u->flags1.bits.caged || u->pos.x < 0) + continue; + df::creature_raw *craw = df::creature_raw::find(u->race); + int unit_extents = (craw && craw->flags.is_set(df::creature_raw_flags::EQUIPMENT_WAGON)) ? 1 : 0; + for (int16_t x = u->pos.x - unit_extents; x <= u->pos.x + unit_extents; ++x) + { + for (int16_t y = u->pos.y - unit_extents; y <= u->pos.y + unit_extents; ++y) + { + uo_buffer.set(x, y, u->pos.z, 0); + } + } + } + + for (size_t i = 0; i < uo_buffer.size; i++) + { + if (uo_buffer.buf[i]) + { + uint32_t x, y, z; + uo_buffer.get_coords(i, x, y, z); + out.print("(%u, %u, %u) - no unit found\n", x, y, z); + ++count; + if (!opts.dry_run) + { + df::map_block *b = Maps::getTileBlock(x, y, z); + b->occupancy[x % 16][y % 16].bits.unit = false; } } } + float time2 = getClock(); std::cerr << "fix-unit-occupancy: elapsed time: " << time2 - time1 << " secs" << endl; if (count) - out << "Fixed occupancy of " << count << " tiles [fix-unit-occupancy]" << endl; + out << (opts.dry_run ? "[dry run] " : "") << "Fixed occupancy of " << count << " tiles [fix-unit-occupancy]" << endl; return count; } diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 45ca07cbd..d7139dd96 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -319,7 +319,7 @@ static command_result hotkeys_cmd(color_ostream &out, vector & paramete if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys") { find_active_keybindings(top_screen); - Screen::show(new ViewscreenHotkeys(top_screen)); + Screen::show(new ViewscreenHotkeys(top_screen), plugin_self); } } } diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 0576ca4e4..5c96b4594 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -35,8 +35,9 @@ public: bool allow_search; bool feed_mouse_set_highlight; bool feed_changed_highlight; + T default_value; - ListColumn() + ListColumn(const T default_value_ = T()) { bottom_margin = 3; clear(); @@ -50,6 +51,7 @@ public: allow_search = true; feed_mouse_set_highlight = false; feed_changed_highlight = false; + default_value = default_value_; } void clear() @@ -310,7 +312,7 @@ public: { vector results = getSelectedElems(true); if (results.size() == 0) - return (T)nullptr; + return default_value; else return results[0]; } diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua new file mode 100644 index 000000000..135187358 --- /dev/null +++ b/plugins/lua/confirm.lua @@ -0,0 +1,240 @@ +local _ENV = mkmodule('plugins.confirm') + +local ui = df.global.ui + +local confs = {} +-- Wraps df.interface_key[foo] functionality but fails with invalid keys +keys = {} +setmetatable(keys, { + __index = function(self, k) + return df.interface_key[k] or error('Invalid key: ' .. tostring(k)) + end, + __newindex = function() error('Table is read-only') end +}) +--[[ The screen where a confirmation has been triggered +Note that this is *not* necessarily the topmost viewscreen, so do not use +gui.getCurViewscreen() or related functions. ]] +screen = nil + +function if_nil(obj, default) + if obj == nil then + return default + else + return obj + end +end + +function defconf(id) + if not get_ids()[id] then + error('Bad confirmation ID (not defined in plugin): ' .. id) + end + local cls = {} + cls.intercept_key = function(key) return false end + cls.get_title = function() return if_nil(cls.title, '') end + cls.get_message = function() return if_nil(cls.message, '') end + cls.get_color = function() return if_nil(cls.color, COLOR_YELLOW) end + confs[id] = cls + return cls +end + +--[[ Beginning of confirmation definitions +All confirmations declared in confirm.cpp must have a corresponding call to +defconf() here, and should implement intercept_key(), get_title(), and +get_message(). get_color() can also be implemented here, but the default should +be sufficient. + +In cases where getter functions always return the same value (e.g. get_title()), +they can be replaced with a field named after the method without the "get_" +prefix: + + trade.title = "Confirm trade" + +is equivalent to: + + function trade.get_title() return "Confirm trade" end + +]] + +trade = defconf('trade') +function trade.intercept_key(key) + return screen.in_edit_count == 0 and + key == keys.TRADE_TRADE +end +trade.title = "Confirm trade" +function trade.get_message() + if trader_goods_selected(screen) and broker_goods_selected(screen) then + return "Are you sure you want to trade the selected goods?" + elseif trader_goods_selected(screen) then + return "You are not giving any items. This is likely\n" .. + "to irritate the merchants.\n" .. + "Attempt to trade anyway?" + elseif broker_goods_selected(screen) then + return "You are not receiving any items. You may want to\n" .. + "offer these items instead or choose items to receive.\n" .. + "Attempt to trade anyway?" + else + return "No items are selected. This is likely\n" .. + "to irritate the merchants.\n" .. + "Attempt to trade anyway?" + end +end + +trade_cancel = defconf('trade-cancel') +function trade_cancel.intercept_key(key) + return screen.in_edit_count == 0 and + key == keys.LEAVESCREEN and + (trader_goods_selected(screen) or broker_goods_selected(screen)) +end +trade_cancel.title = "Cancel trade" +trade_cancel.message = "Are you sure you want leave this screen?\nSelected items will not be saved." + +trade_seize = defconf('trade-seize') +function trade_seize.intercept_key(key) + return screen.in_edit_count == 0 and + trader_goods_selected(screen) and + key == keys.TRADE_SEIZE +end +trade_seize.title = "Confirm seize" +trade_seize.message = "Are you sure you want to seize these goods?" + +trade_offer = defconf('trade-offer') +function trade_offer.intercept_key(key) + return screen.in_edit_count == 0 and + broker_goods_selected(screen) and + key == keys.TRADE_OFFER +end +trade_offer.title = "Confirm offer" +trade_offer.message = "Are you sure you want to offer these goods?\nYou will receive no payment." + +trade_select_all = defconf('trade-select-all') +function trade_select_all.intercept_key(key) + if screen.in_edit_count == 0 and key == keys.SEC_SELECT then + if screen.in_right_pane and broker_goods_selected(screen) and not broker_goods_all_selected(screen) then + return true + elseif not screen.in_right_pane and trader_goods_selected(screen) and not trader_goods_all_selected(screen) then + return true + end + end + return false +end +trade_select_all.title = "Confirm selection" +trade_select_all.message = "Selecting all goods will overwrite your current selection\n" .. + "and cannot be undone. Continue?" + +haul_delete = defconf('haul-delete') +function haul_delete.intercept_key(key) + if ui.main.mode == df.ui_sidebar_mode.Hauling and + #ui.hauling.view_routes > 0 and + not ui.hauling.in_name and + not ui.hauling.in_stop and + not ui.hauling.in_assign_vehicle then + return key == keys.D_HAULING_REMOVE + end + return false +end +haul_delete.title = "Confirm deletion" +function haul_delete.get_message() + local t = ui.hauling.view_stops[ui.hauling.cursor_top] and "stop" or "route" + return "Are you sure you want to delete this " .. + (ui.hauling.view_stops[ui.hauling.cursor_top] and "stop" or "route") .. "?" +end + +depot_remove = defconf('depot-remove') +function depot_remove.intercept_key(key) + if df.building_tradedepotst:is_instance(dfhack.gui.getSelectedBuilding(true)) and + key == keys.DESTROYBUILDING then + for _, caravan in pairs(ui.caravans) do + if caravan.time_remaining > 0 then + return true + end + end + end +end +depot_remove.title = "Confirm depot removal" +depot_remove.message = "Are you sure you want to remove this depot?\n" .. + "Merchants are present and will lose profits." + +squad_disband = defconf('squad-disband') +function squad_disband.intercept_key(key) + return key == keys.D_MILITARY_DISBAND_SQUAD and + screen.page == screen._type.T_page.Positions and + screen.num_squads > 0 and + not screen.in_rename_alert +end +squad_disband.title = "Disband squad" +squad_disband.message = "Are you sure you want to disband this squad?" + +uniform_delete = defconf('uniform-delete') +function uniform_delete.intercept_key(key) + return key == keys.D_MILITARY_DELETE_UNIFORM and + screen.page == screen._type.T_page.Uniforms and + #screen.equip.uniforms > 0 and + not screen.equip.in_name_uniform +end +uniform_delete.title = "Delete uniform" +uniform_delete.message = "Are you sure you want to delete this uniform?" + +note_delete = defconf('note-delete') +function note_delete.intercept_key(key) + return key == keys.D_NOTE_DELETE and + ui.main.mode == df.ui_sidebar_mode.NotesPoints and + not ui.waypoints.in_edit_text_mode +end +note_delete.title = "Delete note" +note_delete.message = "Are you sure you want to delete this note?" + +route_delete = defconf('route-delete') +function route_delete.intercept_key(key) + return key == keys.D_NOTE_ROUTE_DELETE and + ui.main.mode == df.ui_sidebar_mode.NotesRoutes and + not ui.waypoints.in_edit_name_mode +end +route_delete.title = "Delete route" +route_delete.message = "Are you sure you want to delete this route?" + +location_retire = defconf('location-retire') +function location_retire.intercept_key(key) + return key == keys.LOCATION_RETIRE and + (screen.menu == df.viewscreen_locationsst.T_menu.Locations or + screen.menu == df.viewscreen_locationsst.T_menu.Occupations) and + screen.in_edit == df.viewscreen_locationsst.T_in_edit.None and + screen.locations[screen.location_idx] +end +location_retire.title = "Retire location" +location_retire.message = "Are you sure you want to retire this location?" + +-- End of confirmation definitions + +function check() + local undefined = {} + for id in pairs(get_ids()) do + if not confs[id] then + table.insert(undefined, id) + end + end + if #undefined > 0 then + error('Confirmation definitions missing: ' .. table.concat(undefined, ', ')) + end +end + +--[[ +The C++ plugin invokes methods of individual confirmations through four +functions (corresponding to method names) which receive the relevant screen, +the confirmation ID, and extra arguments in some cases, but these don't have to +do aything unique. +]] + +function define_wrapper(name) + _ENV[name] = function(scr, id, ...) + _ENV.screen = scr + if not confs[id] then + error('Bad confirmation ID: ' .. id) + end + return confs[id][name](...) + end +end +define_wrapper('intercept_key') +define_wrapper('get_title') +define_wrapper('get_message') +define_wrapper('get_color') +return _ENV diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 745427656..6d3b3ba15 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -127,9 +127,28 @@ function collect_orders() entry = entry, } else - -- It might be worth searching reaction_list for the name. - -- Then again, this should only happen in unusual situations. - print("Mismatched stockflow entry for stockpile #"..stockpile.stockpile_number..": "..entry.value.." ("..order_number..")") + -- Todo: Search reaction_list for the name. + -- This can happen when loading an old save in a new version. + -- It's even possible that the reaction has been removed. + local found = false + for number, reaction in ipairs(reaction_list) do + if reaction.name == entry.value then + print("Adjusting stockflow entry for stockpile #"..stockpile.stockpile_number..": "..entry.value.." ("..order_number.." => "..number..")") + entry.ints[entry_ints.order_number] = number + entry:save() + result[spid] = { + 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 end else -- The stockpile no longer exists. @@ -398,6 +417,14 @@ function collect_reactions() 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 + 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) + end + end -- Metal forging local itemdefs = df.global.world.raws.itemdefs @@ -465,19 +492,10 @@ function collect_reactions() clothing_reactions(result, mat_flags, metalclothing) end - if material.flags.ITEMS_HARD then - resource_reactions(result, job_types.MakeTool, mat_flags, entity.resources.tool_type, itemdefs.tools, { - permissible = (function(itemdef) return itemdef.flags.HARD_MAT end), - capitalize = true, - }) - end - - if material.flags.ITEMS_METAL then - resource_reactions(result, job_types.MakeTool, mat_flags, entity.resources.tool_type, itemdefs.tools, { - permissible = (function(itemdef) return itemdef.flags.METAL_MAT end), - capitalize = true, - }) - end + resource_reactions(result, job_types.MakeTool, mat_flags, entity.resources.tool_type, itemdefs.tools, { + permissible = (function(itemdef) return ((material.flags.ITEMS_HARD and itemdef.flags.HARD_MAT) or (material.flags.ITEMS_METAL and itemdef.flags.METAL_MAT)) and not itemdef.flags.NO_DEFAULT_JOB end), + capitalize = true, + }) if material.flags.ITEMS_HARD then material_reactions(result, { @@ -560,7 +578,8 @@ function collect_reactions() }, materials.wood) resource_reactions(result, job_types.MakeTool, materials.wood, entity.resources.tool_type, itemdefs.tools, { - -- permissible = (function(itemdef) return itemdef.flags.WOOD_MAT end), + -- permissible = (function(itemdef) return itemdef.flags.WOOD_MAT and not itemdef.flags.NO_DEFAULT_JOB end), + permissible = (function(itemdef) return not itemdef.flags.NO_DEFAULT_JOB end), capitalize = true, }) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index c2f6759a5..e2a4e54a0 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -170,6 +170,8 @@ const SkillColumn columns[] = { {9, 9, profession::POTTER, unit_labor::POTTERY, job_skill::POTTERY, "Po"}, {9, 9, profession::GLAZER, unit_labor::GLAZING, job_skill::GLAZING, "Gl"}, {9, 9, profession::WAX_WORKER, unit_labor::WAX_WORKING, job_skill::WAX_WORKING, "Wx"}, + {9, 9, profession::PAPERMAKER, unit_labor::PAPERMAKING, job_skill::PAPERMAKING, "Pa"}, + {9, 9, profession::BOOKBINDER, unit_labor::BOOKBINDING, job_skill::BOOKBINDING, "Bk"}, // Engineering {10, 12, profession::SIEGE_ENGINEER, unit_labor::SIEGECRAFT, job_skill::SIEGECRAFT, "En"}, {10, 12, profession::SIEGE_OPERATOR, unit_labor::SIEGEOPERATE, job_skill::SIEGEOPERATE, "Op"}, @@ -1592,7 +1594,7 @@ void viewscreen_unitlaborsst::feed(set *events) if (enabler->mouse_lbut) { input_row = click_unit; - events->insert(interface_key::UNITJOB_VIEW); + events->insert(interface_key::UNITJOB_VIEW_UNIT); } if (enabler->mouse_rbut) { @@ -1771,14 +1773,14 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CUSTOM_B)) { - Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names)); + Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names), plugin_self); } if (events->count(interface_key::CUSTOM_E)) { vector tmp; tmp.push_back(cur); - Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names)); + Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names), plugin_self); } if (events->count(interface_key::CUSTOM_P)) @@ -1789,11 +1791,11 @@ void viewscreen_unitlaborsst::feed(set *events) has_selected = true; if (has_selected) { - Screen::show(new viewscreen_unitprofessionset(units, true)); + Screen::show(new viewscreen_unitprofessionset(units, true), plugin_self); } else { vector tmp; tmp.push_back(cur); - Screen::show(new viewscreen_unitprofessionset(tmp, false)); + Screen::show(new viewscreen_unitprofessionset(tmp, false), plugin_self); } } @@ -1804,7 +1806,7 @@ void viewscreen_unitlaborsst::feed(set *events) if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) { - if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE)) + 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++) { @@ -2057,7 +2059,7 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT_ALL)); OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, "); - OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW_UNIT)); OutputString(15, x, y, ": ViewCre, "); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); @@ -2142,7 +2144,7 @@ struct unitlist_hook : df::viewscreen_unitlistst { if (units[page].size()) { - Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page])); + Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page]), plugin_self); return; } } diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index b0835832f..2da47d8cd 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -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; + pos.z = vz - Gui::getDepthAt(pos.x, pos.y); return pos; } @@ -582,7 +582,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest { int x = left_margin; int y = gps->dimy - 2; - OutputToggleString(x, y, "Box Select", "Alt+M", box_designation_enabled, + OutputToggleString(x, y, "Box Select", interface_key::CUSTOM_ALT_M, box_designation_enabled, true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } @@ -674,7 +674,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest } } - OutputString(color, mx, my, "X"); + OutputString(color, mx, my, "X", false, 0, 0, true); return; } diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 53dc0144a..4bfc26c8e 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -164,6 +164,10 @@ message MapBlock repeated bool water_salt = 17; repeated MatPair construction_items = 18; repeated BuildingInstance buildings = 19; + repeated int32 tree_percent = 20; + repeated int32 tree_x = 21; + repeated int32 tree_y = 22; + repeated int32 tree_z = 23; } message MatPair { @@ -229,6 +233,9 @@ message UnitDefinition optional uint32 flags3 = 10; optional bool is_soldier = 11; optional BodySizeInfo size_info = 12; + optional string name = 13; + optional int32 blood_max = 14; + optional int32 blood_count = 15; } message UnitList @@ -367,6 +374,7 @@ message CasteRaw repeated string caste_name = 3; repeated string baby_name = 4; repeated string child_name = 5; + optional int32 gender = 6; } message CreatureRaw @@ -403,3 +411,46 @@ message ArmyList { repeated Army armies = 1; } + +message GrowthPrint +{ + optional int32 priority = 1; + optional int32 color = 2; + optional int32 timing_start = 3; + optional int32 timing_end = 4; + optional int32 tile = 5; +} + +message TreeGrowth +{ + optional int32 index = 1; + optional string id = 2; + optional string name = 3; + optional MatPair mat = 4; + repeated GrowthPrint prints = 5; + optional int32 timing_start = 6; + optional int32 timing_end = 7; + optional bool twigs = 8; + optional bool light_branches = 9; + optional bool heavy_branches = 10; + optional bool trunk = 11; + optional bool roots = 12; + optional bool cap = 13; + optional bool sapling = 14; + optional int32 trunk_height_start = 15; + optional int32 trunk_height_end = 16; +} + +message PlantRaw +{ + optional int32 index = 1; + optional string id = 2; + optional string name = 3; + repeated TreeGrowth growths = 4; + optional int32 tile = 5; +} + +message PlantRawList +{ + repeated PlantRaw plant_raws = 1; +} \ No newline at end of file diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 6a98bc05b..9a7bda73a 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -1,4 +1,4 @@ -#define DF_VERSION 40024 +#define DF_VERSION 42004 // some headers required for a plugin. Nothing special, just the basics. #include "Core.h" @@ -24,9 +24,12 @@ #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" @@ -110,6 +113,7 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, 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); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); @@ -162,6 +166,7 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("GetRegionMaps", GetRegionMaps); svc->addFunction("GetCreatureRaws", GetCreatureRaws); svc->addFunction("GetWorldMapCenter", GetWorldMapCenter); + svc->addFunction("GetPlantRaws", GetPlantRaws); return svc; } @@ -1018,6 +1023,68 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc 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++) { @@ -1059,6 +1126,10 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc 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]); } } @@ -1343,6 +1414,10 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, 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; } @@ -1865,8 +1940,66 @@ static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage 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[1] * 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; +} diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 7dd6d46bc..5e6f2e2de 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -724,7 +724,7 @@ void lightingEngineViewscreen::doOcupancyAndLights() for(int 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) + if(f && f->density>0 && (f->type==df::flow_type::Dragonfire || f->type==df::flow_type::Fire)) { df::coord2d pos=f->pos; pos=worldToViewportCoord(pos,vp,window2d); diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 492d5249d..7a85e9314 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -57,10 +57,15 @@ module DFHack bld.setSubtype(subtype) bld.setCustomType(custom) case type + when :Well; bld.bucket_z = bld.z when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0 when :Coffin; bld.initBurialFlags - when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate + when :Trap; bld.ready_timeout = 500 if bld.trap_type == :PressurePlate when :Floodgate; bld.gate_flags.closed = true + when :GrateWall; bld.gate_flags.closed = true + when :GrateFloor; bld.gate_flags.closed = true + when :BarsVertical; bld.gate_flags.closed = true + when :BarsFloor; bld.gate_flags.closed = true end bld end diff --git a/plugins/search.cpp b/plugins/search.cpp index 6d12e922c..83d498367 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -7,6 +7,7 @@ #include "uicommon.h" +#include "df/creature_raw.h" #include "df/ui_look_list.h" #include "df/viewscreen_announcelistst.h" #include "df/viewscreen_petst.h" @@ -21,6 +22,7 @@ #include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_joblistst.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" @@ -620,7 +622,7 @@ protected: }; // This basic match function is separated out from the generic multi column class, because the -// pets screen, which uses a union in its primary list, will cause a compile failure is this +// pets screen, which uses a union in its primary list, will cause a compile failure if this // match function exists in the generic class template < class S, class T, class PARENT = search_generic > class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic @@ -750,7 +752,7 @@ template V generic_search_hook ::module; typedef generic_search_hook module##_hook; \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, prio); \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio); \ - template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio) + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio); // // END: Generic Search functionality @@ -760,19 +762,24 @@ template V generic_search_hook ::module; // // START: Animal screen search // -typedef df::viewscreen_petst::T_animal T_animal; -typedef df::viewscreen_petst::T_mode T_mode; -class pets_search : public search_multicolumn_modifiable_generic +typedef search_multicolumn_modifiable_generic pets_search_base; +class pets_search : public pets_search_base { + typedef df::viewscreen_petst::T_animal T_animal; + typedef df::viewscreen_petst::T_mode T_mode; public: void render() const { - if (viewscreen->mode == T_mode::List) - print_search_option(25, 4); + print_search_option(25, 4); } private: + bool can_init(df::viewscreen_petst *screen) + { + return pets_search_base::can_init(screen) && screen->mode == T_mode::List; + } + int32_t *get_viewscreen_cursor() { return &viewscreen->cursor; @@ -875,13 +882,130 @@ private: std::vector *is_adopting, is_adopting_s; }; -IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search); +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, pets_search, 1, 0); // // END: Animal screen search // +// +// START: Animal knowledge screen search +// + +typedef search_generic animal_knowledge_search_base; +class animal_knowledge_search : public animal_knowledge_search_base +{ + typedef df::viewscreen_petst::T_mode T_mode; + bool can_init(df::viewscreen_petst *screen) + { + return animal_knowledge_search_base::can_init(screen) && screen->mode == T_mode::TrainingKnowledge; + } + +public: + void render() const + { + print_search_option(2, 4); + } + +private: + int32_t *get_viewscreen_cursor() + { + return NULL; + } + + vector *get_primary_list() + { + return &viewscreen->known; + } + + string get_element_description(int32_t id) const + { + auto craw = df::creature_raw::find(id); + string out; + if (craw) + { + for (size_t i = 0; i < 3; ++i) + out += craw->name[i] + " "; + } + return out; + } +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, animal_knowledge_search, 2, 0); + +// +// END: Animal knowledge screen search +// + + +// +// START: Animal trainer search +// + +typedef search_twocolumn_modifiable animal_trainer_search_base; +class animal_trainer_search : public animal_trainer_search_base +{ + typedef df::viewscreen_petst::T_mode T_mode; + typedef df::viewscreen_petst::T_trainer_mode T_trainer_mode; + + bool can_init(df::viewscreen_petst *screen) + { + return animal_trainer_search_base::can_init(screen) && screen->mode == T_mode::SelectTrainer; + } + +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); + 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); + } + print_search_option(16, 2); + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->trainer_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->trainer_unit; + } + + string get_element_description(df::unit *u) const + { + return get_unit_description(u); + } + + std::vector *get_secondary_list() + { + return &viewscreen->trainer_mode; + } + +public: + bool process_input(set *input) + { + if (input->count(interface_key::SELECT) && viewscreen->trainer_unit.empty() && !in_entry_mode()) + return true; + return animal_trainer_search_base::process_input(input); + } + +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, animal_trainer_search, 3, 0); + +// +// END: Animal trainer search +// + // // START: Stocks screen search @@ -1327,12 +1451,15 @@ public: { // About to make an assignment, so restore original list (it will be changed by the game) int32_t *cursor = get_viewscreen_cursor(); - df::unit *selected_unit = get_primary_list()->at(*cursor); + auto list = get_primary_list(); + if (*cursor >= list->size()) + return false; + df::unit *selected_unit = list->at(*cursor); clear_search(); - for (*cursor = 0; *cursor < get_primary_list()->size(); (*cursor)++) + for (*cursor = 0; *cursor < list->size(); (*cursor)++) { - if (get_primary_list()->at(*cursor) == selected_unit) + if (list->at(*cursor) == selected_unit) break; } @@ -1945,6 +2072,47 @@ IMPLEMENT_HOOKS(df::viewscreen_topicmeeting_fill_land_holder_positionsst, noble_ // END: Noble suggestion search // +// +// START: Location occupation assignment search +// + +typedef search_generic location_assign_occupation_search_base; +class location_assign_occupation_search : public location_assign_occupation_search_base +{ +public: + bool can_init (df::viewscreen_locationsst *screen) + { + return screen->menu == df::viewscreen_locationsst::AssignOccupation; + } + + string get_element_description (df::unit *unit) const + { + return unit ? get_unit_description(unit) : "Nobody"; + } + + void render() const + { + print_search_option(37, gps->dimy - 3); + } + + vector *get_primary_list() + { + return &viewscreen->units; + } + + virtual int32_t *get_viewscreen_cursor() + { + return &viewscreen->unit_idx; + } + +}; + +IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); + +// +// END: Location occupation assignment search +// + #define SEARCH_HOOKS \ HOOK_ACTION(unitlist_search_hook) \ HOOK_ACTION(roomlist_search_hook) \ @@ -1952,6 +2120,8 @@ IMPLEMENT_HOOKS(df::viewscreen_topicmeeting_fill_land_holder_positionsst, noble_ HOOK_ACTION(trade_search_fort_hook) \ HOOK_ACTION(stocks_search_hook) \ HOOK_ACTION(pets_search_hook) \ + HOOK_ACTION(animal_knowledge_search_hook) \ + HOOK_ACTION(animal_trainer_search_hook) \ HOOK_ACTION(military_search_hook) \ HOOK_ACTION(nobles_search_hook) \ HOOK_ACTION(profiles_search_hook) \ @@ -1961,7 +2131,8 @@ IMPLEMENT_HOOKS(df::viewscreen_topicmeeting_fill_land_holder_positionsst, noble_ HOOK_ACTION(burrow_search_hook) \ HOOK_ACTION(stockpile_search_hook) \ HOOK_ACTION(room_assign_search_hook) \ - HOOK_ACTION(noble_suggest_search_hook) + HOOK_ACTION(noble_suggest_search_hook) \ + HOOK_ACTION(location_assign_occupation_search_hook) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index c1e4ff019..05e9a67ad 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -69,7 +69,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) out.printerr("Dwarf with strange mood does not have a mood type!\n"); continue; } - out.print("%s is currently ", Translation::TranslateName(&unit->name, false).c_str()); + out.print("%s is currently ", DF2CONSOLE(Translation::TranslateName(&unit->name, false)).c_str()); switch (unit->mood) { case mood_type::Macabre: diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 85fedb9f2..8565c703f 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -771,7 +771,7 @@ public: } else if (input->count(interface_key::HELP)) { - Screen::show(new search_help); + Screen::show(new search_help, plugin_self); } bool key_processed = false; @@ -1425,7 +1425,7 @@ struct stocks_hook : public df::viewscreen_storesst if (input->count(interface_key::CUSTOM_E)) { Screen::dismiss(this); - Screen::show(new ViewscreenStocks()); + Screen::show(new ViewscreenStocks(), plugin_self); return; } INTERPOSE_NEXT(feed)(input); @@ -1457,7 +1457,7 @@ struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest if (input->count(interface_key::CUSTOM_I)) { - Screen::show(new ViewscreenStocks(sp)); + Screen::show(new ViewscreenStocks(sp), plugin_self); return true; } @@ -1531,7 +1531,7 @@ static command_result stocks_cmd(color_ostream &out, vector & parameter } else if (toLower(parameters[0])[0] == 's') { - Screen::show(new ViewscreenStocks()); + Screen::show(new ViewscreenStocks(), plugin_self); return CR_OK; } } @@ -1557,10 +1557,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_MAP_LOADED: ViewscreenStocks::reset(); break; - case SC_BEGIN_UNLOAD: - if (Gui::getCurFocus().find("dfhack/stocks") == 0) - return CR_FAILURE; - break; default: break; } diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 8ad245c7c..c10566d33 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -26,6 +26,9 @@ #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" using std::string; using std::vector; @@ -56,6 +59,8 @@ bool isUnitMoodable (df::unit *unit) return false; if (!ENUM_ATTR(profession,moodable,unit->profession)) return false; + if (!Units::casteFlagSet(unit->race, unit->caste, caste_raw_flags::STRANGE_MOODS)) + return false; return true; } @@ -104,11 +109,11 @@ df::job_skill getMoodSkill (df::unit *unit) } if (!skills.size() && civ) { - if (civ->entity_raw->jobs.permitted_skill[job_skill::WOODCRAFT]) + if (civ->resources.permitted_skill[job_skill::WOODCRAFT]) skills.push_back(job_skill::WOODCRAFT); - if (civ->entity_raw->jobs.permitted_skill[job_skill::STONECRAFT]) + if (civ->resources.permitted_skill[job_skill::STONECRAFT]) skills.push_back(job_skill::STONECRAFT); - if (civ->entity_raw->jobs.permitted_skill[job_skill::BONECARVE]) + if (civ->resources.permitted_skill[job_skill::BONECARVE]) skills.push_back(job_skill::BONECARVE); } if (!skills.size()) @@ -728,6 +733,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::WOODCRAFT: case job_skill::STONECRAFT: case job_skill::BONECARVE: + case job_skill::PAPERMAKING: // These aren't actually moodable skills + case job_skill::BOOKBINDING: // but the game still checks for them anyways job->job_type = job_type::StrangeMoodCrafter; break; case job_skill::TANNER: @@ -861,6 +868,8 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::CARPENTRY: case job_skill::WOODCRAFT: case job_skill::BOWYER: + case job_skill::PAPERMAKING: + case job_skill::BOOKBINDING: job->job_items.push_back(item = new df::job_item()); item->item_type = item_type::WOOD; item->quantity = base_item_count; @@ -960,6 +969,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::FORGE_WEAPON: case job_skill::FORGE_ARMOR: + // there are actually 2 distinct cases here, but they're identical case job_skill::FORGE_FURNITURE: case job_skill::METALCRAFT: filter = NULL; @@ -990,7 +1000,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) item->item_type = item_type::BAR; item->mat_type = filter->getMaterial(); item->mat_index = filter->getMaterialIndex(); - item->quantity = base_item_count * 150; + item->quantity = base_item_count * 150; // BUGFIX - the game does not adjust here! item->min_dimension = 150; } else @@ -1012,7 +1022,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) } if (mats.size()) item->mat_index = mats[rng.df_trandom(mats.size())]; - item->quantity = base_item_count * 150; + item->quantity = base_item_count * 150; // BUGFIX - the game does not adjust here! item->min_dimension = 150; } break; @@ -1264,12 +1274,12 @@ command_result df_strangemood (color_ostream &out, vector & parameters) item->quantity = 1; if (item_type == item_type::BAR) { - item->quantity *= 150; + item->quantity *= 150; // BUGFIX - the game does not adjust here! item->min_dimension = 150; } if (item_type == item_type::CLOTH) { - item->quantity *= 10000; + item->quantity *= 10000; // BUGFIX - the game does not adjust here! item->min_dimension = 10000; } } diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp index c62568f1e..78a694211 100644 --- a/plugins/title-version.cpp +++ b/plugins/title-version.cpp @@ -34,7 +34,12 @@ struct title_version_hook : df::viewscreen_titlest { 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; + OutputString(COLOR_WHITE, x, y, "Git: "); + OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION); + } } }; diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 98ec21eb3..3b17d9fa0 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -14,6 +14,7 @@ #include "modules/Materials.h" #include "modules/MapCache.h" #include "modules/Buildings.h" +#include "modules/Filesystem.h" #include "MiscUtils.h" @@ -78,6 +79,7 @@ #include "tweaks/adamantine-cloth-wear.h" #include "tweaks/advmode-contained.h" +#include "tweaks/block-labors.h" #include "tweaks/civ-agreement-ui.h" #include "tweaks/craft-age-wear.h" #include "tweaks/eggs-fertile.h" @@ -86,6 +88,7 @@ #include "tweaks/fast-heat.h" #include "tweaks/fast-trade.h" #include "tweaks/fps-min.h" +#include "tweaks/hide-priority.h" #include "tweaks/import-priority-category.h" #include "tweaks/kitchen-keys.h" #include "tweaks/kitchen-prefs-color.h" @@ -96,6 +99,7 @@ #include "tweaks/nestbox-color.h" #include "tweaks/shift-8-scroll.h" #include "tweaks/stable-cursor.h" +#include "tweaks/title-start-rename.h" #include "tweaks/tradereq-pet-gender.h" using std::set; @@ -114,7 +118,9 @@ REQUIRE_GLOBAL(ui_area_map_width); REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(ui_look_cursor); REQUIRE_GLOBAL(ui_sidebar_menus); +REQUIRE_GLOBAL(ui_unit_view_mode); REQUIRE_GLOBAL(ui_workshop_in_add); REQUIRE_GLOBAL(world); @@ -174,6 +180,8 @@ 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; + } + + inline bool forbidden_labor (df::unit *unit, df::unit_labor labor) + { + return is_valid_enum_item(labor) && !Units::isValidLabor(unit, labor); + } + + inline bool all_labors_enabled (df::unit *unit, df::unit_labor_category cat) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (ENUM_ATTR(unit_labor, category, labor) == cat && + !unit->status.labors[labor] && + !forbidden_labor(unit, labor)) + return false; + } + return true; + } + + inline void recolor_line (int x1, int x2, int y, UIColor color) + { + for (int x = x1; x <= x2; x++) + { + auto tile = Screen::readTile(x, y); + tile.fg = color; + tile.bold = false; + Screen::paintTile(tile, x, y); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + auto dims = Gui::getDwarfmodeViewDims(); + if (valid_mode()) + { + df::unit *unit = Gui::getAnyUnit(this); + + for (int y = 5, i = (*ui_look_cursor/13)*13; + y <= 17 && i < unit_labors_sidemenu.size(); + ++y, ++i) + { + df::unit_labor labor = unit_labors_sidemenu[i]; + df::unit_labor_category cat = df::unit_labor_category(labor); + + if (is_valid_enum_item(cat) && all_labors_enabled(unit, cat)) + recolor_line(dims.menu_x1, dims.menu_x2, y, COLOR_WHITE); + + if (forbidden_labor(unit, labor)) + recolor_line(dims.menu_x1, dims.menu_x2, y, COLOR_RED + + (unit->status.labors[labor] ? 8 : 0)); + } + } + } + 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); + + if ((input->count(SELECT) || input->count(SELECT_ALL)) && forbidden_labor(unit, labor)) + { + unit->status.labors[labor] = false; + return; + } + else if (input->count(SELECT_ALL) && is_valid_enum_item(cat)) + { + bool new_state = !all_labors_enabled(unit, cat); + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (ENUM_ATTR(unit_labor, category, labor) == cat) + unit->status.labors[labor] = (new_state && !forbidden_labor(unit, labor)); + } + return; + } + } + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(block_labors_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(block_labors_hook, render); diff --git a/plugins/tweak/tweaks/hide-priority.h b/plugins/tweak/tweaks/hide-priority.h new file mode 100644 index 000000000..c9ac78eec --- /dev/null +++ b/plugins/tweak/tweaks/hide-priority.h @@ -0,0 +1,61 @@ +#include "modules/Gui.h" +#include "df/viewscreen_dwarfmodest.h" + +using namespace DFHack; +using df::global::gps; +using df::global::ui_sidebar_menus; + +struct hide_priority_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + inline bool valid_mode () + { + switch (ui->main.mode) + { + case df::ui_sidebar_mode::DesignateMine: + case df::ui_sidebar_mode::DesignateRemoveRamps: + case df::ui_sidebar_mode::DesignateUpStair: + case df::ui_sidebar_mode::DesignateDownStair: + case df::ui_sidebar_mode::DesignateUpDownStair: + case df::ui_sidebar_mode::DesignateUpRamp: + case df::ui_sidebar_mode::DesignateChannel: + case df::ui_sidebar_mode::DesignateGatherPlants: + case df::ui_sidebar_mode::DesignateRemoveDesignation: + case df::ui_sidebar_mode::DesignateSmooth: + case df::ui_sidebar_mode::DesignateCarveTrack: + case df::ui_sidebar_mode::DesignateEngrave: + case df::ui_sidebar_mode::DesignateCarveFortification: + case df::ui_sidebar_mode::DesignateChopTrees: + case df::ui_sidebar_mode::DesignateToggleEngravings: + case df::ui_sidebar_mode::DesignateToggleMarker: + case df::ui_sidebar_mode::DesignateRemoveConstruction: + return true; + default: + return false; + } + } + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (valid_mode()) + { + auto dims = Gui::getDwarfmodeViewDims(); + if (dims.menu_on) + { + int x = dims.menu_x1 + 1, y = gps->dimy - (gps->dimy > 26 ? 8 : 7); + OutputToggleString(x, y, "Show priorities", df::interface_key::CUSTOM_ALT_P, + ui_sidebar_menus->designation.priority_set, true, 0, + COLOR_WHITE, COLOR_LIGHTRED); + } + } + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + if (valid_mode() && input->count(df::interface_key::CUSTOM_ALT_P)) + ui_sidebar_menus->designation.priority_set = !ui_sidebar_menus->designation.priority_set; + else + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(hide_priority_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(hide_priority_hook, render); diff --git a/plugins/tweak/tweaks/max-wheelbarrow.h b/plugins/tweak/tweaks/max-wheelbarrow.h index b21fe22c1..37f292d6b 100644 --- a/plugins/tweak/tweaks/max-wheelbarrow.h +++ b/plugins/tweak/tweaks/max-wheelbarrow.h @@ -35,7 +35,7 @@ struct max_wheelbarrow_hook : df::viewscreen_dwarfmodest { { auto dims = Gui::getDwarfmodeViewDims(); Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN), - dims.menu_x1 + 22, dims.y1 + 6, wheelbarrow_entry + "_"); + dims.menu_x1 + 22, dims.y1 + 6, wheelbarrow_entry + "_ "); } } diff --git a/plugins/tweak/tweaks/title-start-rename.h b/plugins/tweak/tweaks/title-start-rename.h new file mode 100644 index 000000000..baa1552ab --- /dev/null +++ b/plugins/tweak/tweaks/title-start-rename.h @@ -0,0 +1,112 @@ +#include "df/viewscreen_titlest.h" + +using namespace DFHack; + +struct title_start_rename_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + typedef interpose_base::T_sel_subpage T_sel_subpage; + static T_sel_subpage last_subpage; + static bool in_rename; + static bool rename_failed; + static std::string entry; + + inline df::viewscreen_titlest::T_start_savegames *get_cur_save() + { + return vector_get(start_savegames, sel_submenu_line); + } + + inline std::string full_save_dir(const std::string ®ion_name) + { + return std::string("data/save/") + region_name; + } + + bool do_rename() + { + auto save = get_cur_save(); + if (!save) + return false; + if (Filesystem::isdir(full_save_dir(entry))) + return false; + if (rename(full_save_dir(save->save_dir).c_str(), full_save_dir(entry).c_str()) != 0) + return false; + save->save_dir = entry; + entry = ""; + return true; + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (sel_subpage == T_sel_subpage::StartSelectWorld || sel_subpage == T_sel_subpage::StartSelectMode) + { + auto save = get_cur_save(); + if (save) + { + int x = 1, y = 7; + OutputHotkeyString(x, y, + in_rename ? entry.c_str() : "Rename", + df::interface_key::CUSTOM_R, + false, 0, + rename_failed ? COLOR_LIGHTRED : COLOR_WHITE, + in_rename ? COLOR_RED : COLOR_LIGHTRED); + if (in_rename) + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + } + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + if (in_rename) + { + rename_failed = false; + auto string_key = get_string_key(input); + if (input->count(SELECT) && !entry.empty()) + { + if (do_rename()) + in_rename = false; + else + rename_failed = true; + } + else if (input->count(STRING_A000)) + { + if (!entry.empty()) + entry.erase(entry.size() - 1); + } + else if (string_key != NONE) + { + entry += Screen::keyToChar(string_key); + } + else if (input->count(LEAVESCREEN) || (input->count(SELECT) && entry.empty()) || + input->count(STANDARDSCROLL_UP) || input->count(STANDARDSCROLL_DOWN)) + { + entry = ""; + in_rename = false; + std::set tmp; + if (input->count(STANDARDSCROLL_UP)) + tmp.insert(STANDARDSCROLL_UP); + if (input->count(STANDARDSCROLL_DOWN)) + tmp.insert(STANDARDSCROLL_DOWN); + INTERPOSE_NEXT(feed)(&tmp); + } + } + else if (input->count(CUSTOM_R)) + { + in_rename = true; + } + else + { + INTERPOSE_NEXT(feed)(input); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(title_start_rename_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(title_start_rename_hook, feed); + +df::viewscreen_titlest::T_sel_subpage title_start_rename_hook::last_subpage = + df::viewscreen_titlest::T_sel_subpage::None; +bool title_start_rename_hook::in_rename = false; +bool title_start_rename_hook::rename_failed = false; +std::string title_start_rename_hook::entry; diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 7182ee2f2..156552504 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -91,9 +91,9 @@ 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, - bool newline = false, int left_margin = 0, const UIColor bg_color = 0) + 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); + Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text, map); if (newline) { ++y; @@ -104,54 +104,62 @@ static void OutputString(UIColor color, int &x, int &y, const std::string &text, } static 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) + 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); + OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); string display(": "); display.append(text); - OutputString(text_color, x, y, display, newline, left_margin); + 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, - bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) + 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); + 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, - int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) + 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); + OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); string display(": "); display.append(text); display.append(": "); - OutputString(text_color, x, y, display); - OutputString(hotkey_color, x, y, label, newline, left_margin); + OutputString(text_color, x, y, display, false, 0, 0, map); + 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, + 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, - int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN) + int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN, bool map = false) { - OutputString(hotkey_color, x, y, hotkey); - OutputString(COLOR_WHITE, x, y, ": "); - OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin); + OutputString(hotkey_color, x, y, hotkey, false, 0, 0, map); + OutputString(COLOR_WHITE, x, y, ": ", false, 0, 0, map); + 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, - int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) + 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); - OutputString(color, x, y, ": "); + OutputHotkeyString(x, y, text, hotkey, false, 0, color, hotkey_color, map); + OutputString(color, x, y, ": ", false, 0, 0, map); if (state) - OutputString(COLOR_GREEN, x, y, "On", newline, left_margin); + OutputString(COLOR_GREEN, x, y, "On", newline, left_margin, 0, map); else - OutputString(COLOR_GREY, x, y, "Off", newline, left_margin); + 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, - int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) + 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); + OutputToggleString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), state, newline, left_margin, color, hotkey_color, map); } inline string int_to_string(const int n) diff --git a/plugins/weather.cpp b/plugins/weather.cpp deleted file mode 100644 index 481c1a779..000000000 --- a/plugins/weather.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include -#include -#include "modules/World.h" -#include "DataDefs.h" -#include "df/weather_type.h" - -using std::vector; -using std::string; -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("weather"); - -REQUIRE_GLOBAL(current_weather); - -bool locked = false; -unsigned char locked_data[25]; - -command_result weather (color_ostream &out, vector & parameters); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "weather", "Print the weather map or change weather.", - weather, false, - " Prints the current weather map by default.\n" - "Options:\n" - " snow - make it snow everywhere.\n" - " rain - make it rain.\n" - " clear - clear the sky.\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result weather (color_ostream &con, vector & parameters) -{ - int val_override = -1; - bool lock = false; - bool unlock = false; - bool snow = false; - bool rain = false; - bool clear = false; - for(size_t i = 0; i < parameters.size();i++) - { - if(parameters[i] == "rain") - rain = true; - else if(parameters[i] == "snow") - snow = true; - else if(parameters[i] == "clear") - clear = true; - else if(parameters[i] == "lock") - lock = true; - else if(parameters[i] == "unlock") - unlock = true; - else - { - val_override = atoi(parameters[i].c_str()); - if(val_override == 0) - return CR_WRONG_USAGE; - } - } - if(lock && unlock) - { - con << "Lock or unlock? DECIDE!" << std::endl; - return CR_FAILURE; - } - int cnt = 0; - cnt += rain; - cnt += snow; - cnt += clear; - if(cnt > 1) - { - con << "Rain, snow or clear sky? DECIDE!" << std::endl; - return CR_FAILURE; - } - bool something = lock || unlock || rain || snow || clear || val_override != -1; - - CoreSuspender suspend; - - if(!current_weather) - { - con << "Weather support seems broken :(" << std::endl; - return CR_FAILURE; - } - if(!something) - { - // paint weather map - con << "Weather map (C = clear, R = rain, S = snow):" << std::endl; - for(int y = 0; y<5;y++) - { - for(int x = 0; x<5;x++) - { - switch((*current_weather)[x][y]) - { - case weather_type::None: - con << "C "; - break; - case weather_type::Rain: - con << "R "; - break; - case weather_type::Snow: - con << "S "; - break; - default: - con << (int) (*current_weather)[x][y] << " "; - break; - } - } - con << std::endl; - } - } - else - { - // weather changing action! - if(rain) - { - con << "Here comes the rain." << std::endl; - World::SetCurrentWeather(weather_type::Rain); - } - if(snow) - { - con << "Snow everywhere!" << std::endl; - World::SetCurrentWeather(weather_type::Snow); - } - if(clear) - { - con << "Suddenly, sunny weather!" << std::endl; - World::SetCurrentWeather(weather_type::None); - } - if(val_override != -1) - { - con << "I have no damn idea what this is... " << val_override << std::endl; - World::SetCurrentWeather(val_override); - } - // FIXME: weather lock needs map ID to work reliably... needs to be implemented. - } - return CR_OK; -} diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 113f4c89d..18090afc6 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -59,10 +59,13 @@ REQUIRE_GLOBAL(job_next_id); /* Plugin registration */ static command_result workflow_cmd(color_ostream &out, vector & parameters); +static command_result fix_job_postings_cmd(color_ostream &out, vector ¶meters); static void init_state(color_ostream &out); static void cleanup_state(color_ostream &out); +static int fix_job_postings(color_ostream *out = NULL, bool dry_run = false); + DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (!world || !ui) @@ -142,6 +145,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ¶meters) +{ + bool dry = parameters.size(); + int fixed = fix_job_postings(&out, dry); + out << fixed << " job issue(s) " << (dry ? "detected but not fixed" : "fixed") << endl; + return CR_OK; +} + /****************************** * JOB STATE TRACKING STRUCTS * ******************************/ @@ -274,7 +292,7 @@ public: { if (world->frame_counter >= resume_time && actual_job->flags.bits.suspend) { - actual_job->unk_v4020_1 = -1; + Job::removePostings(actual_job, true); actual_job->flags.bits.suspend = false; } } @@ -287,7 +305,7 @@ public: if (!actual_job->flags.bits.suspend) { actual_job->flags.bits.suspend = true; - actual_job->unk_v4020_1 = -1; + Job::removePostings(actual_job, true); } } @@ -406,6 +424,34 @@ public: } }; +static int fix_job_postings (color_ostream *out, bool dry_run) +{ + int count = 0; + df::job_list_link *link = &world->job_list; + while (link) + { + df::job *job = link->item; + if (job) + { + for (size_t i = 0; i < world->job_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) + { + ++count; + if (out) + *out << "Found extra job posting: Job " << job->id << ": " + << Job::getName(job) << endl; + if (!dry_run) + posting->flags.bits.dead = true; + } + } + } + link = link->next; + } + return count; +} + /****************************** * GLOBAL VARIABLES * ******************************/ @@ -502,6 +548,11 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str static void start_protect(color_ostream &out) { + out << "workflow: checking for existing job issues" << endl; + int count = fix_job_postings(&out); + if (count) + out << "workflow: fixed " << count << " job issues" << endl; + check_lost_jobs(out, 0); if (!known_jobs.empty()) @@ -1603,6 +1654,12 @@ static int getCountHistory(lua_State *L) return 1; } +static int fixJobPostings(lua_State *L) +{ + bool dry = lua_toboolean(L, 1); + lua_pushinteger(L, fix_job_postings(NULL, dry)); + return 1; +} DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(deleteConstraint), @@ -1614,6 +1671,7 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(findConstraint), DFHACK_LUA_COMMAND(setConstraint), DFHACK_LUA_COMMAND(getCountHistory), + DFHACK_LUA_COMMAND(fixJobPostings), DFHACK_LUA_END }; diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor index 0b8303f6b..26c600132 160000 --- a/scripts/3rdparty/lethosor +++ b/scripts/3rdparty/lethosor @@ -1 +1 @@ -Subproject commit 0b8303f6b03d574e3a0b3fd8b17b7ff0014af47f +Subproject commit 26c60013223e02b5558e31bed8e0625495430995 diff --git a/scripts/autolabor-artisans.lua b/scripts/autolabor-artisans.lua index 22d394646..68b85817c 100644 --- a/scripts/autolabor-artisans.lua +++ b/scripts/autolabor-artisans.lua @@ -14,7 +14,6 @@ local artisan_labors = { "ARCHITECT", "ANIMALTRAIN", "LEATHER", - "BREWER", "WEAVER", "CLOTHESMAKER", "COOK", diff --git a/scripts/ban-cooking.rb b/scripts/ban-cooking.rb index 906469ea5..535f39126 100644 --- a/scripts/ban-cooking.rb +++ b/scripts/ban-cooking.rb @@ -5,7 +5,8 @@ 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``, and ``seeds`` (non-tree plants with seeds). +``honey``, ``tallow``, ``oil``, ``seeds`` (non-tree plants with seeds), +``brew``, ``mill``, ``thread``, and ``milk``. =end @@ -87,11 +88,45 @@ $script_args.each do |arg| 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/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua index 1d99b2d55..180a3e011 100644 --- a/scripts/devel/export-dt-ini.lua +++ b/scripts/devel/export-dt-ini.lua @@ -1,10 +1,8 @@ -- 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' @@ -66,22 +64,39 @@ end -- List of actual values header('addresses') -address('translation_vector',globals,'world','raws','language','translations') -address('language_vector',globals,'world','raws','language','words') -address('creature_vector',globals,'world','units','all') -address('active_creature_vector',globals,'world','units','active') -address('dwarf_race_index',globals,'ui','race_id') -address('squad_vector',globals,'world','squads','all') -address('current_year',globals,'cur_year') address('cur_year_tick',globals,'cur_year_tick') +address('current_year',globals,'cur_year') address('dwarf_civ_index',globals,'ui','civ_id') -address('races_vector',globals,'world','raws','creatures','all') -address('reactions_vector',globals,'world','raws','reactions') -address('events_vector',globals,'world','history','events') -address('historical_figures_vector',globals,'world','history','figures') -address('fake_identities_vector',globals,'world','identities','all') +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') @@ -96,29 +111,17 @@ 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('inorganics_vector',globals,'world','raws','inorganics') -address('plants_vector',globals,'world','raws','plants','all') -address('material_templates_vector',globals,'world','raws','material_templates') address('all_syndromes_vector',globals,'world','raws','syndromes','all') -address('world_data',globals,'world','world_data') -address('active_sites_vector',df.world_data,'active_site') +address('events_vector',globals,'world','history','events') +address('historical_figures_vector',globals,'world','history','figures') address('world_site_type',df.world_site,'type') -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('active_sites_vector',df.world_data,'active_site') header('offsets') address('word_table',df.language_translation,'words') @@ -220,6 +223,7 @@ 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') @@ -400,9 +404,20 @@ 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') @@ -416,13 +431,33 @@ 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,'id') +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') --- TODO: add an api function to retrieve the checksum -out:write('checksum=<>\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') diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 17bc4f6f9..89cb0918a 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -259,9 +259,16 @@ local function dwarfmode_to_top() return true end -local function feed_menu_choice(catnames,catkeys,enum) +local function feed_menu_choice(catnames,catkeys,enum,enter_seq,exit_seq,prompt) return function (idx) + if idx == 0 and prompt and not utils.prompt_yes_no(' Proceed?', true) 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]] @@ -455,6 +462,22 @@ 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) @@ -468,7 +491,7 @@ end local function find_enabler() -- Data from data/init/colors.txt - local colors = { + 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, @@ -476,10 +499,21 @@ local function find_enabler() 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 @@ -520,6 +554,9 @@ local function find_gps() 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 @@ -1155,6 +1192,48 @@ NOTE: If not done after first 3-4 steps, resize the game window.]], 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 -- @@ -1327,7 +1406,7 @@ end local function find_cur_year_tick() local zone if os_type == 'windows' then - zone = zoomed_searcher('artifact_next_id', -32) + zone = zoomed_searcher('ui_unit_view_mode', 0x200) else zone = zoomed_searcher('cur_year', 128) end @@ -1529,6 +1608,53 @@ Searching for pause_state. Please do as instructed below:]], 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 -- @@ -1568,6 +1694,7 @@ 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') @@ -1583,6 +1710,81 @@ 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') diff --git a/scripts/devel/save-version.lua b/scripts/devel/save-version.lua new file mode 100644 index 000000000..d06745577 --- /dev/null +++ b/scripts/devel/save-version.lua @@ -0,0 +1,147 @@ +-- 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", +} + +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/unit-path.lua b/scripts/devel/unit-path.lua index d05203b71..2eff7ed2d 100644 --- a/scripts/devel/unit-path.lua +++ b/scripts/devel/unit-path.lua @@ -20,11 +20,16 @@ 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() @@ -139,12 +144,11 @@ function UnitPathUI:onRenderBody(dc) end local cursor = guidm.getCursorPos() - local has_path = #self.unit.path.path.x>0 local vp = self:getViewport() local mdc = gui.Painter.new(self.df_layout.map) - if not has_path then + 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 @@ -153,7 +157,7 @@ function UnitPathUI:onRenderBody(dc) else self:renderPath(mdc,vp,cursor) - dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal]) + dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal] or '?') end dc:newline():pen(COLOR_GREY) @@ -183,7 +187,7 @@ function UnitPathUI:onRenderBody(dc) dc:newline():newline(1):pen(COLOR_WHITE) dc:key('CUSTOM_Z'):string(": Zoom unit, ") - dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,has_path) + 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) @@ -194,7 +198,7 @@ function UnitPathUI:onInput(keys) if keys.CUSTOM_Z then self:moveCursorTo(copyall(self.unit.pos)) elseif keys.CUSTOM_G then - if #self.unit.path.path.x > 0 then + if self.has_goal then self:moveCursorTo(copyall(self.unit.path.dest)) end elseif keys.CUSTOM_N then @@ -208,6 +212,10 @@ function UnitPathUI:onInput(keys) 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 diff --git a/scripts/emigration.lua b/scripts/emigration.lua index d523177bf..aba62fa2b 100644 --- a/scripts/emigration.lua +++ b/scripts/emigration.lua @@ -26,7 +26,7 @@ end function desireToStay(unit,method,civ_id) -- on a percentage scale - value = 100 - unit.status.current_soul.personality.stress_level / 5000 + 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 @@ -60,12 +60,12 @@ function canLeave(unit) if skill.rating > 14 then return false end end if unit.flags1.caged - or u.race ~= df.global.ui.race_id - or u.civ_id ~= df.global.ui.civ_id - or dfhack.units.isDead(u) - or dfhack.units.isOpposedToLife(u) - or u.flags1.merchant - or u.flags1.diplomat + 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 @@ -109,8 +109,10 @@ function checkmigrationnow() end local function event_loop() - checkmigrationnow() - dfhack.timeout(1, 'months', event_loop) + if enabled then + checkmigrationnow() + dfhack.timeout(1, 'months', event_loop) + end end dfhack.onStateChange.loadEmigration = function(code) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 3c2999412..82cdf0ab1 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -12,10 +12,11 @@ The 'info' option exports more data than is possible in vanilla, to a Options: -:info: Exports the world/gen info, the legends XML, and 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 +: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]] @@ -85,155 +86,304 @@ end --create an extra legends xml with extra data, by Mason11987 for World Viewer function export_more_legends_xml() - 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 + 1 + 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) - io.output(tostring(df.global.world.cur_savegame.save_dir).."-"..date_str.."-legends_plus.xml") - - io.write ("".."\n") - io.write ("".."\n") - io.write (""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."".."\n") - io.write (""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."".."\n") + 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") - io.write ("".."\n") + file:write("\n") for regionK, regionV in ipairs(df.global.world.world_data.regions) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..regionV.index.."".."\n") - io.write ("\t\t".."") + 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 - io.write (xVal..","..regionV.region_coords.y[xK].."|") + file:write(xVal..","..regionV.region_coords.y[xK].."|") end - io.write ("\n") - io.write ("\t".."".."\n") + file:write("\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..regionV.index.."".."\n") - io.write ("\t\t".."") + 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 - io.write (xVal..","..regionV.region_coords.y[xK].."|") + file:write(xVal..","..regionV.region_coords.y[xK].."|") end - io.write ("\n") - io.write ("\t".."".."\n") + file:write("\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for siteK, siteV in ipairs(df.global.world.world_data.sites) do - if (#siteV.buildings > 0) then - io.write ("\t".."".."\n") - for k,v in pairs(siteV) do - if (k == "id") then - io.write ("\t\t".."<"..k..">"..tostring(v).."".."\n") - elseif (k == "buildings") then - io.write ("\t\t".."".."\n") + 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 - io.write ("\t\t\t".."".."\n") - io.write ("\t\t\t\t"..""..buildingV.id.."".."\n") - io.write ("\t\t\t\t"..""..df.abstract_building_type[buildingV:getType()]:lower().."".."\n") + file:write("\t\t\t\n") + file:write("\t\t\t\t"..buildingV.id.."\n") + file:write("\t\t\t\t"..df.abstract_building_type[buildingV:getType()]:lower().."\n") if (df.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire") then - io.write ("\t\t\t\t"..""..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."".."\n") - io.write ("\t\t\t\t"..""..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."".."\n") + -- if spire: unk_50 should be name and unk_bc some kind of flag + 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 - io.write ("\t\t\t".."".."\n") + 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 - io.write ("\t\t".."".."\n") + file:write("\t\t\n") end end - io.write ("\t".."".."\n") end + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..wcV.id.."".."\n") - io.write ("\t\t"..""..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."".."\n") - io.write ("\t\t"..""..(df.world_construction_type[wcV:getType()]):lower().."".."\n") - io.write ("\t\t".."") + 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.world_construction_type[wcV:getType()]):lower().."\n") + file:write("\t\t") for xK, xVal in ipairs(wcV.square_pos.x) do - io.write (xVal..","..wcV.square_pos.y[xK].."|") + file:write(xVal..","..wcV.square_pos.y[xK].."|") end - io.write ("\n") - io.write ("\t".."".."\n") + file:write("\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..artifactV.id.."".."\n") + file:write("\t\n") + file:write("\t\t"..artifactV.id.."\n") if (artifactV.item:getType() ~= -1) then - io.write ("\t\t"..""..tostring(df.item_type[artifactV.item:getType()]):lower().."".."\n") + file:write("\t\t"..tostring(df.item_type[artifactV.item:getType()]):lower().."\n") if (artifactV.item:getSubtype() ~= -1) then - io.write ("\t\t"..""..artifactV.item.subtype.name.."".."\n") + 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 - io.write ("\t\t"..""..artifactV.item.description:lower().."".."\n") + file:write("\t\t"..dfhack.df2utf(artifactV.item.description:lower()).."\n") end if (artifactV.item:getMaterial() ~= -1 and artifactV.item:getMaterialIndex() ~= -1) then - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."\n") end - io.write ("\t".."".."\n") + file:write("\t\n") end - io.write ("".."\n") - - io.write ("".."\n".."".."\n") + 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") - io.write ("".."\n") + file:write("\n") for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..entityPopV.id.."".."\n") + 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() - io.write ("\t\t"..""..raceName..":"..entityPopV.counts[raceK].."".."\n") + file:write("\t\t"..raceName..":"..entityPopV.counts[raceK].."\n") end - io.write ("\t\t"..""..entityPopV.civ_id.."".."\n") - io.write ("\t".."".."\n") + file:write("\t\t"..entityPopV.civ_id.."\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for entityK, entityV in ipairs(df.global.world.entities.all) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..entityV.id.."".."\n") - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."".."\n") - io.write ("\t\t"..""..(df.historical_entity_type[entityV.type]):lower().."".."\n") - if (df.historical_entity_type[entityV.type]):lower() == "religion" then -- Get worshipped figure - if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nill and - #entityV.unknown1b.worship == 1) then - io.write ("\t\t"..""..entityV.unknown1b.worship[0].."".."\n") - else - print(entityV.unknown1b, entityV.unknown1b.worship, #entityV.unknown1b.worship) + 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.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 - io.write ("\t\t".."".."\n") + file:write("\t\t\n") for k, v in pairs(link) do if (k == "type") then - io.write ("\t\t\t".."<"..k..">"..tostring(df.entity_entity_link_type[v]).."".."\n") + file:write("\t\t\t<"..k..">"..tostring(df.entity_entity_link_type[v]).."\n") else - io.write ("\t\t\t".."<"..k..">"..v.."".."\n") + file:write("\t\t\t<"..k..">"..v.."\n") end end - io.write ("\t\t".."".."\n") + 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 - io.write ("\t\t"..""..link.."".."\n") + 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("\n") + 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.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.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.written_content_type[wcV.type] or wcV.type).."\n") + for styleK, styleV in pairs(wcV.styles) do + file:write("\t\t\n") end - io.write ("\t".."".."\n") + file:write("\t\t"..wcV.author.."\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.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 @@ -243,7 +393,9 @@ function export_more_legends_xml() 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 @@ -274,9 +426,9 @@ function export_more_legends_xml() or event:getType() == df.history_event_type.HIST_FIGURE_WOUNDED or event:getType() == df.history_event_type.HIST_FIGURE_DIED then - io.write ("\t".."".."\n") - io.write ("\t\t"..""..event.id.."".."\n") - io.write ("\t\t"..""..tostring(df.history_event_type[event:getType()]):lower().."".."\n") + file:write("\t\n") + file:write("\t\t"..event.id.."\n") + file:write("\t\t"..tostring(df.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) @@ -284,70 +436,70 @@ function export_more_legends_xml() 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 - io.write ("\t\t".."<"..k..">"..df.histfig_entity_link_type[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.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 - io.write ("\t\t"..""..tostring(entityPositionsV.name[0]):lower().."".."\n") + file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") break end end else - io.write ("\t\t".."-1".."\n") + 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 - io.write ("\t\t"..""..tostring(entityPositionsV.name[0]):lower().."".."\n") + file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") break end end else - io.write ("\t\t".."-1".."\n") + file:write("\t\t-1\n") end elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "link_type" then - io.write ("\t\t".."<"..k..">"..df.histfig_entity_link_type[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.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 - io.write ("\t\t"..""..tostring(entityPositionsV.name[0]):lower().."".."\n") + file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") break end end else - io.write ("\t\t".."-1".."\n") + file:write("\t\t-1\n") end elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then - io.write ("\t\t"..""..df.histfig_hf_link_type[v]:lower().."".."\n") + file:write("\t\t"..df.histfig_hf_link_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then - io.write ("\t\t"..""..df.histfig_site_link_type[v]:lower().."".."\n") + file:write("\t\t"..df.histfig_site_link_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then - io.write ("\t\t"..""..df.histfig_site_link_type[v]:lower().."".."\n") + file:write("\t\t"..df.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 ) and k == "item_type" then - io.write ("\t\t"..""..df.item_type[v]:lower().."".."\n") + file:write("\t\t"..df.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 ) and k == "item_subtype" then --if event.item_type > -1 and v > -1 then - io.write ("\t\t".."<"..k..">"..getItemSubTypeName(event.item_type,v).."".."\n") + file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,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 - io.write ("\t\t"..""..event.mattype.."".."\n") - io.write ("\t\t"..""..event.matindex.."".."\n") + file:write("\t\t"..event.mattype.."\n") + file:write("\t\t"..event.matindex.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."".."\n") + 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 @@ -355,19 +507,19 @@ function export_more_legends_xml() ) and k == "mat_type" then if (v > -1) then if (dfhack.matinfo.decode(event.mat_type, event.mat_index) == nil) then - io.write ("\t\t"..""..event.mat_type.."".."\n") - io.write ("\t\t"..""..event.mat_index.."".."\n") + file:write("\t\t"..event.mat_type.."\n") + file:write("\t\t"..event.mat_index.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."".."\n") + 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 - io.write ("\t\t"..""..event.imp_mat_type.."".."\n") - io.write ("\t\t"..""..event.imp_mat_index.."".."\n") + file:write("\t\t"..event.imp_mat_type.."\n") + file:write("\t\t"..event.imp_mat_index.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."\n") end end @@ -387,76 +539,76 @@ function export_more_legends_xml() event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED or event:getType() == df.history_event_type.TOPICAGREEMENT_MADE ) and k == "topic" then - io.write ("\t\t"..""..tostring(df.meeting_topic[v]):lower().."".."\n") + file:write("\t\t"..tostring(df.meeting_topic[v]):lower().."\n") elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then - io.write ("\t\t"..""..df.improvement_type[v]:lower().."".."\n") - elseif ((event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT and k == "figures") or - (event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "group") + file:write("\t\t"..df.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 - io.write ("\t\t".."<"..k..">"..detailV.."".."\n") + 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 - io.write ("\t\t".."<"..k..">"..(df.global.world.raws.creatures.all[detailV].creature_id):lower().."".."\n") + 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 - io.write ("\t\t".."<"..k.."_item_type"..">"..tostring(df.item_type[event.props.item.item_type]):lower().."".."\n") - io.write ("\t\t".."<"..k.."_item_subtype"..">"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."".."\n") + file:write("\t\t<"..k.."_item_type>"..tostring(df.item_type[event.props.item.item_type]):lower().."\n") + file:write("\t\t<"..k.."_item_subtype>"..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 - io.write ("\t\t"..""..event.props.item.mat_type.."".."\n") - io.write ("\t\t"..""..event.props.item.mat_index.."".."\n") + file:write("\t\t"..event.props.item.mat_type.."\n") + file:write("\t\t"..event.props.item.mat_index.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."\n") end end - --io.write ("\t\t".."<"..k.."_item_mat_type"..">"..tostring(event.props.item.mat_type).."".."\n") - --io.write ("\t\t".."<"..k.."_item_mat_index"..">"..tostring(event.props.item.mat_index).."".."\n") - io.write ("\t\t".."<"..k.."_pile_type"..">"..tostring(event.props.pile_type).."".."\n") + --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] - io.write ("\t\t"..""..thisIdentity.name.first_name.."".."\n") - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."".."\n") - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."".."\n") + 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 - io.write ("\t\t"..""..df.global.world.identities.all[v].histfig_id.."".."\n") + 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 - io.write ("\t\t"..""..df.building_type[v]:lower().."".."\n") + file:write("\t\t"..df.building_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_subtype" then if (df.building_type[event.building_type]:lower() == "furnace") then - io.write ("\t\t"..""..df.furnace_type[v]:lower().."".."\n") + file:write("\t\t"..df.furnace_type[v]:lower().."\n") elseif v > -1 then - io.write ("\t\t"..""..tostring(v).."".."\n") + file:write("\t\t"..tostring(v).."\n") end elseif k == "race" then if v > -1 then - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[v].creature_id):lower().."".."\n") + file:write("\t\t"..df.global.world.raws.creatures.all[v].name[0].."\n") end elseif k == "caste" then if v > -1 then - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."".."\n") + 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 - io.write ("\t\t"..""..df.global.world.raws.interactions[v].str[3].value.."".."\n") - io.write ("\t\t"..""..df.global.world.raws.interactions[v].str[4].value.."".."\n") + 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 - io.write ("\t\t"..""..df.global.world.raws.interactions[v].str[2].value.."".."\n") + 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 - io.write ("\t\t".."<"..detailK..">"..detailV.."".."\n") + 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() == 1) then - io.write ("\t\t"..""..refv.artifact_id.."".."\n") + if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then + file:write("\t\t"..refv.artifact_id.."\n") break end end @@ -466,27 +618,27 @@ function export_more_legends_xml() end elseif (detailK == "item_type") then if event.weapon.item > -1 then - io.write ("\t\t".."<"..detailK..">"..tostring(df.item_type[detailV]):lower().."".."\n") + file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."\n") end elseif (detailK == "item_subtype") then if event.weapon.item > -1 and detailV > -1 then - io.write ("\t\t".."<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."".."\n") + file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."\n") end elseif (detailK == "mattype") then if (detailV > -1) then - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."".."\n") + 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 - io.write ("\t\t".."<"..detailK..">"..detailV.."".."\n") + 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() == 1) then - io.write ("\t\t"..""..refv.artifact_id.."".."\n") + if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then + file:write("\t\t"..refv.artifact_id.."\n") break end end @@ -495,42 +647,44 @@ function export_more_legends_xml() end elseif (detailK == "shooter_item_type") then if event.weapon.shooter_item > -1 then - io.write ("\t\t".."<"..detailK..">"..tostring(df.item_type[detailV]):lower().."".."\n") + file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."\n") end elseif (detailK == "shooter_item_subtype") then if event.weapon.shooter_item > -1 and detailV > -1 then - io.write ("\t\t".."<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."".."\n") + file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."\n") end elseif (detailK == "shooter_mattype") then if (detailV > -1) then - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."".."\n") + 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 - io.write ("\t\t".."<"..detailK..">"..detailV.."".."\n") + file:write("\t\t<"..detailK..">"..detailV.."\n") end end elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "death_cause" then - io.write ("\t\t".."<"..k..">"..df.death_type[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.death_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.CHANGE_HF_JOB and (k == "new_job" or k == "old_job") then - io.write ("\t\t".."<"..k..">"..df.profession[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.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 - io.write ("\t\t".."<"..k..">"..tostring(v).."".."\n") + file:write("\t\t<"..k..">"..tostring(v).."\n") end end - io.write ("\t".."".."\n") + file:write("\t\n") end end - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.close() + 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') @@ -598,6 +752,8 @@ if dfhack.gui.getCurFocus() == "legends" or dfhack.gui.getCurFocus() == "dfhack/ 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 diff --git a/scripts/feature.lua b/scripts/feature.lua new file mode 100644 index 000000000..7c634de6b --- /dev/null +++ b/scripts/feature.lua @@ -0,0 +1,75 @@ +-- List or manage map features & enable magma furnaces +local help = [[=begin + +feature +======= +Enables management of map features. + +* Discovering a magma feature (magma pool, volcano, magma sea, or curious + underground structure) permits magma workshops and furnaces to be built. +* Discovering a cavern layer causes plants (trees, shrubs, and grass) from + that cavern to grow within your fortress. + +Options: + +:list: Lists all map features in your current embark by index. +:magma: Enable magma furnaces (discovers a random magma feature). +:show X: Marks the selected map feature as discovered. +:hide X: Marks the selected map feature as undiscovered. + +=end]] + +local map_features = df.global.world.features.map_features + +function toggle_feature(idx, discovered) + idx = tonumber(idx) + if idx < 0 or idx >= #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/dry-buckets.lua b/scripts/fix/dry-buckets.lua new file mode 100644 index 000000000..91cff2cd1 --- /dev/null +++ b/scripts/fix/dry-buckets.lua @@ -0,0 +1,28 @@ +-- 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/gaydar.lua b/scripts/gaydar.lua index 83de3ae6a..fb357fd1c 100644 --- a/scripts/gaydar.lua +++ b/scripts/gaydar.lua @@ -72,6 +72,9 @@ local function determineorientation(unit) if unit.sex~=-1 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' @@ -144,6 +147,7 @@ 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 @@ -155,7 +159,7 @@ function isGay(v) end function isAsexual(v) - if v:find('asexual') then return true else return false end + if v:find('asexual') or v:find('indeterminate') then return true else return false end end function isBi(v) diff --git a/scripts/gui/confirm-opts.lua b/scripts/gui/confirm-opts.lua new file mode 100644 index 000000000..d2aba2a94 --- /dev/null +++ b/scripts/gui/confirm-opts.lua @@ -0,0 +1,74 @@ +-- 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/family-affairs.lua b/scripts/gui/family-affairs.lua index 51525f36e..e3806e9f2 100644 --- a/scripts/gui/family-affairs.lua +++ b/scripts/gui/family-affairs.lua @@ -28,6 +28,8 @@ The target/s must be alive, sane, and in fortress mode. =end]] +helpstr = help:gsub('=begin', ''):gsub('=end', '') + local dlg = require ('gui.dialogs') function ErrorPopup (msg,color) @@ -36,14 +38,8 @@ function ErrorPopup (msg,color) dlg.showMessage("Dwarven Family Affairs", msg, color, nil) end -function AnnounceAndGamelog (text,l) - if not l then l = true end - dfhack.gui.showAnnouncement(text, _G["COLOR_LIGHTMAGENTA"]) - if l then - local log = io.open('gamelog.txt', 'a') - log:write(text.."\n") - log:close() - end +function AnnounceAndGamelog(text) + dfhack.gui.showAnnouncement(text, COLOR_LIGHTMAGENTA) end function ListPrompt (msg, choicelist, bool, yes_func) @@ -181,7 +177,7 @@ function ChooseNewSpouse (source) if not source then qerror("no unit") return end - if (source.profession == 103 or source.profession == 104) then + 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 @@ -199,7 +195,7 @@ function ChooseNewSpouse (source) and v.sex ~= source.sex and v.relations.spouse_id == -1 and v.relations.lover_id == -1 - and not (v.profession == 103 or v.profession == 104) + and dfhack.units.isAdult(v) then table.insert(choicelist,dfhack.TranslateName(v.name)..', '..dfhack.units.getProfessionName(v)) table.insert(targetlist,v) @@ -229,11 +225,11 @@ function MainDialog (source) local choicelist = {} local on_select = {} - local child = (source.profession == 103 or source.profession == 104) - local is_single = source.relations.spouse_id == -1 and source.relations.lover_id == -1 - local ready_for_marriage = single and not child + 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 not child then + if adult then table.insert(choicelist,"Remove romantic relationships (if any)") table.insert(on_select, Divorce) if ready_for_marriage then @@ -258,8 +254,8 @@ local args = {...} if args[1] == "help" or args[1] == "?" then print(helpstr) return end -if not df.global.gamemode == 0 then - print (helpstr) qerror ("invalid gamemode") return +if not dfhack.world.isFortressMode() then + print (helpstr) qerror ("invalid game mode") return end if args[1] == "divorce" and tonumber(args[2]) then @@ -287,10 +283,10 @@ if selected then if dfhack.units.isCitizen(selected) and dfhack.units.isSane(selected) then MainDialog(selected) else - qerror("You must select sane fortress citizen.") + qerror("You must select a sane fortress citizen.") return end else print (helpstr) - qerror("select a sane fortress dwarf") + qerror("Select a sane fortress dwarf") end diff --git a/scripts/gui/prerelease-warning.lua b/scripts/gui/prerelease-warning.lua new file mode 100644 index 000000000..e902f3353 --- /dev/null +++ b/scripts/gui/prerelease-warning.lua @@ -0,0 +1,59 @@ +-- Shows the warning about missing configuration file. +--[[=begin + +gui/prerelease-warning +====================== +Shows a warning on world load for pre-release builds. + +=end]] + +shown = shown or false +if shown then return end +if not dfhack.isPrerelease() 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 then + return +end + +local gui = require 'gui' +local dlg = require 'gui.dialogs' +local utils = require 'utils' + +message = { + 'This is a prerelease 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!'}, +} + +pack_message = pack_message or [[ + +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 + pack_message = '' +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_YELLOW) + dfhack.print(v) + end +end + +dfhack.color(COLOR_RESET) +dfhack.print('\n\n') + +dlg.showMessage('Warning', message, COLOR_YELLOW) +shown = true diff --git a/scripts/startdwarf.rb b/scripts/startdwarf.rb index 4e7d01c07..a592d5391 100644 --- a/scripts/startdwarf.rb +++ b/scripts/startdwarf.rb @@ -15,5 +15,6 @@ 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/weather.lua b/scripts/weather.lua new file mode 100644 index 000000000..a61ab48bd --- /dev/null +++ b/scripts/weather.lua @@ -0,0 +1,46 @@ +-- 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/travis/authors-rst.py b/travis/authors-rst.py new file mode 100644 index 000000000..2d7274219 --- /dev/null +++ b/travis/authors-rst.py @@ -0,0 +1,67 @@ +""" Overly-complicated script to check formatting/sorting in Authors.rst """ + +import re, sys + +def main(): + success = [True] + def error(line, msg, **kwargs): + info = '' + for k in kwargs: + info += ' %s %s:' % (k, kwargs[k]) + print('line %i:%s %s' % (line, info, msg)) + success[0] = False + with open('docs/Authors.rst', 'rb') as f: + lines = list(map(lambda line: line.decode('utf8').replace('\n', ''), f.readlines())) + + if lines[1].startswith('='): + if len(lines[0]) != len(lines[1]): + error(2, 'Length of header does not match underline') + if lines[1].replace('=', ''): + error(2, 'Invalid header') + + first_div_index = list(filter(lambda pair: pair[1].startswith('==='), enumerate(lines[2:])))[0][0] + 2 + first_div = lines[first_div_index] + div_indices = [] + for i, line in enumerate(lines[first_div_index:]): + line_number = i + first_div_index + 1 + if '\t' in line: + error(line_number, 'contains tabs') + if line.startswith('==='): + div_indices.append(i + first_div_index) + if not re.match(r'^=+( =+)+$', line): + error(line_number, 'bad table divider') + if line != lines[first_div_index]: + error(line_number, 'malformed table divider') + if len(div_indices) < 3: + error(len(lines), 'missing table divider(s)') + for i in div_indices[3:]: + error(i + 1, 'extra table divider') + + col_ranges = [] + i = 0 + while True: + j = first_div.find(' ', i) + col_ranges.append(slice(i, j if j > 0 else None)) + if j == -1: + break + i = j + 1 + + for i, line in enumerate(lines[div_indices[1] + 1:div_indices[2]]): + line_number = i + div_indices[1] + 2 + for c, col in enumerate(col_ranges): + cell = line[col] + if cell.startswith(' '): + error(line_number, 'text does not start in correct location', column=c+1) + # check for text extending into next column if this isn't the last column + if col.stop is not None and col.stop < len(line) and line[col.stop] != ' ': + error(line_number, 'text extends into next column', column=c+1) + if i > 0: + prev_line = lines[div_indices[1] + i] + if line.lower()[col_ranges[0]] < prev_line.lower()[col_ranges[0]]: + error(line_number, 'not sorted: should come before line %i ("%s" before "%s")' % + (line_number - 1, line[col_ranges[0]].rstrip(' '), prev_line[col_ranges[0]].rstrip(' '))) + + return success[0] + +if __name__ == '__main__': + sys.exit(int(not main())) diff --git a/travis/script-in-readme.py b/travis/script-in-readme.py index a6804fb00..b8ad67b65 100644 --- a/travis/script-in-readme.py +++ b/travis/script-in-readme.py @@ -1,3 +1,4 @@ +from __future__ import print_function from io import open import os from os.path import basename, dirname, join, splitext @@ -16,6 +17,8 @@ def check_file(fname): errors, doclines = 0, [] with open(fname, errors='ignore') as f: for l in f.readlines(): + if not l.strip(): + continue if doclines or l.strip().endswith('=begin'): doclines.append(l.rstrip()) if l.startswith('=end'): @@ -26,7 +29,7 @@ def check_file(fname): else: print('Error: no documentation in: ' + fname) return 1 - title, underline = doclines[2], doclines[3] + title, underline = doclines[1], doclines[2] if underline != '=' * len(title): print('Error: title/underline mismatch:', fname, title, underline) errors += 1