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

develop
PeridexisErrant 2016-02-01 16:36:42 +11:00
commit 9a81fb1add
108 changed files with 4508 additions and 1339 deletions

42
.gitignore vendored

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

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

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

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

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

@ -1,7 +1,7 @@
#include "jsoncpp.h"
#pragma once
namespace JsonEx {
namespace Json {
template <typename T> bool is (const Json::Value &val) { return false; }
template <typename T> T as (const Json::Value &val);
@ -11,7 +11,7 @@ namespace JsonEx {
template<> inline bool is<type> (const Json::Value &val) { return val.is_func(); } \
template<> inline type as<type> (const Json::Value &val) { return val.as_func(); } \
template<> inline type get<type> (const Json::Value &val, const std::string &key, const type &default_) \
{ Json::Value x = val[key]; return is<type>(x) ? as<type>(x) : default_; }
{ const Json::Value &x = val[key]; return is<type>(x) ? as<type>(x) : default_; }
define_helpers(bool, isBool, asBool);
define_helpers(Json::Int, isInt, asInt);
define_helpers(Json::UInt, isUInt, asUInt);

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

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

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

@ -10,24 +10,43 @@ and `install the latest release instead <installing>`.
.. 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=<path to DF>
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.
<path to DF> should be a path to a copy of Dwarf Fortress, of the appropriate
version for the DFHack you are building. This will build the library along
with the normal set of plugins and install them into your DF folder.
Alternatively, you can use ccmake instead of cmake::
@ -98,18 +143,17 @@ 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
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 <path to df>/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 <http://brew.sh/>`_::
Using `Homebrew <http://brew.sh/>`_ (recommended)::
brew tap homebrew/versions
brew install git
@ -150,9 +225,17 @@ 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.
#. Install Perl dependencies
* Using system Perl
1. ``sudo cpan``
* ``sudo cpan``
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.
@ -162,15 +245,26 @@ following environment variable::
sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml
2. ``install XML::LibXML``
3. ``install XML::LibXSLT``
* ``install XML::LibXML``
* ``install XML::LibXSLT``
#. Get the dfhack source::
* In a separate, local Perl install
git clone --recursive https://github.com/DFHack/dfhack.git
cd dfhack
Rather than using system Perl, you might also want to consider
the Perl manager, `Perlbrew <http://perlbrew.pl>`_.
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)``)::
@ -182,14 +276,19 @@ following environment variable::
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=<path to DF>
make install # or make -j X install on multi-core systems to compile with X parallel processes
<path to DF> 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 <http://download.microsoft.com/download/1/E/5/1E5F1C0A-0D5B-426A-A603-1798B951DDAE/VS2010Express1.iso>`_.
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 <https://www.microsoft.com/en-gb/download/details.aspx?id=23691>`_.
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 <http://cmder.net/>`_;
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 <https://git-for-windows.github.io>`_ (command-line and GUI)
* `tortoisegit <https://tortoisegit.org>`_ (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 <http://www.cmake.org/cmake/resources/software.html>`_.
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 <http://download.microsoft.com/download/1/E/5/1E5F1C0A-0D5B-426A-A603-1798B951DDAE/VS2010Express1.iso>`_.
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 <http://strawberryperl.com>`_ 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 <http://strawberryperl.com>`_ works nicely for this and includes
all of the required packages.
* ``<path to perl>\c\bin``
* ``<path to perl>\perl\site\bin``
* ``<path to perl>\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 <http://cmder.net/>`_. 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) <http://sphinx-doc.org/rest.html>`_ 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 <http://sphinx-doc.org>`_.
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 <https://dfhack.readthedocs.org>`_.
(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=<path to DF>
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 <compile-windows>`::
choco install python pip -y
Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run::
pip install sphinx

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

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

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

@ -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 <enable>`.
.. _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.

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

@ -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<string,string> &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<std::string> 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<string> 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<string> 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<df::viewscreen_game_cleanerst>(screen) ||
strict_virtual_cast<df::viewscreen_loadgamest>(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);

@ -33,5 +33,14 @@ namespace DFHack {
return false;
#endif
}
bool is_prerelease()
{
#ifdef DFHACK_PRERELEASE
return true;
#else
return false;
#endif
}
}
}

@ -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::Graphic* g = c.getGraphic();
DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h);

@ -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::Graphic* g = c.getGraphic();
DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h);

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

@ -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 <std::string> & parameters)
{

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

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

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

@ -164,7 +164,6 @@ namespace DFHack
out << sstr.str();
return out;
}
//private:
uint8_t * bits;
uint32_t size;
};

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

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

@ -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<void *, std::string> classNameCache;
uint32_t my_pe;
std::string my_md5;
};
class DFHACK_EXPORT ClassNameCheck

@ -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 <std::string> & 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<std::string, void**> bindings;
bool bind(DFLibrary* lib);
};
#define PLUGIN_EXPORT_BIND(sym) bindings.insert(std::pair<std::string, void**>(#sym, (void**)&this->sym))
#define PLUGIN_EXPORT_BINDN(sym, name) bindings.insert(std::pair<std::string, void**>(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 <std::string> & 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 \

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

@ -0,0 +1,71 @@
#pragma once
#include <vector>
namespace DFHack {
#define GUI_HOOK_DECLARE(name, rtype, args) DFHACK_EXPORT extern DFHack::GuiHooks::Hook<rtype args> name
#define GUI_HOOK_DEFINE(name, base_func) DFHack::GuiHooks::Hook<decltype(base_func)> name(base_func)
#define GUI_HOOK_TOP(name) name.top()
#define GUI_HOOK_CALLBACK(hook, name, callback) DFHack::GuiHooks::Hook<decltype(callback)>::Callback name(&hook, callback)
namespace GuiHooks {
template <typename T_func>
class Hook {
typedef Hook<T_func> T_hook;
friend class Callback;
T_func *base_func;
std::vector<T_func*> 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); }
};
};
}
}

@ -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<df::job*> *pvec, int *id_var);

@ -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<df::interface_key> 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 {

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

@ -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;
@ -346,27 +351,59 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in
{
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;
}
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;
}
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;
}
case building_type::Trap:
{
auto obj = (df::building_trapst*)bld;
if (VIRTUAL_CAST_VAR(obj, df::building_trapst, bld))
{
if (obj->trap_type == trap_type::PressurePlate)
obj->ready_timeout = 500;
}
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:
@ -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<df::building_civzonest>(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<df::building_civzonest>(bld))
zone->zone_num = getMaxCivzoneId() + 1;
break;
default:
break;
}

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

@ -1366,12 +1366,13 @@ int32_t Items::createItem(df::item_type item_type, int16_t item_subtype, int16_t
}
//makeItem
vector<df::reaction_product*> out_products;
vector<df::item*> out_items;
vector<df::reaction_reagent*> in_reag;
vector<df::item*> 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 )

@ -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<df::job*> *pvec, int *id_var)
{
using df::global::world;

@ -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<df::viewscreen*, Plugin*> 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<dfhack_viewscreen*>(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 {

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

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

@ -1 +1 @@
Subproject commit 378a580f7e333607a64a301d598e3885954a5d9d
Subproject commit 49eff8dca662bff83ff2191ac5b611504c72ae1c

@ -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 <<EOF
NOTICE: $libcxx_orig has been moved to $libcxx_backup
for better compatibility. If you are using an older distro and this breaks,
run "cp $libcxx_backup $libcxx_orig", or add this to
$HOME/$RC to affect future DFHack installations:
export DFHACK_NO_RENAME_LIBSTDCXX=1
EOF
fi
# Save current terminal settings
old_tty_settings=$(stty -g)
# Use distro_fixes.sh from LNP if it exists
DISTROFIXES="distro_fixes.sh"
if [ -r "$DISTROFIXES" ]; then
. "./$DISTROFIXES"
fi
# Now run
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"

@ -0,0 +1,3 @@
# You can add custom plugins here to avoid touching plugins/CMakeLists.txt,
# This can be useful if you've made modifications to that file and try to
# switch between branches that have also made modifications to it.

@ -90,6 +90,7 @@ if (BUILD_SUPPORTED)
# DFHACK_PLUGIN(advtools advtools.cpp)
DFHACK_PLUGIN(autochop autochop.cpp)
DFHACK_PLUGIN(autodump autodump.cpp)
DFHACK_PLUGIN(autogems autogems.cpp)
DFHACK_PLUGIN(autohauler autohauler.cpp)
DFHACK_PLUGIN(autolabor autolabor.cpp)
DFHACK_PLUGIN(automaterial automaterial.cpp)
@ -108,19 +109,17 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(cleanowned cleanowned.cpp)
DFHACK_PLUGIN(colonies colonies.cpp)
DFHACK_PLUGIN(command-prompt command-prompt.cpp)
DFHACK_PLUGIN(confirm confirm.cpp)
DFHACK_PLUGIN(confirm confirm.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(createitem createitem.cpp)
DFHACK_PLUGIN(cursecheck cursecheck.cpp)
DFHACK_PLUGIN(deramp deramp.cpp)
DFHACK_PLUGIN(dig dig.cpp)
DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(drybuckets drybuckets.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)
DFHACK_PLUGIN(feature feature.cpp)
DFHACK_PLUGIN(filltraffic filltraffic.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp)
DFHACK_PLUGIN(fixpositions fixpositions.cpp)
@ -169,7 +168,6 @@ if (BUILD_SUPPORTED)
# DFHACK_PLUGIN(treefarm treefarm.cpp)
DFHACK_PLUGIN(tubefill tubefill.cpp)
add_subdirectory(tweak)
DFHACK_PLUGIN(weather weather.cpp)
DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(workNow workNow.cpp)
DFHACK_PLUGIN(zone zone.cpp LINK_LIBRARIES lua)
@ -180,3 +178,5 @@ OPTION(BUILD_SKELETON "Build the skeleton plugin." OFF)
if(BUILD_SKELETON)
add_subdirectory(skeleton)
endif()
INCLUDE(CMakeLists.custom.txt)

@ -243,7 +243,9 @@ struct product_hook : improvement_product {
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
(df::unit *unit,
std::vector<df::reaction_product*> *out_products,
std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *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);
}
};

@ -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 <string> & parameters)
return CR_WRONG_USAGE;
}
if (Maps::IsValid())
Screen::show(new ViewscreenAutochop());
Screen::show(new ViewscreenAutochop(), plugin_self);
return CR_OK;
}

@ -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<mat_index, int> 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<df::general_ref_building_holderst>();
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<item_id> stockpiled;
std::set<df::building_workshopst*> 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<df::building_workshopst>(*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<df::building_stockpilest>(*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<df::building_workshopst>(*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<df::interface_key> *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<df::interface_key> *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 <PluginCommand> &commands) {
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return plugin_enable(out, false);
}

@ -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 <std::string> & 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)
{

@ -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 <std::string> & 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)
{

@ -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 <algorithm>
#include <Console.h>
#include <PluginManager.h>

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

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

@ -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<std::pair<color_value,std::string> > 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 <std::string> & param
std::string params;
for(size_t i=0;i<parameters.size();i++)
params+=parameters[i]+" ";
Screen::show(new viewscreen_commandpromptst(params));
Screen::show(new viewscreen_commandpromptst(params), plugin_self);
return CR_OK;
}
bool hotkey_allow_all(df::viewscreen *top)
@ -332,8 +336,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event e)
{
if (e == SC_BEGIN_UNLOAD && Gui::getCurFocus() == "dfhack/commandprompt")
return CR_FAILURE;
return CR_OK;
}

@ -1,19 +1,25 @@
#include <set>
#include <map>
#include <set>
#include <queue>
#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<df::interface_key> ikey_set;
command_result df_confirm (color_ostream &out, vector <string> & parameters);
struct conf_wrapper;
static std::map<std::string, conf_wrapper*> confirmations;
static std::map<string, conf_wrapper*> confirmations;
string active_id;
std::queue<string> cmds;
template <typename VT, typename FT>
bool in_vector (std::vector<VT> &vec, FT item)
inline bool in_vector (std::vector<VT> &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<df::general_ref*> *refs = &screen->list##_items[i]->general_refs; \
bool in_container = false; \
for (auto it = refs->begin(); it != refs->end(); ++it) \
{ \
if (virtual_cast<df::general_ref_contained_in_itemst>(*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<VMethodInterposeLinkBase*> 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<char> &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<char> &selected, const std::vector<df::item*> &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<df::general_ref*> &refs = items[i]->general_refs;
bool in_container = false;
for (auto it = refs.begin(); it != refs.end(); ++it)
{
if (virtual_cast<df::general_ref_contained_in_itemst>(*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 <typename T>
void push (T val)
{
Lua::Push(l_state, val);
}
template <typename KeyType, typename ValueType>
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 T>
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; }
protected:
cstate state;
df::interface_key last_key;
};
struct conf_wrapper {
bool enabled;
std::set<VMethodInterposeLinkBase*> hooks;
conf_wrapper()
:enabled(false)
{}
void add_hook(VMethodInterposeLinkBase *hook)
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)
{
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))
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";
}
enabled = state;
return true;
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 "<Message generation failed>";
};
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;
};
#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0)
#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \
template<typename T>
int conf_register(confirmation<T> *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, 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);
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 trade_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
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<df::viewscreen_tradegoodsst> {
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<df::viewscreen_tradegoodsst> {
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<df::viewscreen_tradegoodsst> {
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);
#define DEFINE_CONFIRMATION(cls, screen, prio) \
class confirmation_##cls : public confirmation<df::screen> { \
virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \
}; \
IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, prio);
class trade_select_all_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
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<df::viewscreen_dwarfmodest> {
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);
class depot_remove_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
public:
virtual bool intercept_key (df::interface_key key)
{
df::building_tradedepotst *depot = virtual_cast<df::building_tradedepotst>(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);
class squad_disband_confirmation : public confirmation<df::viewscreen_layer_militaryst> {
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<df::viewscreen_dwarfmodest> {
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<df::viewscreen_dwarfmodest> {
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 <PluginCommand> &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 <string> & 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,10 +548,8 @@ command_result df_confirm (color_ostream &out, vector <string> & parameters)
else if (*it == "all")
{
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
it->second->apply(state);
}
}
else
enable_conf(out, *it, state);
}

@ -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<df::reaction_product*> out_products;
vector<df::item *> out_items;
vector<df::reaction_reagent *> in_reag;
vector<df::item *> 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())

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

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

@ -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<std::string> &params)
{
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 <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"color-dfhack-text",
"color <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;
}

@ -252,10 +252,11 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map<df:
prod->count = 1;
prod->product_dimension = 1;
vector<df::reaction_product*> out_products;
vector<df::item*> out_items;
vector<df::reaction_reagent*> in_reag;
vector<df::item*> 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));

@ -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 <string> & 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 <PluginCommand> &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;
}

@ -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 <string> & 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')
{

@ -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 <std::string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &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;
}

@ -309,7 +309,9 @@ struct product_hook : item_product {
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
(df::unit *unit,
std::vector<df::reaction_product*> *out_products,
std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *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

@ -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 <stdlib.h>
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 <string> &parameters)
{
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 <PluginCommand> &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 <ID>\n"
" Marks the specified map feature as discovered.\n"
" feature hide <ID>\n"
" Marks the specified map feature as undiscovered.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
return CR_OK;
}

@ -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)
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)
{
++count;
block->occupancy[x][y].bits.unit = fixed_occupancy;
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;
}

@ -319,7 +319,7 @@ static command_result hotkeys_cmd(color_ostream &out, vector <string> & 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);
}
}
}

@ -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<T> results = getSelectedElems(true);
if (results.size() == 0)
return (T)nullptr;
return default_value;
else
return results[0];
}

@ -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, '<No title>') end
cls.get_message = function() return if_nil(cls.message, '<No 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

@ -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.
@ -399,6 +418,14 @@ function collect_reactions()
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
for rock_id = 0, #rock_types - 1 do
@ -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),
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,
})
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
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,
})

@ -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<df::interface_key> *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<df::interface_key> *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<UnitInfo*> 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<df::interface_key> *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<UnitInfo*> 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<df::interface_key> *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;
}
}

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

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

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

@ -724,7 +724,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
for(int i=0;i<block->flows.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);

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

@ -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<S,T> >
class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic<S, T, PARENT>
@ -750,7 +752,7 @@ template <class T, class V, int D> V generic_search_hook<T, V, D> ::module;
typedef generic_search_hook<screen, module> 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 <class T, class V, int D> V generic_search_hook<T, V, D> ::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<df::viewscreen_petst, T_animal>
typedef search_multicolumn_modifiable_generic<df::viewscreen_petst, df::viewscreen_petst::T_animal> 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);
}
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<char > *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<df::viewscreen_petst, int32_t> 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<int32_t> *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<df::viewscreen_petst, df::unit*, df::viewscreen_petst::T_trainer_mode> 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<df::unit*> *get_primary_list()
{
return &viewscreen->trainer_unit;
}
string get_element_description(df::unit *u) const
{
return get_unit_description(u);
}
std::vector<T_trainer_mode> *get_secondary_list()
{
return &viewscreen->trainer_mode;
}
public:
bool process_input(set<df::interface_key> *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<df::viewscreen_locationsst, df::unit*> 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<df::unit*> *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)
{

@ -69,7 +69,7 @@ command_result df_showmood (color_ostream &out, vector <string> & 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:

@ -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 <string> & 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;
}

@ -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 <string> & 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 <string> & 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 <string> & 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 <string> & 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 <string> & 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 <string> & 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;
}
}

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

@ -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 <Plugi
" Fixes custom reactions with container inputs in advmode. The issue is\n"
" that the screen tries to force you to select the contents separately\n"
" from the container. This forcefully skips child reagents.\n"
" tweak block-labors [disable]\n"
" Prevents labors that can't be used from being toggled.\n"
" tweak civ-view-agreement\n"
" Fixes overlapping text on the \"view agreement\" screen\n"
" tweak craft-age-wear [disable]\n"
@ -193,6 +201,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" the current item (fully, in case of a stack), and scroll down one line.\n"
" tweak fps-min [disable]\n"
" Fixes the in-game minimum FPS setting (bug 6277)\n"
" tweak hide-priority [disable]\n"
" Adds an option to hide designation priority indicators\n"
" tweak import-priority-category [disable]\n"
" When meeting with a liaison, makes Shift+Left/Right arrow adjust\n"
" the priority of an entire category of imports.\n"
@ -218,6 +228,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" tweak shift-8-scroll [disable]\n"
" Gives Shift+8 (or *) priority when scrolling menus, instead of \n"
" scrolling the map\n"
" tweak title-start-rename [disable]\n"
" Adds a safe rename option to the title screen \"Start Playing\" menu\n"
" tweak tradereq-pet-gender [disable]\n"
" Displays the gender of pets in the trade request list\n"
// " tweak military-training [disable]\n"
@ -232,6 +244,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("advmode-contained", advmode_contained_hook, feed);
TWEAK_HOOK("block-labors", block_labors_hook, feed);
TWEAK_HOOK("block-labors", block_labors_hook, render);
TWEAK_HOOK("civ-view-agreement", civ_agreement_view_hook, render);
TWEAK_HOOK("craft-age-wear", craft_age_wear_hook, ageItem);
@ -252,6 +267,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_ONUPDATE_HOOK("fps-min", fps_min_hook);
TWEAK_HOOK("hide-priority", hide_priority_hook, feed);
TWEAK_HOOK("hide-priority", hide_priority_hook, render);
TWEAK_HOOK("import-priority-category", takerequest_hook, feed);
TWEAK_HOOK("import-priority-category", takerequest_hook, render);
@ -277,6 +295,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("stable-cursor", stable_cursor_hook, feed);
TWEAK_HOOK("title-start-rename", title_start_rename_hook, feed);
TWEAK_HOOK("title-start-rename", title_start_rename_hook, render);
TWEAK_HOOK("tradereq-pet-gender", pet_gender_hook, render);
return CR_OK;

@ -0,0 +1,104 @@
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Units.h"
#include "df/ui_unit_view_mode.h"
#include "df/unit_labor.h"
#include "df/viewscreen_dwarfmodest.h"
using namespace DFHack;
using df::global::ui;
using df::global::ui_look_cursor;
using df::global::ui_unit_view_mode;
struct block_labors_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
inline bool valid_mode()
{
return ui->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<df::interface_key> *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);

@ -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<df::interface_key> *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);

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

@ -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 &region_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<df::interface_key> *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<df::interface_key> 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;

@ -91,9 +91,9 @@ static void transform_(vector<T> &src, vector<V> &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)

@ -1,147 +0,0 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include <vector>
#include <string>
#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 <string> & parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &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 <string> & 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;
}

@ -59,10 +59,13 @@ REQUIRE_GLOBAL(job_next_id);
/* Plugin registration */
static command_result workflow_cmd(color_ostream &out, vector <string> & parameters);
static command_result fix_job_postings_cmd(color_ostream &out, vector<string> &parameters);
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 <PluginCommand> &commands)
{
if (!world || !ui)
@ -142,6 +145,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Maintain 10-100 locally-made crafts of exceptional quality.\n"
)
);
commands.push_back(PluginCommand(
"fix-job-postings",
"Fix broken job postings caused by certain versions of workflow",
fix_job_postings_cmd, false,
"fix-job-postings: Fix job postings\n"
"fix-job-postings dry|[any argument]: Dry run only (avoid making changes)\n"
));
}
init_state(out);
@ -173,6 +183,14 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
}
command_result fix_job_postings_cmd(color_ostream &out, vector<string> &parameters)
{
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
};

@ -1 +1 @@
Subproject commit 0b8303f6b03d574e3a0b3fd8b17b7ff0014af47f
Subproject commit 26c60013223e02b5558e31bed8e0625495430995

@ -14,7 +14,6 @@ local artisan_labors = {
"ARCHITECT",
"ANIMALTRAIN",
"LEATHER",
"BREWER",
"WEAVER",
"CLOTHESMAKER",
"COOK",

@ -5,7 +5,8 @@ ban-cooking
===========
A more convenient way to ban cooking various categories of foods than the
kitchen interface. Usage: ``ban-cooking <type>``. 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

@ -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=<<fillme>>\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=<<fillme>>\n')
end
out:write('version_name='..dfhack.getDFVersion()..'\n')
out:write('complete='..(complete and 'true' or 'false')..'\n')

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

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

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

@ -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()
if enabled then
checkmigrationnow()
dfhack.timeout(1, 'months', event_loop)
end
end
dfhack.onStateChange.loadEmigration = function(code)

@ -13,6 +13,7 @@ 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
: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
@ -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 ("<?xml version=\"1.0\" encoding='UTF-8'?>".."\n")
io.write ("<df_world>".."\n")
io.write ("<name>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."</name>".."\n")
io.write ("<altname>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."</altname>".."\n")
io.write ("<regions>".."\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("<?xml version=\"1.0\" encoding='UTF-8'?>\n")
file:write("<df_world>\n")
file:write("<name>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."</name>\n")
file:write("<altname>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."</altname>\n")
file:write("<landmasses>\n")
for landmassK, landmassV in ipairs(df.global.world.world_data.landmasses) do
file:write("\t<landmass>\n")
file:write("\t\t<id>"..landmassV.index.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(landmassV.name,1)).."</name>\n")
file:write("\t\t<coord_1>"..landmassV.min_x..","..landmassV.min_y.."</coord_1>\n")
file:write("\t\t<coord_2>"..landmassV.max_x..","..landmassV.max_y.."</coord_2>\n")
file:write("\t</landmass>\n")
end
file:write("</landmasses>\n")
file:write("<mountain_peaks>\n")
for mountainK, mountainV in ipairs(df.global.world.world_data.mountain_peaks) do
file:write("\t<mountain_peak>\n")
file:write("\t\t<id>"..mountainK.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(mountainV.name,1)).."</name>\n")
file:write("\t\t<coords>"..mountainV.pos.x..","..mountainV.pos.y.."</coords>\n")
file:write("\t\t<height>"..mountainV.height.."</height>\n")
file:write("\t</mountain_peak>\n")
end
file:write("</mountain_peaks>\n")
file:write("<regions>\n")
for regionK, regionV in ipairs(df.global.world.world_data.regions) do
io.write ("\t".."<region>".."\n")
io.write ("\t\t".."<id>"..regionV.index.."</id>".."\n")
io.write ("\t\t".."<coords>")
file:write("\t<region>\n")
file:write("\t\t<id>"..regionV.index.."</id>\n")
file:write("\t\t<coords>")
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 ("</coords>\n")
io.write ("\t".."</region>".."\n")
file:write("</coords>\n")
file:write("\t</region>\n")
end
io.write ("</regions>".."\n")
file:write("</regions>\n")
io.write ("<underground_regions>".."\n")
file:write("<underground_regions>\n")
for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do
io.write ("\t".."<underground_region>".."\n")
io.write ("\t\t".."<id>"..regionV.index.."</id>".."\n")
io.write ("\t\t".."<coords>")
file:write("\t<underground_region>\n")
file:write("\t\t<id>"..regionV.index.."</id>\n")
file:write("\t\t<coords>")
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 ("</coords>\n")
io.write ("\t".."</underground_region>".."\n")
file:write("</coords>\n")
file:write("\t</underground_region>\n")
end
io.write ("</underground_regions>".."\n")
file:write("</underground_regions>\n")
io.write ("<sites>".."\n")
file:write("<sites>\n")
for siteK, siteV in ipairs(df.global.world.world_data.sites) do
if (#siteV.buildings > 0) then
io.write ("\t".."<site>".."\n")
file:write("\t<site>\n")
for k,v in pairs(siteV) do
if (k == "id") then
io.write ("\t\t".."<"..k..">"..tostring(v).."</"..k..">".."\n")
if (k == "id" or k == "civ_id" or k == "cur_owner_id") then
file:write("\t\t<"..k..">"..tostring(v).."</"..k..">\n")
elseif (k == "buildings") then
io.write ("\t\t".."<structures>".."\n")
if (#siteV.buildings > 0) then
file:write("\t\t<structures>\n")
for buildingK, buildingV in ipairs(siteV.buildings) do
io.write ("\t\t\t".."<structure>".."\n")
io.write ("\t\t\t\t".."<id>"..buildingV.id.."</id>".."\n")
io.write ("\t\t\t\t".."<type>"..df.abstract_building_type[buildingV:getType()]:lower().."</type>".."\n")
file:write("\t\t\t<structure>\n")
file:write("\t\t\t\t<id>"..buildingV.id.."</id>\n")
file:write("\t\t\t\t<type>"..df.abstract_building_type[buildingV:getType()]:lower().."</type>\n")
if (df.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire") then
io.write ("\t\t\t\t".."<name>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."</name>".."\n")
io.write ("\t\t\t\t".."<name2>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."</name2>".."\n")
-- if spire: unk_50 should be name and unk_bc some kind of flag
file:write("\t\t\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."</name>\n")
file:write("\t\t\t\t<name2>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."</name2>\n")
end
if (buildingV:getType() == df.abstract_building_type.TEMPLE) then
file:write("\t\t\t\t<deity>"..buildingV.deity.."</deity>\n")
file:write("\t\t\t\t<religion>"..buildingV.religion.."</religion>\n")
end
if (buildingV:getType() == df.abstract_building_type.DUNGEON) then
file:write("\t\t\t\t<dungeon_type>"..buildingV.dungeon_type.."</dungeon_type>\n")
end
io.write ("\t\t\t".."</structure>".."\n")
for inhabitabntK,inhabitabntV in pairs(buildingV.inhabitants) do
file:write("\t\t\t\t<inhabitant>"..inhabitabntV.anon_2.."</inhabitant>\n")
end
io.write ("\t\t".."</structures>".."\n")
file:write("\t\t\t</structure>\n")
end
file:write("\t\t</structures>\n")
end
io.write ("\t".."</site>".."\n")
end
end
io.write ("</sites>".."\n")
file:write("\t</site>\n")
end
file:write("</sites>\n")
io.write ("<world_constructions>".."\n")
file:write("<world_constructions>\n")
for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do
io.write ("\t".."<world_construction>".."\n")
io.write ("\t\t".."<id>"..wcV.id.."</id>".."\n")
io.write ("\t\t".."<name>"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."</name>".."\n")
io.write ("\t\t".."<type>"..(df.world_construction_type[wcV:getType()]):lower().."</type>".."\n")
io.write ("\t\t".."<coords>")
file:write("\t<world_construction>\n")
file:write("\t\t<id>"..wcV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."</name>\n")
file:write("\t\t<type>"..(df.world_construction_type[wcV:getType()]):lower().."</type>\n")
file:write("\t\t<coords>")
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 ("</coords>\n")
io.write ("\t".."</world_construction>".."\n")
file:write("</coords>\n")
file:write("\t</world_construction>\n")
end
io.write ("</world_constructions>".."\n")
file:write("</world_constructions>\n")
io.write ("<artifacts>".."\n")
file:write("<artifacts>\n")
for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do
io.write ("\t".."<artifact>".."\n")
io.write ("\t\t".."<id>"..artifactV.id.."</id>".."\n")
file:write("\t<artifact>\n")
file:write("\t\t<id>"..artifactV.id.."</id>\n")
if (artifactV.item:getType() ~= -1) then
io.write ("\t\t".."<item_type>"..tostring(df.item_type[artifactV.item:getType()]):lower().."</item_type>".."\n")
file:write("\t\t<item_type>"..tostring(df.item_type[artifactV.item:getType()]):lower().."</item_type>\n")
if (artifactV.item:getSubtype() ~= -1) then
io.write ("\t\t".."<item_subtype>"..artifactV.item.subtype.name.."</item_subtype>".."\n")
file:write("\t\t<item_subtype>"..artifactV.item.subtype.name.."</item_subtype>\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<writing>"..writingV.."</writing>\n")
end
elseif impovementV:getType() == df.improvement_type.PAGES then
file:write("\t\t<page_count>"..impovementV.count.."</page_count>\n")
for writingk,writingV in pairs(impovementV.contents) do
file:write("\t\t<writing>"..writingV.."</writing>\n")
end
end
end
end
if (table.containskey(artifactV.item,"description")) then
io.write ("\t\t".."<item_description>"..artifactV.item.description:lower().."</item_description>".."\n")
file:write("\t\t<item_description>"..dfhack.df2utf(artifactV.item.description:lower()).."</item_description>\n")
end
if (artifactV.item:getMaterial() ~= -1 and artifactV.item:getMaterialIndex() ~= -1) then
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."</mat>\n")
end
io.write ("\t".."</artifact>".."\n")
file:write("\t</artifact>\n")
end
io.write ("</artifacts>".."\n")
file:write("</artifacts>\n")
io.write ("<historical_figures>".."\n".."</historical_figures>".."\n")
file:write("<historical_figures>\n")
for hfK, hfV in ipairs(df.global.world.history.figures) do
file:write("\t<historical_figure>\n")
file:write("\t\t<id>"..hfV.id.."</id>\n")
file:write("\t\t<sex>"..hfV.sex.."</sex>\n")
if hfV.race >= 0 then file:write("\t\t<race>"..df.global.world.raws.creatures.all[hfV.race].name[0].."</race>\n") end
file:write("\t</historical_figure>\n")
end
file:write("</historical_figures>\n")
io.write ("<entity_populations>".."\n")
file:write("<entity_populations>\n")
for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do
io.write ("\t".."<entity_population>".."\n")
io.write ("\t\t".."<id>"..entityPopV.id.."</id>".."\n")
file:write("\t<entity_population>\n")
file:write("\t\t<id>"..entityPopV.id.."</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".."<race>"..raceName..":"..entityPopV.counts[raceK].."</race>".."\n")
file:write("\t\t<race>"..raceName..":"..entityPopV.counts[raceK].."</race>\n")
end
io.write ("\t\t".."<civ_id>"..entityPopV.civ_id.."</civ_id>".."\n")
io.write ("\t".."</entity_population>".."\n")
file:write("\t\t<civ_id>"..entityPopV.civ_id.."</civ_id>\n")
file:write("\t</entity_population>\n")
end
io.write ("</entity_populations>".."\n")
file:write("</entity_populations>\n")
io.write ("<entities>".."\n")
file:write("<entities>\n")
for entityK, entityV in ipairs(df.global.world.entities.all) do
io.write ("\t".."<entity>".."\n")
io.write ("\t\t".."<id>"..entityV.id.."</id>".."\n")
io.write ("\t\t".."<race>"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."</race>".."\n")
io.write ("\t\t".."<type>"..(df.historical_entity_type[entityV.type]):lower().."</type>".."\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".."<worship_id>"..entityV.unknown1b.worship[0].."</worship_id>".."\n")
else
print(entityV.unknown1b, entityV.unknown1b.worship, #entityV.unknown1b.worship)
file:write("\t<entity>\n")
file:write("\t\t<id>"..entityV.id.."</id>\n")
if entityV.race >= 0 then
file:write("\t\t<race>"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."</race>\n")
end
file:write("\t\t<type>"..(df.historical_entity_type[entityV.type]):lower().."</type>\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<worship_id>"..v.."</worship_id>\n")
end
end
end
for id, link in pairs(entityV.entity_links) do
io.write ("\t\t".."<entity_link>".."\n")
file:write("\t\t<entity_link>\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]).."</"..k..">".."\n")
file:write("\t\t\t<"..k..">"..tostring(df.entity_entity_link_type[v]).."</"..k..">\n")
else
io.write ("\t\t\t".."<"..k..">"..v.."</"..k..">".."\n")
file:write("\t\t\t<"..k..">"..v.."</"..k..">\n")
end
end
file:write("\t\t</entity_link>\n")
end
for positionK,positionV in pairs(entityV.positions.own) do
file:write("\t\t<entity_position>\n")
file:write("\t\t\t<id>"..positionV.id.."</id>\n")
if positionV.name[0] ~= "" then file:write("\t\t\t<name>"..positionV.name[0].."</name>\n") end
if positionV.name_male[0] ~= "" then file:write("\t\t\t<name_male>"..positionV.name_male[0].."</name_male>\n") end
if positionV.name_female[0] ~= "" then file:write("\t\t\t<name_female>"..positionV.name_female[0].."</name_female>\n") end
if positionV.spouse[0] ~= "" then file:write("\t\t\t<spouse>"..positionV.spouse[0].."</spouse>\n") end
if positionV.spouse_male[0] ~= "" then file:write("\t\t\t<spouse_male>"..positionV.spouse_male[0].."</spouse_male>\n") end
if positionV.spouse_female[0] ~= "" then file:write("\t\t\t<spouse_female>"..positionV.spouse_female[0].."</spouse_female>\n") end
file:write("\t\t</entity_position>\n")
end
for assignmentK,assignmentV in pairs(entityV.positions.assignments) do
file:write("\t\t<entity_position_assignment>\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.."</"..k..">\n")
end
io.write ("\t\t".."</entity_link>".."\n")
end
file:write("\t\t</entity_position_assignment>\n")
end
for idx,id in pairs(entityV.histfig_ids) do
file:write("\t\t<histfig_id>"..id.."</histfig_id>\n")
end
for id, link in ipairs(entityV.children) do
io.write ("\t\t".."<child>"..link.."</child>".."\n")
file:write("\t\t<child>"..link.."</child>\n")
end
file:write("\t\t<claims>")
for xK, xVal in ipairs(entityV.claims.unk2.x) do
file:write(xVal..","..entityV.claims.unk2.y[xK].."|")
end
file:write("</claims>\n")
file:write("\t</entity>\n")
end
file:write("</entities>\n")
file:write("<poetic_forms>\n")
for formK, formV in ipairs(df.global.world.poetic_forms.all) do
file:write("\t<poetic_form>\n")
file:write("\t\t<id>"..formV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."</name>\n")
file:write("\t</poetic_form>\n")
end
file:write("</poetic_forms>\n")
file:write("<musical_forms>\n")
for formK, formV in ipairs(df.global.world.musical_forms.all) do
file:write("\t<musical_form>\n")
file:write("\t\t<id>"..formV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."</name>\n")
file:write("\t</musical_form>\n")
end
file:write("</musical_forms>\n")
file:write("<dance_forms>\n")
for formK, formV in ipairs(df.global.world.dance_forms.all) do
file:write("\t<dance_form>\n")
file:write("\t\t<id>"..formV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."</name>\n")
file:write("\t</dance_form>\n")
end
file:write("</dance_forms>\n")
file:write("<written_contents>\n")
for wcK, wcV in ipairs(df.global.world.written_contents.all) do
file:write("\t<written_content>\n")
file:write("\t\t<id>"..wcV.id.."</id>\n")
file:write("\t\t<title>"..wcV.title.."</title>\n")
file:write("\t\t<page_start>"..wcV.page_start.."</page_start>\n")
file:write("\t\t<page_end>"..wcV.page_end.."</page_end>\n")
for refK, refV in pairs(wcV.refs) do
file:write("\t\t<reference>\n")
file:write("\t\t\t<type>"..df.general_ref_type[refV:getType()].."</type>\n")
if refV:getType() == df.general_ref_type.ARTIFACT then file:write("\t\t\t<id>"..refV.artifact_id.."</id>\n") -- artifact
elseif refV:getType() == df.general_ref_type.ENTITY then file:write("\t\t\t<id>"..refV.entity_id.."</id>\n") -- entity
elseif refV:getType() == df.general_ref_type.HISTORICAL_EVENT then file:write("\t\t\t<id>"..refV.event_id.."</id>\n") -- event
elseif refV:getType() == df.general_ref_type.SITE then file:write("\t\t\t<id>"..refV.site_id.."</id>\n") -- site
elseif refV:getType() == df.general_ref_type.SUBREGION then file:write("\t\t\t<id>"..refV.region_id.."</id>\n") -- region
elseif refV:getType() == df.general_ref_type.HISTORICAL_FIGURE then file:write("\t\t\t<id>"..refV.hist_figure_id.."</id>\n") -- hist figure
elseif refV:getType() == df.general_ref_type.WRITTEN_CONTENT then file:write("\t\t\t<id>"..refV.anon_1.."</id>\n")
elseif refV:getType() == df.general_ref_type.POETIC_FORM then file:write("\t\t\t<id>"..refV.poetic_form_id.."</id>\n") -- poetic form
elseif refV:getType() == df.general_ref_type.MUSICAL_FORM then file:write("\t\t\t<id>"..refV.musical_form_id.."</id>\n") -- musical form
elseif refV:getType() == df.general_ref_type.DANCE_FORM then file:write("\t\t\t<id>"..refV.dance_form_id.."</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
io.write ("\t".."</entity>".."\n")
file:write("\t\t</reference>\n")
end
io.write ("</entities>".."\n")
file:write("\t\t<type>"..(df.written_content_type[wcV.type] or wcV.type).."</type>\n")
for styleK, styleV in pairs(wcV.styles) do
file:write("\t\t<style>"..(df.written_content_style[styleV] or styleV).."</style>\n")
end
file:write("\t\t<author>"..wcV.author.."</author>\n")
file:write("\t</written_content>\n")
end
file:write("</written_contents>\n")
io.write ("<historical_events>".."\n")
file:write("<historical_events>\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".."<historical_event>".."\n")
io.write ("\t\t".."<id>"..event.id.."</id>".."\n")
io.write ("\t\t".."<type>"..tostring(df.history_event_type[event:getType()]):lower().."</type>".."\n")
file:write("\t<historical_event>\n")
file:write("\t\t<id>"..event.id.."</id>\n")
file:write("\t\t<type>"..tostring(df.history_event_type[event:getType()]):lower().."</type>\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().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.histfig_entity_link_type[v]:lower().."</"..k..">\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".."<position>"..tostring(entityPositionsV.name[0]):lower().."</position>".."\n")
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
io.write ("\t\t".."<position>-1</position>".."\n")
file:write("\t\t<position>-1</position>\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".."<position>"..tostring(entityPositionsV.name[0]):lower().."</position>".."\n")
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
io.write ("\t\t".."<position>-1</position>".."\n")
file:write("\t\t<position>-1</position>\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().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.histfig_entity_link_type[v]:lower().."</"..k..">\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".."<position>"..tostring(entityPositionsV.name[0]):lower().."</position>".."\n")
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
io.write ("\t\t".."<position>-1</position>".."\n")
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then
io.write ("\t\t".."<link_type>"..df.histfig_hf_link_type[v]:lower().."</link_type>".."\n")
file:write("\t\t<link_type>"..df.histfig_hf_link_type[v]:lower().."</link_type>\n")
elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then
io.write ("\t\t".."<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>".."\n")
file:write("\t\t<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>\n")
elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then
io.write ("\t\t".."<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>".."\n")
file:write("\t\t<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>\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".."<item_type>"..df.item_type[v]:lower().."</item_type>".."\n")
file:write("\t\t<item_type>"..df.item_type[v]:lower().."</item_type>\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).."</"..k..">".."\n")
file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,v).."</"..k..">\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".."<mattype>"..event.mattype.."</mattype>".."\n")
io.write ("\t\t".."<matindex>"..event.matindex.."</matindex>".."\n")
file:write("\t\t<mattype>"..event.mattype.."</mattype>\n")
file:write("\t\t<matindex>"..event.matindex.."</matindex>\n")
else
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."</mat>\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".."<mat_type>"..event.mat_type.."</mat_type>".."\n")
io.write ("\t\t".."<mat_index>"..event.mat_index.."</mat_index>".."\n")
file:write("\t\t<mat_type>"..event.mat_type.."</mat_type>\n")
file:write("\t\t<mat_index>"..event.mat_index.."</mat_index>\n")
else
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."</mat>\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".."<imp_mat_type>"..event.imp_mat_type.."</imp_mat_type>".."\n")
io.write ("\t\t".."<imp_mat_index>"..event.imp_mat_index.."</imp_mat_index>".."\n")
file:write("\t\t<imp_mat_type>"..event.imp_mat_type.."</imp_mat_type>\n")
file:write("\t\t<imp_mat_index>"..event.imp_mat_index.."</imp_mat_index>\n")
else
io.write ("\t\t".."<imp_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."</imp_mat>".."\n")
file:write("\t\t<imp_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."</imp_mat>\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".."<topic>"..tostring(df.meeting_topic[v]):lower().."</topic>".."\n")
file:write("\t\t<topic>"..tostring(df.meeting_topic[v]):lower().."</topic>\n")
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then
io.write ("\t\t".."<improvement_type>"..df.improvement_type[v]:lower().."</improvement_type>".."\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<improvement_type>"..df.improvement_type[v]:lower().."</improvement_type>\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.."</"..k..">".."\n")
file:write("\t\t<"..k..">"..detailV.."</"..k..">\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().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[detailV].name[0].."</"..k..">\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().."</"..k.."_item_type"..">".."\n")
io.write ("\t\t".."<"..k.."_item_subtype"..">"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."</"..k.."_item_subtype"..">".."\n")
file:write("\t\t<"..k.."_item_type>"..tostring(df.item_type[event.props.item.item_type]):lower().."</"..k.."_item_type>\n")
file:write("\t\t<"..k.."_item_subtype>"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."</"..k.."_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".."<props_item_mat_type>"..event.props.item.mat_type.."</props_item_mat_type>".."\n")
io.write ("\t\t".."<props_item_mat_index>"..event.props.item.mat_index.."</props_item_mat_index>".."\n")
file:write("\t\t<props_item_mat_type>"..event.props.item.mat_type.."</props_item_mat_type>\n")
file:write("\t\t<props_item_mat_index>"..event.props.item.mat_index.."</props_item_mat_index>\n")
else
io.write ("\t\t".."<props_item_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."</props_item_mat>".."\n")
file:write("\t\t<props_item_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."</props_item_mat>\n")
end
end
--io.write ("\t\t".."<"..k.."_item_mat_type"..">"..tostring(event.props.item.mat_type).."</"..k.."_item_mat_index"..">".."\n")
--io.write ("\t\t".."<"..k.."_item_mat_index"..">"..tostring(event.props.item.mat_index).."</"..k.."_item_mat_index"..">".."\n")
io.write ("\t\t".."<"..k.."_pile_type"..">"..tostring(event.props.pile_type).."</"..k.."_pile_type"..">".."\n")
--file:write("\t\t<"..k.."_item_mat_type>"..tostring(event.props.item.mat_type).."</"..k.."_item_mat_index>\n")
--file:write("\t\t<"..k.."_item_mat_index>"..tostring(event.props.item.mat_index).."</"..k.."_item_mat_index>\n")
file:write("\t\t<"..k.."_pile_type>"..tostring(event.props.pile_type).."</"..k.."_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".."<identity_name>"..thisIdentity.name.first_name.."</identity_name>".."\n")
io.write ("\t\t".."<identity_race>"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."</identity_race>".."\n")
io.write ("\t\t".."<identity_caste>"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."</identity_caste>".."\n")
file:write("\t\t<identity_name>"..thisIdentity.name.first_name.."</identity_name>\n")
file:write("\t\t<identity_race>"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."</identity_race>\n")
file:write("\t\t<identity_caste>"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."</identity_caste>\n")
else
io.write ("\t\t".."<identity_hf>"..df.global.world.identities.all[v].histfig_id.."</identity_hf>".."\n")
file:write("\t\t<identity_hf>"..df.global.world.identities.all[v].histfig_id.."</identity_hf>\n")
end
end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_type" then
io.write ("\t\t".."<building_type>"..df.building_type[v]:lower().."</building_type>".."\n")
file:write("\t\t<building_type>"..df.building_type[v]:lower().."</building_type>\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".."<building_subtype>"..df.furnace_type[v]:lower().."</building_subtype>".."\n")
file:write("\t\t<building_subtype>"..df.furnace_type[v]:lower().."</building_subtype>\n")
elseif v > -1 then
io.write ("\t\t".."<building_subtype>"..tostring(v).."</building_subtype>".."\n")
file:write("\t\t<building_subtype>"..tostring(v).."</building_subtype>\n")
end
elseif k == "race" then
if v > -1 then
io.write ("\t\t".."<race>"..(df.global.world.raws.creatures.all[v].creature_id):lower().."</race>".."\n")
file:write("\t\t<race>"..df.global.world.raws.creatures.all[v].name[0].."</race>\n")
end
elseif k == "caste" then
if v > -1 then
io.write ("\t\t".."<caste>"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."</caste>".."\n")
file:write("\t\t<caste>"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."</caste>\n")
end
elseif k == "interaction" and event:getType() == df.history_event_type.HF_DOES_INTERACTION then
io.write ("\t\t".."<interaction_action>"..df.global.world.raws.interactions[v].str[3].value.."</interaction_action>".."\n")
io.write ("\t\t".."<interaction_string>"..df.global.world.raws.interactions[v].str[4].value.."</interaction_string>".."\n")
file:write("\t\t<interaction_action>"..df.global.world.raws.interactions[v].str[3].value.."</interaction_action>\n")
file:write("\t\t<interaction_string>"..df.global.world.raws.interactions[v].str[4].value.."</interaction_string>\n")
elseif k == "interaction" and event:getType() == df.history_event_type.HF_LEARNS_SECRET then
io.write ("\t\t".."<secret_text>"..df.global.world.raws.interactions[v].str[2].value.."</secret_text>".."\n")
file:write("\t\t<secret_text>"..df.global.world.raws.interactions[v].str[2].value.."</secret_text>\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.."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\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".."<artifact_id>"..refv.artifact_id.."</artifact_id>".."\n")
if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then
file:write("\t\t<artifact_id>"..refv.artifact_id.."</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().."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."</"..detailK..">\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).."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."</"..detailK..">\n")
end
elseif (detailK == "mattype") then
if (detailV > -1) then
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."</mat>\n")
end
elseif (detailK == "matindex") then
elseif (detailK == "shooter_item") then
if detailV > -1 then
io.write ("\t\t".."<"..detailK..">"..detailV.."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\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".."<shooter_artifact_id>"..refv.artifact_id.."</shooter_artifact_id>".."\n")
if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then
file:write("\t\t<shooter_artifact_id>"..refv.artifact_id.."</shooter_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().."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."</"..detailK..">\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).."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."</"..detailK..">\n")
end
elseif (detailK == "shooter_mattype") then
if (detailV > -1) then
io.write ("\t\t".."<shooter_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."</shooter_mat>".."\n")
file:write("\t\t<shooter_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."</shooter_mat>\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.."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\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().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.death_type[v]:lower().."</"..k..">\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().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.profession[v]:lower().."</"..k..">\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].."</"..k..">\n")
else
io.write ("\t\t".."<"..k..">"..tostring(v).."</"..k..">".."\n")
file:write("\t\t<"..k..">"..tostring(v).."</"..k..">\n")
end
end
io.write ("\t".."</historical_event>".."\n")
file:write("\t</historical_event>\n")
end
end
io.write ("</historical_events>".."\n")
io.write ("<historical_event_collections>".."\n")
io.write ("</historical_event_collections>".."\n")
io.write ("<historical_eras>".."\n")
io.write ("</historical_eras>".."\n")
io.write ("</df_world>".."\n")
io.close()
file:write("</historical_events>\n")
file:write("<historical_event_collections>\n")
file:write("</historical_event_collections>\n")
file:write("<historical_eras>\n")
file:write("</historical_eras>\n")
file:write("</df_world>\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

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

@ -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.')

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