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

develop
Japa 2015-08-13 11:26:33 +05:30
commit e4ac25d1e0
59 changed files with 9228 additions and 934 deletions

3
.gitmodules vendored

@ -13,6 +13,3 @@
[submodule "depends/clsocket"] [submodule "depends/clsocket"]
path = depends/clsocket path = depends/clsocket
url = git://github.com/DFHack/clsocket.git url = git://github.com/DFHack/clsocket.git
[submodule "depends/jsonxx"]
path = depends/jsonxx
url = git://github.com/lethosor/jsonxx.git

@ -53,8 +53,7 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
endif() endif()
# make sure all the necessary submodules have been set up # 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 if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt)
OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/jsonxx/CMakeLists.txt)
message(FATAL_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(FATAL_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)")
endif() endif()
@ -136,7 +135,7 @@ find_package(ZLIB REQUIRED)
include_directories(depends/protobuf) include_directories(depends/protobuf)
include_directories(depends/lua/include) include_directories(depends/lua/include)
include_directories(depends/md5) include_directories(depends/md5)
include_directories(depends/jsonxx) include_directories(depends/jsoncpp)
include_directories(depends/tinyxml) include_directories(depends/tinyxml)
include_directories(depends/tthread) include_directories(depends/tthread)
include_directories(${ZLIB_INCLUDE_DIRS}) include_directories(${ZLIB_INCLUDE_DIRS})

@ -4,28 +4,43 @@ Building DFHACK
.. contents:: .. contents::
===================
=====
Linux
=====
On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD.
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. 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.
Having a 'git' package installed is the minimal requirement, but some sort of git gui or git integration for your favorite text editor/IDE will certainly help. The code resides here: https://github.com/DFHack/dfhack
On Linux and OS X, having a 'git' package installed is the minimal requirement (see below for OS X instructions),
but some sort of git gui or git integration for your favorite text editor/IDE will certainly help.
On Windows, you will need some sort of Windows port of git, or a GUI. Some examples:
* http://msysgit.github.io/ - this is a command line version of git for windows. Most tutorials on git usage will apply.
* http://code.google.com/p/tortoisegit/ - this puts a pretty, graphical face on top of msysgit
The code resides here: https://github.com/DFHack/dfhack The code resides here: https://github.com/DFHack/dfhack
If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:: To get the code::
git clone git://github.com/DFHack/dfhack.git git clone --recursive https://github.com/DFHack/dfhack
cd dfhack cd dfhack
git submodule init
git submodule update
If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode). 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.
If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address instead::
git clone --recursive git://github.com/DFHack/dfhack.git
cd dfhack
The tortoisegit GUI should have the equivalent options included.
If you want to get really involved with the development, create an account on Github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).
=====
Linux
=====
On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD.
Dependencies Dependencies
============ ============
@ -44,9 +59,11 @@ You should be able to find them in your distro repositories (on Arch linux 'perl
To build Stonesense, you'll also need OpenGL headers. To build Stonesense, you'll also need OpenGL headers.
Some additional dependencies for other distros are listed on the `wiki <https://github.com/DFHack/dfhack/wiki/Linux-dependencies>`_.
Build Build
===== =====
Building is fairly straightforward. Enter the ``build`` folder and start the build like this:: 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 cd build
cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF
@ -72,8 +89,8 @@ Fixing the libstdc++ version bug
When compiling dfhack yourself, it builds against your system libc. When compiling dfhack yourself, it builds against your system libc.
When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which
is usually way older, and incompatible with your dfhack. This manifests with comes from GCC 4.5 and is incompatible with code compiled with newer GCC versions.
the error message:: This manifests itself with the error message::
./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version ./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version
`GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so) `GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so)
@ -84,18 +101,33 @@ to your system lib and everything will work fine::
cd /path/to/DF/ cd /path/to/DF/
rm libs/libstdc++.so.6 rm libs/libstdc++.so.6
Alternatively, this issue can be avoided by compiling DFHack with GCC 4.5.
======== ========
Mac OS X Mac OS X
======== ========
If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST. DFHack functions similarly on OS X and Linux, and the majority of the
information above regarding the build process (cmake and make) applies here
as well.
* If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST.
* If you are building on 10.10+, read the "Yosemite Changes" subsection before building.
1. Download and unpack a copy of the latest DF 1. Download and unpack a copy of the latest DF
2. Install Xcode from Mac App Store 2. Install Xcode from Mac App Store
3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. 3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools.
4. Install dependencies 4. Install dependencies
Option 1: Using MacPorts: Option 1: Using Homebrew:
* `Install Homebrew <http://brew.sh/>`_ and run:
* ``brew tap homebrew/versions``
* ``brew install git``
* ``brew install cmake``
* ``brew install gcc45``
Option 2: Using MacPorts:
* `Install MacPorts <http://www.macports.org/>`_ * `Install MacPorts <http://www.macports.org/>`_
* Run ``sudo port install gcc45 +universal cmake +universal git-core +universal`` * Run ``sudo port install gcc45 +universal cmake +universal git-core +universal``
@ -103,14 +135,6 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa
At some point during this process, it may ask you to install a Java environment; let it do so. At some point during this process, it may ask you to install a Java environment; let it do so.
Option 2: Using Homebrew:
* `Install Homebrew <http://brew.sh/>`_ and run:
* ``brew tap homebrew/versions``
* ``brew install git``
* ``brew install cmake``
* ``brew install gcc45 --enable-multilib``
5. Install perl dependencies 5. Install perl dependencies
1. ``sudo cpan`` 1. ``sudo cpan``
@ -123,23 +147,21 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa
6. Get the dfhack source:: 6. Get the dfhack source::
git clone git://github.com/DFHack/dfhack.git git clone --recursive https://github.com/DFHack/dfhack.git
cd dfhack cd dfhack
git submodule init
git submodule update
7. Set environment variables: 7. Set environment variables:
Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``)::
export CC=/usr/local/bin/gcc-4.5
export CXX=/usr/local/bin/g++-4.5
Macports:: Macports::
export CC=/opt/local/bin/gcc-mp-4.5 export CC=/opt/local/bin/gcc-mp-4.5
export CXX=/opt/local/bin/g++-mp-4.5 export CXX=/opt/local/bin/g++-mp-4.5
Homebrew::
export CC=/usr/local/bin/gcc-4.5
export CXX=/usr/local/bin/g++-4.5
8. Build dfhack:: 8. Build dfhack::
mkdir build-osx mkdir build-osx
@ -172,27 +194,6 @@ Windows
======= =======
On Windows, DFHack replaces the SDL library distributed with DF. On Windows, DFHack replaces the SDL library distributed with DF.
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.
You will need some sort of Windows port of git, or a GUI. Some examples:
* http://msysgit.github.io/ - this is a command line version of git for windows. Most tutorials on git usage will apply.
* http://code.google.com/p/tortoisegit/ - this puts a pretty, graphical face on top of msysgit :)
The code resides here: https://github.com/DFHack/dfhack
If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address::
git clone git://github.com/DFHack/dfhack.git
cd dfhack
git submodule init
git submodule update
The tortoisegit GUI should have the equivalent options included.
If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).
Dependencies Dependencies
============ ============
First, you need ``cmake``. Get the win32 installer version from the official First, you need ``cmake``. Get the win32 installer version from the official
@ -264,7 +265,7 @@ The most important parts of DFHack are the Core, Console, Modules and Plugins.
* Core acts as the centerpiece of DFHack - it acts as a filter between DF and SDL and synchronizes the various plugins with DF. * Core acts as the centerpiece of DFHack - it acts as a filter between DF and SDL and synchronizes the various plugins with DF.
* Console is a thread-safe console that can be used to invoke commands exported by Plugins. * Console is a thread-safe console that can be used to invoke commands exported by Plugins.
* Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. Higl-level is mostly method calls, low-level publicly visible pointers to DF's data structures. * Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. High-level is mostly method calls, low-level publicly visible pointers to DF's data structures.
* Plugins are the tools that use all the other stuff to make things happen. A plugin can have a list of commands that it exports and an onupdate function that will be called each DF game tick. * Plugins are the tools that use all the other stuff to make things happen. A plugin can have a list of commands that it exports and an onupdate function that will be called each DF game tick.
Rudimentary API documentation can be built using doxygen (see build options with ``ccmake`` or ``cmake-gui``). Rudimentary API documentation can be built using doxygen (see build options with ``ccmake`` or ``cmake-gui``).
@ -280,7 +281,7 @@ DF data structure definitions
DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule. DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.
Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code. Data structure layouts are described in files following the df.\*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code.
Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime. Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.

@ -79,6 +79,7 @@ James Logsdon jlogsdon
melkor217 melkor217 melkor217 melkor217
acwatkins acwatkins acwatkins acwatkins
Wes Malone wesQ3 Wes Malone wesQ3
Michon van Dooren MaienM
======================= ==================== =========================== ======================= ==================== ===========================
And these are the cool people who made **Stonesense**. And these are the cool people who made **Stonesense**.

@ -164,30 +164,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
jsonxx license jsoncpp license
Copyright (c) 2010 Hong Jiang Copyright (c) 2007-2010 Baptiste Lepilleur
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, restriction, including without limitation the rights to use, copy,
copy, modify, merge, publish, distribute, sublicense, and/or sell modify, merge, publish, distribute, sublicense, and/or sell copies
copies of the Software, and to permit persons to whom the of the Software, and to permit persons to whom the Software is
Software is furnished to do so, subject to the following furnished to do so, subject to the following conditions:
conditions:
The above copyright notice and this permission notice shall be The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software. included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
OTHER DEALINGS IN THE SOFTWARE. SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
JSON.lua license JSON.lua license

@ -785,6 +785,20 @@ can be omitted.
Returns the DF version string from ``symbols.xml``. Returns the DF version string from ``symbols.xml``.
* ``getDFHackVersion()``
* ``getDFHackRelease()``
* ``getCompiledDFVersion()``
* ``getGitDescription()``
* ``getGitCommit()``
Return information about the DFHack build in use.
**Note:** ``getCompiledDFVersion()`` returns the DF version specified at compile time,
while ``getDFVersion()`` returns the version and typically the OS as well.
These do not necessarily match - for example, DFHack 0.34.11-r5 worked with
DF 0.34.10 and 0.34.11, so the former function would always return ``0.34.11``
while the latter would return ``v0.34.10 <platform>`` or ``v0.34.11 <platform>``.
* ``dfhack.getDFPath()`` * ``dfhack.getDFPath()``
Returns the DF directory path. Returns the DF directory path.

62
NEWS

@ -3,17 +3,26 @@ DFHack Future
A method for caching screen output is now available to Lua (and C++) A method for caching screen output is now available to Lua (and C++)
Developer plugins can be ignored on startup by setting the DFHACK_NO_DEV_PLUGINS environment variable Developer plugins can be ignored on startup by setting the DFHACK_NO_DEV_PLUGINS environment variable
The console on Linux and OS X now recognizes keyboard input between prompts The console on Linux and OS X now recognizes keyboard input between prompts
JSON libraries available (C++ and Lua)
More DFHack build information used in plugin version checks and available to plugins and lua scripts
Fixed a rare overflow issue that could cause crashes on Linux and OS X
Stopped DF window from receiving input when unfocused on OS X
Fixed issues with keybindings involving Ctrl-A and Ctrl-Z, as well as Alt-E/U/N on OS X
Multiple contexts can now be specified when adding keybindings
Lua Lua
Scripts can be enabled with the built-in enable/disable commands Scripts can be enabled with the built-in enable/disable commands
A new function, reqscript(), is available as a safer alternative to script_environment() A new function, reqscript(), is available as a safer alternative to script_environment()
Lua viewscreens can choose not to intercept the OPTIONS keybinding
New internal commands New internal commands
kill-lua: Interrupt running Lua scripts kill-lua: Interrupt running Lua scripts
New plugins New plugins
confirm: Adds confirmation dialogs for several potentially dangerous actions
fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499)
title-version (formerly vshook): Display DFHack version on title screen
New scripts New scripts
burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) burial: sets all unowned coffins to allow burial ("-pets" to allow pets too)
fix-ster: changes fertility/sterility of animals or dwarves fix-ster: changes fertility/sterility of animals or dwarves
view-item-info: adds information and customisable descriptions to item viewscreens view-item-info: adds information and customisable descriptions to item viewscreens
item-descriptions: holds a default description for every item type and subtype
warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found
New tweaks New tweaks
kitchen-keys: Fixes DF kitchen meal keybindings kitchen-keys: Fixes DF kitchen meal keybindings
@ -23,24 +32,61 @@ DFHack Future
Plugins with vmethod hooks can now be reloaded on OS X Plugins with vmethod hooks can now be reloaded on OS X
Lua's os.system() now works on OS X Lua's os.system() now works on OS X
Fixed default arguments in Lua gametype detection functions Fixed default arguments in Lua gametype detection functions
gui/hack-wish now properly assigns quality to items. Circular lua dependencies (reqscript/script_environment) fixed
Prevented crash in Items::createItem()
buildingplan: Now supports hatch covers
gui/create-item:
- fixed assigning quality to items
- made "esc" work properly
gui/gm-editor handles lua tables properly gui/gm-editor handles lua tables properly
manipulator: fixed crash when selecting custom professions when none are found
remotefortressreader: fixed crash when attempting to send map info when no map was loaded
search: fixed crash in unit list after cancelling a job search: fixed crash in unit list after cancelling a job
steam-engine: fixed a crash on arena load stockpiles: now checks/sanitizes filenames when saving
stocks: fixed a crash when right-clicking
steam-engine:
- fixed a crash on arena load
- number keys (e.g. 2/8) take priority over cursor keys when applicable
tweak fps-min fixed tweak fps-min fixed
tweak farm-plot-select: Stopped controls from appearing when plots weren't fully built
workflow: Fixed some issues with stuck jobs workflow: Fixed some issues with stuck jobs
- Note: Existing stuck jobs must be cancelled and re-added - Note: Existing stuck jobs must be cancelled and re-added
zone: Fixed a crash when using "zone set" (and a few other potential crashes)
Misc Improvements Misc Improvements
dwarfmonitor widgets' positions, formats, etc. are now customizable (see Readme) autolabor:
dwarfmonitor weather display now separated from the date display - Stopped modification of labors that shouldn't be modified for brokers/diplomats
dwarfmonitor: New mouse cursor widget - Prioritize skilled dwarves more efficiently
gui/gm-editor: Pointers can now be displaced - Prevent dwarves from running away with tools from previous jobs
dwarfmonitor:
- widgets' positions, formats, etc. are now customizable (see Readme)
- weather display now separated from the date display
- New mouse cursor widget
full-heal: "-r" option removes corpses
gui/gm-editor
- Pointers can now be displaced
- Added some useful aliases: "item" for the selected item, "screen" for the current screen, etc.
- Now avoids errors with unrecognized types
gui/hack-wish: renamed to gui/create-item gui/hack-wish: renamed to gui/create-item
"keybinding list" accepts a context "keybinding list" accepts a context
lever:
- Lists lever names
- "lever pull" can be used to pull the currently-selected lever
memview: Fixed display issue
nyan: Can now be stopped with dfhack-run nyan: Can now be stopped with dfhack-run
quicksave: Restricted to fortress mode quicksave: Restricted to fortress mode
search: Now supports the noble suggestion screen (e.g. suggesting a baron) remotefortressreader: Exposes more information
search:
- Supports noble suggestion screen (e.g. suggesting a baron)
- Supports fortress mode loo[k] menu
- Recognizes ? and ; keys
stocks: can now match beginning and end of item names
teleport: Fixed cursor recognition
tweak:
- debug output now logged to stderr.log instead of console - makes DFHack start faster
- farm-plot-select: Fixed issues with selecting undiscovered crops
workflow: Improved handling of plant reactions
Removed Removed
embark-tools nano: 1x1 embarks are now possible in vanilla 0.40.24
DFHack 0.40.24-r3 DFHack 0.40.24-r3
Internals Internals

@ -212,7 +212,7 @@ Possible ways to call the command:
The *<key>* parameter above has the following *case-sensitive* syntax:: The *<key>* parameter above has the following *case-sensitive* syntax::
[Ctrl-][Alt-][Shift-]KEY[@context] [Ctrl-][Alt-][Shift-]KEY[@context[|context...]]
where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts. where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts.
@ -227,7 +227,8 @@ the ``keybinding`` command among other things prints the current context string.
Only bindings with a *context* tag that either matches the current context fully, Only bindings with a *context* tag that either matches the current context fully,
or is a prefix ending at a '/' boundary would be considered for execution, i.e. or is a prefix ending at a '/' boundary would be considered for execution, i.e.
for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@foo/bar``, for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@foo/bar``,
``@foo`` or none. ``@foo`` or none. Multiple contexts can be specified by separating them with a
pipe (``|``) - for example, ``@foo|bar|baz/foo``.
Enabling plugins Enabling plugins
================ ================
@ -311,6 +312,17 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p
Game interface Game interface
============== ==============
confirm
-------
Implements several confirmation dialogs for potentially destructive actions
(for example, seizing goods from traders or deleting hauling routes).
Usage:
* ``enable confirm`` or ``confirm enable all``: Enable all confirmations (replace with ``disable`` to disable)
* ``confirm enable option1 [option2...]``: Enable (or disable) specific confirmations. Run ``confirm help`` for a complete list of options.
follow follow
------ ------
Makes the game view follow the currently highlighted unit after you exit from Makes the game view follow the currently highlighted unit after you exit from
@ -1252,6 +1264,22 @@ This command adds the Guild Representative position to all Human civilizations,
allowing them to make trade agreements (just as they did back in 0.28.181.40d allowing them to make trade agreements (just as they did back in 0.28.181.40d
and earlier) in case you haven't already modified your raws accordingly. and earlier) in case you haven't already modified your raws accordingly.
fix-unit-occupancy
------------------
This plugin fixes issues with unit occupancy, notably issues with phantom
"unit blocking tile" messages (`Bug 3499`_). It can be run manually, or
periodically when enabled with the built-in enable/disable commands:
* ``fix-unit-occupancy``: Run the plugin immediately. Available options:
* ``-h``, ``here``, ``cursor``: Only operate on the tile at the cursor
* ``-n``, ``dry``, ``dry-run``: Do not write changes to map
* ``fix-unit-occupancy interval X``: Run the plugin every ``X`` ticks (when enabled).
The default is 1200 ticks, or 1 day. Ticks are only counted when the game is unpaused.
.. _`Bug 3499`: http://bay12games.com/dwarves/mantisbt/view.php?id=3499
fixveins fixveins
-------- --------
Removes invalid references to mineral inclusions and restores missing ones. Removes invalid references to mineral inclusions and restores missing ones.
@ -2144,7 +2172,6 @@ Tools:
* ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution. * ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution.
* ``mouse``: Implements mouse controls (currently in the local embark region only) * ``mouse``: Implements mouse controls (currently in the local embark region only)
* ``nano``: An implementation of nano embark - allows resizing below 2x2 when enabled.
* ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators. * ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators.
* ``sticky``: Maintains the selected local area while navigating the world map * ``sticky``: Maintains the selected local area while navigating the world map

@ -4,7 +4,7 @@ add_subdirectory(md5)
add_subdirectory(protobuf) add_subdirectory(protobuf)
add_subdirectory(tinyxml) add_subdirectory(tinyxml)
add_subdirectory(tthread) add_subdirectory(tthread)
add_subdirectory(jsonxx) add_subdirectory(jsoncpp)
# build clsocket static and only as a dependency. Setting those options here overrides its own default settings. # build clsocket static and only as a dependency. Setting those options here overrides its own default settings.
OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF)
OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON)

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

@ -0,0 +1,31 @@
#include "jsoncpp.h"
#pragma once
namespace JsonEx {
template <typename T> bool is (const Json::Value &val) { return false; }
template <typename T> T as (const Json::Value &val);
template <typename T> T get (const Json::Value &val, const std::string &key, const T &default_) { return default_; }
#define define_helpers(type, is_func, as_func) \
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_; }
define_helpers(bool, isBool, asBool);
define_helpers(Json::Int, isInt, asInt);
define_helpers(Json::UInt, isUInt, asUInt);
define_helpers(Json::Int64, isInt64, asInt64);
define_helpers(Json::UInt64, isUInt64, asUInt64);
define_helpers(float, isDouble, asFloat);
define_helpers(double, isDouble, asDouble);
define_helpers(std::string, isString, asString);
#undef define_helpers
inline std::string toSimpleString (const Json::Value &val)
{
Json::FastWriter w;
return w.write(val);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

@ -45,7 +45,7 @@ keybinding add Ctrl-Shift-P command-prompt
keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs"
keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats"
# export a Dwarf's preferences screen in BBCode to post ot a forum # export a Dwarf's preferences screen in BBCode to post to a forum
keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves
############################## ##############################
@ -191,6 +191,9 @@ tweak tradereq-pet-gender
# Globally acting plugins # # Globally acting plugins #
########################### ###########################
# Display DFHack version on title screen
enable title-version
# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) # Dwarf Manipulator (simple in-game Dwarf Therapist replacement)
enable manipulator enable manipulator
@ -201,8 +204,8 @@ enable search
enable automaterial enable automaterial
# Other interface improvement tools # Other interface improvement tools
# enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone stocks autochop stockpiles
enable \ enable \
confirm \
dwarfmonitor \ dwarfmonitor \
mousequery \ mousequery \
automelt \ automelt \
@ -222,6 +225,9 @@ enable \
# allow the fortress bookkeeper to queue jobs through the manager # allow the fortress bookkeeper to queue jobs through the manager
enable stockflow enable stockflow
# enable mouse controls and sand indicator in embark screen
embark-tools enable sand mouse
########### ###########
# Scripts # # Scripts #
########### ###########

@ -240,14 +240,13 @@ IF(UNIX)
ENDIF() ENDIF()
ENDIF() ENDIF()
IF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE) IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF() ELSEIF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ELSE(WIN32) ELSE(WIN32)
#FIXME: do we really need psapi? #FIXME: do we really need psapi?
SET(PROJECT_LIBS psapi dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS psapi dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF() ENDIF()
ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp) ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp)
@ -298,6 +297,9 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE) IF(APPLE)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
IF(NOT EXISTS ${SDL_LIBRARY})
MESSAGE(FATAL_ERROR "SDL framework not found. Make sure CMAKE_INSTALL_PREFIX is specified and correct.")
ENDIF()
SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib)
SET(ZIP_LIBRARY /usr/lib/libz.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
@ -307,7 +309,7 @@ IF(APPLE)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF() ENDIF()
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsonxx dfhack-version ${PROJECT_LIBS}) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp dfhack-version ${PROJECT_LIBS})
SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "")
TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)

@ -73,6 +73,7 @@ using namespace DFHack;
#include <stdlib.h> #include <stdlib.h>
#include <fstream> #include <fstream>
#include "tinythread.h" #include "tinythread.h"
#include "md5wrapper.h"
#include "SDL_events.h" #include "SDL_events.h"
@ -1755,6 +1756,68 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve
void Core::onStateChange(color_ostream &out, state_change_event event) void Core::onStateChange(color_ostream &out, state_change_event event)
{ {
using df::global::gametype;
static md5wrapper md5w;
static std::string ostype = "";
if (!ostype.size())
{
ostype = "unknown OS";
if (vinfo) {
switch (vinfo->getOS())
{
case OS_WINDOWS:
ostype = "Windows";
break;
case OS_APPLE:
ostype = "OS X";
break;
case OS_LINUX:
ostype = "Linux";
break;
default:
break;
}
}
}
switch (event)
{
case SC_WORLD_LOADED:
case SC_WORLD_UNLOADED:
case SC_MAP_LOADED:
case SC_MAP_UNLOADED:
if (world && world->cur_savegame.save_dir.size())
{
std::string evtlogpath = "data/save/" + world->cur_savegame.save_dir + "/events-dfhack.log";
std::ofstream evtlog;
evtlog.open(evtlogpath, std::ios_base::app); // append
if (evtlog.fail())
{
out.printerr("Could not append to %s\n", evtlogpath.c_str());
}
else
{
char timebuf[30];
time_t rawtime = time(NULL);
struct tm * timeinfo = localtime(&rawtime);
strftime(timebuf, sizeof(timebuf), "[%Y-%m-%dT%H:%M:%S%z] ", timeinfo);
evtlog << timebuf;
evtlog << "DFHack " << Version::git_description() << " on " << ostype << "; ";
evtlog << "cwd md5: " << md5w.getHashFromString(getHackPath()).substr(0, 10) << "; ";
evtlog << "save: " << world->cur_savegame.save_dir << "; ";
evtlog << sc_event_name(event) << "; ";
if (gametype)
evtlog << "game type " << ENUM_KEY_STR(game_type, *gametype) << " (" << *gametype << ")";
else
evtlog << "game type unavailable";
evtlog << std::endl;
}
}
default:
break;
}
EventManager::onStateChange(out, event); EventManager::onStateChange(out, event);
buildings_onStateChange(out, event); buildings_onStateChange(out, event);
@ -1854,7 +1917,7 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke)
} }
// convert A-Z to their a-z counterparts: // convert A-Z to their a-z counterparts:
if('A' < unicode && unicode < 'Z') if('A' <= unicode && unicode <= 'Z')
{ {
unicode += 'a' - 'A'; unicode += 'a' - 'A';
} }
@ -1900,7 +1963,7 @@ int Core::DFH_SDL_Event(SDL::Event* ev)
// Use unicode so Windows gives the correct value for the // Use unicode so Windows gives the correct value for the
// user's Input Language // user's Input Language
if((ke->ksym.unicode & 0xff80) == 0) if(ke->ksym.unicode && ((ke->ksym.unicode & 0xff80) == 0))
{ {
int key = UnicodeAwareSym(*ke); int key = UnicodeAwareSym(*ke);
SelectHotkey(key, modstate); SelectHotkey(key, modstate);
@ -2042,6 +2105,23 @@ bool Core::ClearKeyBindings(std::string keyspec)
bool Core::AddKeyBinding(std::string keyspec, std::string cmdline) bool Core::AddKeyBinding(std::string keyspec, std::string cmdline)
{ {
size_t at_pos = keyspec.find('@');
if (at_pos != std::string::npos)
{
std::string raw_spec = keyspec.substr(0, at_pos);
std::string raw_focus = keyspec.substr(at_pos + 1);
if (raw_focus.find('|') != std::string::npos)
{
std::vector<std::string> focus_strings;
split_string(&focus_strings, raw_focus, "|");
for (size_t i = 0; i < focus_strings.size(); i++)
{
if (!AddKeyBinding(raw_spec + "@" + focus_strings[i], cmdline))
return false;
}
return true;
}
}
int sym; int sym;
KeyBinding binding; KeyBinding binding;
if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus)) if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus))
@ -2093,11 +2173,12 @@ std::vector<std::string> Core::ListKeyBindings(std::string keyspec)
return rv; return rv;
} }
////////////////
// ClassNamCheck
////////////////
// Since there is no Process.cpp, put ClassNamCheck stuff in Core.cpp /////////////////
// ClassNameCheck
/////////////////
// Since there is no Process.cpp, put ClassNameCheck stuff in Core.cpp
static std::set<std::string> known_class_names; static std::set<std::string> known_class_names;
static std::map<std::string, void*> known_vptrs; static std::map<std::string, void*> known_vptrs;

@ -1,3 +1,4 @@
#define NO_DFHACK_VERSION_MACROS
#include "DFHackVersion.h" #include "DFHackVersion.h"
#include "git-describe.h" #include "git-describe.h"
#include "Export.h" #include "Export.h"
@ -19,5 +20,9 @@ namespace DFHack {
{ {
return DFHACK_GIT_DESCRIPTION; return DFHACK_GIT_DESCRIPTION;
} }
const char *git_commit()
{
return DFHACK_GIT_COMMIT;
}
} }
} }

@ -71,6 +71,16 @@ DYLD_INTERPOSE(DFH_SDL_NumJoysticks,SDL_NumJoysticks);
/******************************************************************************* /*******************************************************************************
* SDL part starts here * * SDL part starts here *
*******************************************************************************/ *******************************************************************************/
#define SDL_APPMOUSEFOCUS 0x01 /**< The app has mouse coverage */
#define SDL_APPINPUTFOCUS 0x02 /**< The app has input focus */
#define SDL_APPACTIVE 0x04 /**< The application is active */
static uint8_t (*_SDL_GetAppState)(void) = 0;
DFhackCExport uint8_t SDL_GetAppState(void)
{
return _SDL_GetAppState();
}
// hook - called for each game tick (or more often) // hook - called for each game tick (or more often)
DFhackCExport int DFH_SDL_NumJoysticks(void) DFhackCExport int DFH_SDL_NumJoysticks(void)
{ {
@ -95,7 +105,7 @@ DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event)
pollevent_again: pollevent_again:
// if SDL returns 0 here, it means there are no more events. return 0 // if SDL returns 0 here, it means there are no more events. return 0
int orig_return = SDL_PollEvent(event); int orig_return = SDL_PollEvent(event);
if(!orig_return) if(!orig_return || !(SDL_GetAppState() & SDL_APPINPUTFOCUS))
return 0; return 0;
// otherwise we have an event to filter // otherwise we have an event to filter
else if( event != 0 ) else if( event != 0 )
@ -265,7 +275,6 @@ DFhackCExport int SDL_SemPost(vPtr sem)
return _SDL_SemPost(sem); return _SDL_SemPost(sem);
} }
// hook - called at program start, initialize some stuffs we'll use later // hook - called at program start, initialize some stuffs we'll use later
static int (*_SDL_Init)(uint32_t flags) = 0; static int (*_SDL_Init)(uint32_t flags) = 0;
DFhackCExport int DFH_SDL_Init(uint32_t flags) DFhackCExport int DFH_SDL_Init(uint32_t flags)
@ -297,6 +306,7 @@ DFhackCExport int DFH_SDL_Init(uint32_t flags)
bind(SDL_SemWait); bind(SDL_SemWait);
bind(SDL_SemPost); bind(SDL_SemPost);
bind(SDL_GetAppState);
#undef bind #undef bind
fprintf(stderr, "dfhack: saved real SDL functions\n"); fprintf(stderr, "dfhack: saved real SDL functions\n");

@ -38,6 +38,7 @@ distribution.
#include "DataDefs.h" #include "DataDefs.h"
#include "DataIdentity.h" #include "DataIdentity.h"
#include "DataFuncs.h" #include "DataFuncs.h"
#include "DFHackVersion.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Gui.h" #include "modules/Gui.h"
@ -1396,6 +1397,8 @@ static std::string df2utf(std::string s) { return DF2UTF(s); }
static std::string utf2df(std::string s) { return UTF2DF(s); } static std::string utf2df(std::string s) { return UTF2DF(s); }
static std::string df2console(std::string s) { return DF2CONSOLE(s); } static std::string df2console(std::string s) { return DF2CONSOLE(s); }
#define WRAP_VERSION_FUNC(name, function) WRAPN(name, DFHack::Version::function)
static const LuaWrapper::FunctionReg dfhack_module[] = { static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType), WRAP(getOSType),
WRAP(getDFVersion), WRAP(getDFVersion),
@ -1408,6 +1411,11 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(df2utf), WRAP(df2utf),
WRAP(utf2df), WRAP(utf2df),
WRAP(df2console), WRAP(df2console),
WRAP_VERSION_FUNC(getDFHackVersion, dfhack_version),
WRAP_VERSION_FUNC(getDFHackRelease, dfhack_release),
WRAP_VERSION_FUNC(getCompiledDFVersion, df_version),
WRAP_VERSION_FUNC(getGitDescription, git_description),
WRAP_VERSION_FUNC(getGitCommit, git_commit),
{ NULL, NULL } { NULL, NULL }
}; };
@ -2201,7 +2209,14 @@ static int filesystem_listdir(lua_State *L)
luaL_checktype(L,1,LUA_TSTRING); luaL_checktype(L,1,LUA_TSTRING);
std::string dir=lua_tostring(L,1); std::string dir=lua_tostring(L,1);
std::vector<std::string> files; std::vector<std::string> files;
DFHack::Filesystem::listdir(dir, files); int err = DFHack::Filesystem::listdir(dir, files);
if (err)
{
lua_pushnil(L);
lua_pushstring(L, strerror(err));
lua_pushinteger(L, err);
return 3;
}
lua_newtable(L); lua_newtable(L);
for(int i=0;i<files.size();i++) for(int i=0;i<files.size();i++)
{ {
@ -2224,8 +2239,12 @@ static int filesystem_listdir_recursive(lua_State *L)
if (err) if (err)
{ {
lua_pushnil(L); lua_pushnil(L);
if (err == -1)
lua_pushfstring(L, "max depth exceeded: %d", depth);
else
lua_pushstring(L, strerror(err));
lua_pushinteger(L, err); lua_pushinteger(L, err);
return 2; return 3;
} }
lua_newtable(L); lua_newtable(L);
int i = 1; int i = 1;

@ -264,7 +264,6 @@ bool Plugin::load(color_ostream &con)
return false; return false;
} }
*plug_self = this; *plug_self = this;
RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init"); plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
std::vector<std::string>* plugin_globals = *((std::vector<std::string>**) LookupPlugin(plug, "plugin_globals")); std::vector<std::string>* plugin_globals = *((std::vector<std::string>**) LookupPlugin(plug, "plugin_globals"));
if (plugin_globals->size()) if (plugin_globals->size())
@ -298,6 +297,7 @@ bool Plugin::load(color_ostream &con)
commands.clear(); commands.clear();
if(plugin_init(con,commands) == CR_OK) if(plugin_init(con,commands) == CR_OK)
{ {
RefAutolock lock(access);
state = PS_LOADED; state = PS_LOADED;
parent->registerCommands(this); parent->registerCommands(this);
if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled)
@ -311,8 +311,7 @@ bool Plugin::load(color_ostream &con)
plugin_is_enabled = 0; plugin_is_enabled = 0;
plugin_onupdate = 0; plugin_onupdate = 0;
reset_lua(); reset_lua();
ClosePlugin(plugin_lib); plugin_abort_load;
state = PS_BROKEN;
return false; return false;
} }
} }
@ -327,7 +326,7 @@ bool Plugin::unload(color_ostream &con)
EventManager::unregisterAll(this); EventManager::unregisterAll(this);
// notify the plugin about an attempt to shutdown // notify the plugin about an attempt to shutdown
if (plugin_onstatechange && if (plugin_onstatechange &&
plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) plugin_onstatechange(con, SC_BEGIN_UNLOAD) != CR_OK)
{ {
con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str());
access->unlock(); access->unlock();

@ -259,18 +259,24 @@ uint32_t Process::getTickCount()
string Process::getPath() string Process::getPath()
{ {
static string cached_path = "";
if (cached_path.size())
return cached_path;
char path[1024]; char path[1024];
char *real_path; char *real_path;
uint32_t size = sizeof(path); uint32_t size = sizeof(path);
if (getcwd(path, size)) if (getcwd(path, size))
return string(path); {
cached_path = string(path);
return cached_path;
}
if (_NSGetExecutablePath(path, &size) == 0) { if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
std::string path_string(real_path); std::string path_string(real_path);
int last_slash = path_string.find_last_of("/"); int last_slash = path_string.find_last_of("/");
std::string directory = path_string.substr(0,last_slash); cached_path = path_string.substr(0,last_slash);
return directory; return cached_path;
} }
int Process::getPID() int Process::getPID()

@ -81,6 +81,8 @@ VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uint32_t timestamp
void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
{ {
bool no_vtables = getenv("DFHACK_NO_VTABLES");
bool no_globals = getenv("DFHACK_NO_GLOBALS");
TiXmlElement* pMemEntry; TiXmlElement* pMemEntry;
const char *cstr_name = entry->Attribute("name"); const char *cstr_name = entry->Attribute("name");
if (!cstr_name) if (!cstr_name)
@ -136,6 +138,8 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
cerr << "Dummy symbol table entry: " << cstr_key << endl; cerr << "Dummy symbol table entry: " << cstr_key << endl;
continue; continue;
} }
if ((is_vtable && no_vtables) || (!is_vtable && no_globals))
continue;
uint32_t addr = strtol(cstr_value, 0, 0); uint32_t addr = strtol(cstr_value, 0, 0);
if (is_vtable) if (is_vtable)
mem->setVTable(cstr_key, addr); mem->setVTable(cstr_key, addr);

@ -1,9 +1,15 @@
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}" WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION) OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_COMMIT)
string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION) string(STRIP ${DFHACK_GIT_DESCRIPTION} DFHACK_GIT_DESCRIPTION)
file(WRITE ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h file(WRITE ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
"#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"") "#define DFHACK_GIT_DESCRIPTION \"${DFHACK_GIT_DESCRIPTION}\"\n")
string(STRIP ${DFHACK_GIT_COMMIT} DFHACK_GIT_COMMIT)
file(APPEND ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
"#define DFHACK_GIT_COMMIT \"${DFHACK_GIT_COMMIT}\"")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
${dfhack_SOURCE_DIR}/library/include/git-describe.h) ${dfhack_SOURCE_DIR}/library/include/git-describe.h)

@ -5,5 +5,14 @@ namespace DFHack {
const char *df_version(); const char *df_version();
const char *dfhack_release(); const char *dfhack_release();
const char *git_description(); const char *git_description();
const char *git_commit();
} }
} }
#ifndef NO_DFHACK_VERSION_MACROS
#define DF_VERSION DFHack::Version::df_version()
#define DFHACK_RELEASE DFHack::Version::dfhack_release()
#define DFHACK_VERSION DFHack::Version::dfhack_version()
#define DFHACK_GIT_DESCRIPTION DFHack::Version::git_description()
#define DFHACK_GIT_COMMIT DFHack::Version::git_commit()
#endif

@ -700,7 +700,7 @@ namespace DFHack {
#define ENUM_NEXT_ITEM(enum,val) \ #define ENUM_NEXT_ITEM(enum,val) \
(DFHack::next_enum_item<df::enum>(val)) (DFHack::next_enum_item<df::enum>(val))
#define FOR_ENUM_ITEMS(enum,iter) \ #define FOR_ENUM_ITEMS(enum,iter) \
for(df::enum iter = ENUM_FIRST_ITEM(enum); iter <= ENUM_LAST_ITEM(enum); iter = df::enum(1+int(iter))) for(df::enum iter = ENUM_FIRST_ITEM(enum); is_valid_enum_item(iter); iter = df::enum(1+int(iter)))
/* /*
* Include mandatory generated headers. * Include mandatory generated headers.

@ -1 +1 @@
Subproject commit 53c424e7d891aa6828c3690ad0a3d8d9248dbc3f Subproject commit 7cfed040fe83b94fb6b97afec1c5d86ccf1cb81e

@ -105,6 +105,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(cleanowned cleanowned.cpp) DFHACK_PLUGIN(cleanowned cleanowned.cpp)
DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(colonies colonies.cpp)
DFHACK_PLUGIN(command-prompt command-prompt.cpp) DFHACK_PLUGIN(command-prompt command-prompt.cpp)
DFHACK_PLUGIN(confirm confirm.cpp)
DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(createitem createitem.cpp)
DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp)
DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(deramp deramp.cpp)
@ -112,7 +113,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(digFlood digFlood.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(drybuckets drybuckets.cpp) DFHACK_PLUGIN(drybuckets drybuckets.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES jsonxx lua) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)
@ -120,6 +121,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(filltraffic filltraffic.cpp) DFHACK_PLUGIN(filltraffic filltraffic.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp) DFHACK_PLUGIN(fix-armory fix-armory.cpp)
DFHACK_PLUGIN(fixpositions fixpositions.cpp) DFHACK_PLUGIN(fixpositions fixpositions.cpp)
DFHACK_PLUGIN(fix-unit-occupancy fix-unit-occupancy.cpp)
DFHACK_PLUGIN(fixveins fixveins.cpp) DFHACK_PLUGIN(fixveins fixveins.cpp)
DFHACK_PLUGIN(flows flows.cpp) DFHACK_PLUGIN(flows flows.cpp)
DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(follow follow.cpp)
@ -159,6 +161,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(stocks stocks.cpp)
DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp)
DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h)
DFHACK_PLUGIN(title-version title-version.cpp)
DFHACK_PLUGIN(trackstop trackstop.cpp) DFHACK_PLUGIN(trackstop trackstop.cpp)
# DFHACK_PLUGIN(treefarm treefarm.cpp) # DFHACK_PLUGIN(treefarm treefarm.cpp)
DFHACK_PLUGIN(tubefill tubefill.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp)

@ -59,7 +59,7 @@ ENDMACRO()
MACRO(DFHACK_PLUGIN) MACRO(DFHACK_PLUGIN)
PARSE_ARGUMENTS(PLUGIN PARSE_ARGUMENTS(PLUGIN
"LINK_LIBRARIES;DEPENDS;PROTOBUFS" "LINK_LIBRARIES;DEPENDS;PROTOBUFS;COMPILE_FLAGS;COMPILE_FLAGS_GCC;COMPILE_FLAGS_MSVC"
"SOME_OPT" "SOME_OPT"
${ARGN} ${ARGN}
) )
@ -95,6 +95,13 @@ MACRO(DFHACK_PLUGIN)
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES})
ENDIF() ENDIF()
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS}")
IF(UNIX)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_GCC}")
ELSE()
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS_MSVC}")
ENDIF()
IF(APPLE) IF(APPLE)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "") SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "")
ELSEIF(UNIX) ELSEIF(UNIX)

@ -1,6 +1,7 @@
// automatically chop trees // automatically chop trees
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
#include "Core.h" #include "Core.h"
#include "Console.h" #include "Console.h"

@ -37,6 +37,7 @@ using MapExtras::MapCache;
using df::building_stockpilest; using df::building_stockpilest;
DFHACK_PLUGIN("autodump"); DFHACK_PLUGIN("autodump");
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
// Stockpile interface START // Stockpile interface START
@ -254,9 +255,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{ {
if (!gps)
return CR_FAILURE;
if (enable != is_enabled) if (enable != is_enabled)
{ {
if (!INTERPOSE_HOOK(dump_hook, feed).apply(enable) || if (!INTERPOSE_HOOK(dump_hook, feed).apply(enable) ||

@ -18,6 +18,7 @@ using df::building_stockpilest;
DFHACK_PLUGIN("automelt"); DFHACK_PLUGIN("automelt");
#define PLUGIN_VERSION 0.3 #define PLUGIN_VERSION 0.3
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(cursor);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
@ -282,9 +283,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{ {
if (!gps)
return CR_FAILURE;
if (enable != is_enabled) if (enable != is_enabled)
{ {
if (!INTERPOSE_HOOK(melt_hook, feed).apply(enable) || if (!INTERPOSE_HOOK(melt_hook, feed).apply(enable) ||

@ -22,6 +22,7 @@
using df::building_stockpilest; using df::building_stockpilest;
DFHACK_PLUGIN("autotrade"); DFHACK_PLUGIN("autotrade");
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(cursor);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
@ -465,9 +466,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{ {
if (!gps)
return CR_FAILURE;
if (enable != is_enabled) if (enable != is_enabled)
{ {
depot_info.reset(); depot_info.reset();

@ -9,6 +9,7 @@ void debug(const string &msg)
color_ostream_proxy out(Core::getInstance().getConsole()); color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl;
} }
#define dbg Core::getInstance().getConsole()
void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; } void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; }
@ -521,51 +522,47 @@ void Planner::reset(color_ostream &out)
void Planner::initialize() void Planner::initialize()
{ {
std::vector<std::string> item_names; #define add_building_type(btype, itype) \
typedef df::enum_traits<df::item_type> item_types; item_for_building_type[df::building_type::btype] = df::item_type::itype; \
int size = item_types::last_item_value - item_types::first_item_value+1; default_item_filters[df::building_type::btype] = ItemFilter(); \
for (size_t i = 1; i < size; i++) available_item_vectors[df::item_type::itype] = std::vector<df::item *>(); \
{ is_relevant_item_type[df::item_type::itype] = true; \
is_relevant_item_type[(df::item_type) (i-1)] = false; if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \
std::string item_name = toLower(item_types::key_table[i]); planmode_enabled[df::building_type::btype] = false
std::string item_name_clean;
for (auto c = item_name.begin(); c != item_name.end(); c++) FOR_ENUM_ITEMS(item_type, it)
{ is_relevant_item_type[it] = false;
if (*c == '_')
continue; add_building_type(Armorstand, ARMORSTAND);
item_name_clean += *c; add_building_type(Bed, BED);
} add_building_type(Chair, CHAIR);
item_names.push_back(item_name_clean); add_building_type(Coffin, COFFIN);
} add_building_type(Door, DOOR);
// add_building_type(Floodgate, FLOODGATE); not displayed before or after being built
typedef df::enum_traits<df::building_type> building_types; add_building_type(Hatch, HATCH_COVER);
size = building_types::last_item_value - building_types::first_item_value+1; // not displayed before or after being built:
for (size_t i = 1; i < size; i++) // add_building_type(GrateWall, GRATE);
{ // add_building_type(GrateFloor, GRATE);
auto building_type = (df::building_type) (i-1); // add_building_type(BarsVertical, BAR);
if (building_type == building_type::Weapon || building_type == building_type::Floodgate) // add_building_type(BarsFloor, BAR);
continue; add_building_type(Cabinet, CABINET);
add_building_type(Box, BOX);
std::string building_name = toLower(building_types::key_table[i]); // skip kennels, farm plot
for (size_t j = 0; j < item_names.size(); j++) add_building_type(Weaponrack, WEAPONRACK);
{ add_building_type(Statue, STATUE);
if (building_name == item_names[j]) add_building_type(Slab, SLAB);
{ add_building_type(Table, TABLE);
auto btype = (df::building_type) (i-1); // skip roads ... furnaces
auto itype = (df::item_type) j; add_building_type(WindowGlass, WINDOW);
// skip gem window ... support
item_for_building_type[btype] = itype; add_building_type(AnimalTrap, ANIMALTRAP);
default_item_filters[btype] = ItemFilter(); add_building_type(Chain, CHAIN);
available_item_vectors[itype] = std::vector<df::item *>(); add_building_type(Cage, CAGE);
is_relevant_item_type[itype] = true; // skip archery target
add_building_type(TractionBench, TRACTION_BENCH);
if (planmode_enabled.find(btype) == planmode_enabled.end()) // skip nest box, hive (tools)
{
planmode_enabled[btype] = false; #undef add_building_type
}
}
}
}
} }
void Planner::doCycle() void Planner::doCycle()

@ -2,6 +2,7 @@
#define BUILDINGPLAN_H #define BUILDINGPLAN_H
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
#include <functional> #include <functional>

@ -189,7 +189,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
else if (isInNobleRoomQueryMode()) else if (isInNobleRoomQueryMode())
{ {
auto np = getNoblePositionOfSelectedBuildingOwner(); auto np = getNoblePositionOfSelectedBuildingOwner();
df::interface_key last_token = *input->rbegin(); df::interface_key last_token = get_string_key(input);
if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058)
{ {
int selection = last_token - interface_key::STRING_A048; int selection = last_token - interface_key::STRING_A048;

@ -0,0 +1,504 @@
#include <set>
#include <map>
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "VTableInterpose.h"
#include "uicommon.h"
#include "modules/Gui.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_tradegoodsst.h"
using namespace DFHack;
using namespace df::enums;
using std::string;
using std::vector;
DFHACK_PLUGIN("confirm");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(ui);
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;
template <typename VT, typename FT>
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; \
}
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; \
}
goods_all_selected_func(trader);
goods_all_selected_func(broker);
#undef goods_all_selected_func
template <class T>
class confirmation {
public:
enum cstate { INACTIVE, ACTIVE, SELECTED };
typedef T screen_type;
screen_type *screen;
bool feed (ikey_set *input) {
if (state == INACTIVE)
{
for (auto it = input->begin(); it != input->end(); ++it)
{
if (intercept_key(*it))
{
last_key = *it;
state = ACTIVE;
return true;
}
}
return false;
}
else if (state == ACTIVE)
{
if (input->count(df::interface_key::LEAVESCREEN))
state = INACTIVE;
else if (input->count(df::interface_key::SELECT))
state = SELECTED;
return true;
}
return false;
}
bool key_conflict (df::interface_key key)
{
if (key == df::interface_key::SELECT || key == df::interface_key::LEAVESCREEN)
return false;
return state == ACTIVE;
}
void render() {
static vector<string> lines;
Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK);
Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK);
Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK);
Screen::Pen corner_dr = Screen::Pen((char)188, COLOR_GREY, COLOR_BLACK);
Screen::Pen border_ud = Screen::Pen((char)205, COLOR_GREY, COLOR_BLACK);
Screen::Pen border_lr = Screen::Pen((char)186, COLOR_GREY, COLOR_BLACK);
if (state == ACTIVE)
{
split_string(&lines, get_message(), "\n");
size_t max_length = 30;
for (auto it = lines.begin(); it != lines.end(); ++it)
max_length = std::max(max_length, it->size());
int width = max_length + 4;
int height = lines.size() + 4;
int x1 = (gps->dimx / 2) - (width / 2);
int x2 = x1 + width - 1;
int y1 = (gps->dimy / 2) - (height / 2);
int y2 = y1 + height - 1;
for (int x = x1; x <= x2; x++)
{
Screen::paintTile(border_ud, x, y1);
Screen::paintTile(border_ud, x, y2);
}
for (int y = y1; y <= y2; y++)
{
Screen::paintTile(border_lr, x1, y);
Screen::paintTile(border_lr, x2, y);
}
Screen::paintTile(corner_ul, x1, y1);
Screen::paintTile(corner_ur, x2, y1);
Screen::paintTile(corner_dl, x1, y2);
Screen::paintTile(corner_dr, x2, y2);
string title = " " + get_title() + " ";
Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK),
x2 - 6, y1, "DFHack");
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");
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");
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++)
{
Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]);
}
}
else if (state == SELECTED)
{
ikey_set tmp;
tmp.insert(last_key);
screen->feed(&tmp);
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)
{
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;
}
};
#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0)
#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \
static cls cls##_instance; \
struct cls##_hooks : cls::screen_type { \
typedef cls::screen_type interpose_base; \
DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \
{ \
cls##_instance.screen = this; \
if (!cls##_instance.feed(input)) \
INTERPOSE_NEXT(feed)(input); \
} \
DEFINE_VMETHOD_INTERPOSE(void, render, ()) \
{ \
cls##_instance.screen = this; \
INTERPOSE_NEXT(render)(); \
cls##_instance.render(); \
} \
DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \
{ \
return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \
} \
}; \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio);
class trade_confirmation : public confirmation<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);
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)
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
commands.push_back(PluginCommand(
"confirm",
"Confirmation dialogs",
df_confirm,
false, //allow non-interactive use
" confirmation enable|disable option|all ...\n"
" confirmation help|status\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
if (is_enabled != enable)
{
for (auto c = confirmations.begin(); c != confirmations.end(); ++c)
{
if (!c->second->apply(enable))
return CR_FAILURE;
}
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
if (plugin_enable(out, false) != CR_OK)
return CR_FAILURE;
return CR_OK;
}
void enable_conf (color_ostream &out, string name, bool state)
{
bool found = false;
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
if (it->first == name)
{
found = true;
it->second->apply(state);
}
}
if (!found)
out.printerr("Unrecognized option: %s\n", name.c_str());
}
command_result df_confirm (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
bool state = true;
if (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;
}
return CR_OK;
}
for (auto it = parameters.begin(); it != parameters.end(); ++it)
{
if (*it == "enable")
state = true;
else if (*it == "disable")
state = false;
else if (*it == "all")
{
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
it->second->apply(state);
}
}
else
enable_conf(out, *it, state);
}
return CR_OK;
}

@ -19,7 +19,6 @@ DFHACK_PLUGIN(stepBetween stepBetween.cpp)
DFHACK_PLUGIN(stockcheck stockcheck.cpp) DFHACK_PLUGIN(stockcheck stockcheck.cpp)
DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(tilesieve tilesieve.cpp) DFHACK_PLUGIN(tilesieve tilesieve.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(zoom zoom.cpp) DFHACK_PLUGIN(zoom zoom.cpp)
IF(UNIX) IF(UNIX)

@ -1,71 +0,0 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <VTableInterpose.h>
#include "df/graphic.h"
#include "df/viewscreen_titlest.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using df::global::gps;
DFHACK_PLUGIN("vshook");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
struct title_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
Screen::Pen pen(' ',COLOR_WHITE,COLOR_BLACK);
Screen::paintString(pen, 0, 0, "DFHack ");
Screen::paintString(pen, 7, 0, Version::dfhack_version());
}
};
IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render);
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(title_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// DON'T DO THIS IN NON-EXAMPLE PLUGINS
plugin_enable(out, true);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
INTERPOSE_HOOK(title_hook, render).remove();
return CR_OK;
}

@ -1,4 +1,5 @@
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
#include "DataDefs.h" #include "DataDefs.h"
@ -43,8 +44,6 @@
#include "df/descriptor_shape.h" #include "df/descriptor_shape.h"
#include "df/descriptor_color.h" #include "df/descriptor_color.h"
#include "jsonxx.h"
using std::deque; using std::deque;
DFHACK_PLUGIN("dwarfmonitor"); DFHACK_PLUGIN("dwarfmonitor");
@ -156,6 +155,7 @@ static void open_stats_srceen();
namespace dm_lua { namespace dm_lua {
static color_ostream_proxy *out; static color_ostream_proxy *out;
static lua_State *state;
typedef int(*initializer)(lua_State*); typedef int(*initializer)(lua_State*);
int no_args (lua_State *L) { return 0; } int no_args (lua_State *L) { return 0; }
void cleanup() void cleanup()
@ -165,26 +165,26 @@ namespace dm_lua {
delete out; delete out;
out = NULL; out = NULL;
} }
lua_close(state);
} }
bool init_call (lua_State *L, const char *func) bool init_call (const char *func)
{ {
if (!out) if (!out)
out = new color_ostream_proxy(Core::getInstance().getConsole()); out = new color_ostream_proxy(Core::getInstance().getConsole());
return Lua::PushModulePublic(*out, L, "plugins.dwarfmonitor", func); return Lua::PushModulePublic(*out, state, "plugins.dwarfmonitor", func);
} }
bool safe_call (lua_State *L, int nargs) bool safe_call (int nargs)
{ {
return Lua::SafeCall(*out, L, nargs, 0); return Lua::SafeCall(*out, state, nargs, 0);
} }
bool call (const char *func, initializer init = no_args) bool call (const char *func, initializer init = no_args)
{ {
auto L = Lua::Core::State; Lua::StackUnwinder top(state);
Lua::StackUnwinder top(L); if (!init_call(func))
if (!init_call(L, func))
return false; return false;
int nargs = init(L); int nargs = init(state);
return safe_call(L, nargs); return safe_call(nargs);
} }
template <typename KeyType, typename ValueType> template <typename KeyType, typename ValueType>
@ -1976,6 +1976,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
" Reload configuration file (dfhack-config/dwarfmonitor.json)\n" " Reload configuration file (dfhack-config/dwarfmonitor.json)\n"
)); ));
dm_lua::state = Lua::Open(out);
return CR_OK; return CR_OK;
} }

@ -8,6 +8,7 @@
#include "modules/Screen.h" #include "modules/Screen.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include <algorithm> #include <algorithm>
#include <map>
#include <set> #include <set>
#include <VTableInterpose.h> #include <VTableInterpose.h>
@ -61,37 +62,6 @@ void set_embark_pos (df::viewscreen_choose_start_sitest * screen,
int a, b, c, d, e, f; \ int a, b, c, d, e, f; \
get_embark_pos(screen, a, b, c, d, e, f); get_embark_pos(screen, a, b, c, d, e, f);
void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy)
{
/* Reproduces DF's embark resizing functionality
* Local area resizes up and to the right, unless it's already touching the edge
*/
GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height);
if (x1 == x2 && dx == -1)
dx = 0;
if (y1 == y2 && dy == -1)
dy = 0;
x2 += dx; // Resize right
while (x2 > 15)
{
x2--;
x1--;
}
x1 = std::max(0, x1);
y1 -= dy; // Resize up
while (y1 < 0)
{
y1++;
y2++;
}
y2 = std::min(15, y2);
set_embark_pos(screen, x1, x2, y1, y2);
update_embark_sidebar(screen);
}
typedef df::viewscreen_choose_start_sitest start_sitest; typedef df::viewscreen_choose_start_sitest start_sitest;
typedef std::set<df::interface_key> ikey_set; typedef std::set<df::interface_key> ikey_set;
@ -116,7 +86,7 @@ public:
virtual void after_feed(start_sitest* screen, ikey_set* input) { }; virtual void after_feed(start_sitest* screen, ikey_set* input) { };
virtual void after_mouse_event(start_sitest* screen) { }; virtual void after_mouse_event(start_sitest* screen) { };
}; };
std::vector<EmbarkTool*> tools; std::map<std::string, EmbarkTool*> tools;
/* /*
@ -160,47 +130,6 @@ public:
}; };
}; };
class NanoEmbark : public EmbarkTool
{
public:
virtual std::string getId() { return "nano"; }
virtual std::string getName() { return "Nano embark"; }
virtual std::string getDesc() { return "Allows the embark size to be decreased below 2x2"; }
virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_N; }
virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel)
{
for (auto iter = input->begin(); iter != input->end(); iter++)
{
df::interface_key key = *iter;
bool is_resize = true;
int dx = 0, dy = 0;
switch (key)
{
case df::interface_key::SETUP_LOCAL_Y_UP:
dy = 1;
break;
case df::interface_key::SETUP_LOCAL_Y_DOWN:
dy = -1;
break;
case df::interface_key::SETUP_LOCAL_X_UP:
dx = 1;
break;
case df::interface_key::SETUP_LOCAL_X_DOWN:
dx = -1;
break;
default:
is_resize = false;
}
if (is_resize)
{
cancel = true;
resize_embark(screen, dx, dy);
return;
}
}
};
};
class SandIndicator : public EmbarkTool class SandIndicator : public EmbarkTool
{ {
protected: protected:
@ -647,6 +576,8 @@ public:
max_y = min_y + height; max_y = min_y + height;
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y);
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1);
std::string title = " Embark tools (DFHack) ";
Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (max_x - min_x + title.size()) / 2, min_y, title);
x = min_x + 2; x = min_x + 2;
y = max_y - 2; y = max_y - 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT)); OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT));
@ -656,7 +587,7 @@ public:
y = min_y + 2; y = min_y + 2;
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* t = *iter; EmbarkTool* t = iter->second;
x = min_x + 2; x = min_x + 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey())); OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey()));
OutputString(COLOR_WHITE, x, y, ": " + t->getName() + ": "); OutputString(COLOR_WHITE, x, y, ": " + t->getName() + ": ");
@ -677,7 +608,7 @@ public:
df::interface_key key = *iter; df::interface_key key = *iter;
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* t = *iter; EmbarkTool* t = iter->second;
if (t->getToggleKey() == key) if (t->getToggleKey() == key)
{ {
t->toggleEnabled(); t->toggleEnabled();
@ -687,25 +618,20 @@ public:
}; };
}; };
bool tool_exists (std::string tool_name) void add_tool (EmbarkTool *t)
{ {
FOR_ITER_TOOLS(iter) tools[t->getId()] = t;
{
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return true;
} }
return false;
bool tool_exists (std::string tool_name)
{
return tools.find(tool_name) != tools.end();
} }
bool tool_enabled (std::string tool_name) bool tool_enabled (std::string tool_name)
{ {
FOR_ITER_TOOLS(iter) if (tool_exists(tool_name))
{ return tools[tool_name]->getEnabled();
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return tool->getEnabled();
}
return false; return false;
} }
@ -714,7 +640,7 @@ bool tool_enable (std::string tool_name, bool enable_state)
int n = 0; int n = 0;
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* tool = *iter; EmbarkTool* tool = iter->second;
if (tool->getId() == tool_name || tool_name == "all") if (tool->getId() == tool_name || tool_name == "all")
{ {
tool->setEnabled(enable_state); tool->setEnabled(enable_state);
@ -738,8 +664,9 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
std::vector<std::string> parts; std::vector<std::string> parts;
FOR_ITER_TOOLS(it) FOR_ITER_TOOLS(it)
{ {
if ((*it)->getEnabled()) EmbarkTool *t = it->second;
parts.push_back((*it)->getName()); if (t->getEnabled())
parts.push_back(t->getName());
} }
if (parts.size()) if (parts.size())
{ {
@ -770,7 +697,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
bool cancel = false; bool cancel = false;
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* tool = *iter; EmbarkTool* tool = iter->second;
if (tool->getEnabled()) if (tool->getEnabled())
tool->before_feed(this, input, cancel); tool->before_feed(this, input, cancel);
} }
@ -781,7 +708,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
display_settings(); display_settings();
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* tool = *iter; EmbarkTool* tool = iter->second;
if (tool->getEnabled()) if (tool->getEnabled())
tool->after_feed(this, input); tool->after_feed(this, input);
} }
@ -790,7 +717,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
{ {
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* tool = *iter; EmbarkTool* tool = iter->second;
if (tool->getEnabled()) if (tool->getEnabled())
tool->before_render(this); tool->before_render(this);
} }
@ -798,7 +725,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
display_tool_status(); display_tool_status();
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* tool = *iter; EmbarkTool* tool = iter->second;
if (tool->getEnabled()) if (tool->getEnabled())
tool->after_render(this); tool->after_render(this);
} }
@ -814,17 +741,16 @@ command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> &
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
tools.push_back(new EmbarkAnywhere); add_tool(new EmbarkAnywhere);
tools.push_back(new MouseControl); add_tool(new MouseControl);
tools.push_back(new NanoEmbark); add_tool(new SandIndicator);
tools.push_back(new SandIndicator); add_tool(new StablePosition);
tools.push_back(new StablePosition);
std::string help = ""; std::string help = "";
help += "embark-tools (enable/disable) tool [tool...]\n" help += "embark-tools (enable/disable) tool [tool...]\n"
"Tools:\n"; "Tools:\n";
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
help += (" " + (*iter)->getId() + ": " + (*iter)->getDesc() + "\n"); help += (" " + iter->second->getId() + ": " + iter->second->getDesc() + "\n");
} }
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"embark-tools", "embark-tools",
@ -855,6 +781,19 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
return CR_OK; return CR_OK;
} }
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_NOT_FOUND;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out) DFhackCExport command_result plugin_onupdate (color_ostream &out)
{ {
static int8_t mask = 0; static int8_t mask = 0;
@ -874,8 +813,8 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out)
{ {
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
if ((*iter)->getEnabled()) if (iter->second->getEnabled())
(*iter)->after_mouse_event(screen); iter->second->after_mouse_event(screen);
} }
} }
mask = new_mask; mask = new_mask;
@ -915,8 +854,8 @@ command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> &
out << "Tool status:" << std::endl; out << "Tool status:" << std::endl;
FOR_ITER_TOOLS(iter) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool* t = *iter; EmbarkTool* t = iter->second;
out << t->getName() << " (" << t->getId() << "): " out << " " << t->getName() << " (" << t->getId() << "): "
<< (t->getEnabled() ? "Enabled" : "Disabled") << std::endl; << (t->getEnabled() ? "Enabled" : "Disabled") << std::endl;
} }
} }

@ -0,0 +1,238 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include "modules/Translation.h"
#include "modules/World.h"
#include "df/map_block.h"
#include "df/unit.h"
#include "df/world.h"
using namespace DFHack;
DFHACK_PLUGIN("fix-unit-occupancy");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(cursor);
REQUIRE_GLOBAL(world);
static int run_interval = 1200; // daily
inline float getClock()
{
return (float)clock() / (float)CLOCKS_PER_SEC;
}
static std::string get_unit_description(df::unit *unit)
{
if (!unit)
return "";
std::string desc;
auto name = Units::getVisibleName(unit);
if (name->has_name)
desc = Translation::TranslateName(name, false);
desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too
return desc;
}
df::unit *findUnit(int x, int y, int z)
{
for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u)
{
if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z)
return *u;
}
return NULL;
}
struct uo_opts {
bool dry_run;
bool use_cursor;
uo_opts (bool dry_run = false, bool use_cursor = false)
:dry_run(dry_run), use_cursor(use_cursor)
{}
};
command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector <std::string> & parameters);
unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts)
{
if (!Core::getInstance().isWorldLoaded())
return 0;
if (opts.use_cursor && cursor->x < 0)
out.printerr("No cursor\n");
unsigned count = 0;
float time1 = getClock();
for (size_t i = 0; i < world->map.map_blocks.size(); i++)
{
df::map_block *block = world->map.map_blocks[i];
int map_z = block->map_pos.z;
if (opts.use_cursor && (map_z != cursor->z || block->map_pos.y != (cursor->y / 16) * 16 || block->map_pos.x != (cursor->x / 16) * 16))
continue;
for (int x = 0; x < 16; x++)
{
int map_x = x + block->map_pos.x;
for (int y = 0; y < 16; y++)
{
if (block->designation[x][y].bits.hidden)
continue;
int map_y = y + block->map_pos.y;
if (opts.use_cursor && (map_x != cursor->x || map_y != cursor->y))
continue;
bool cur_occupancy = block->occupancy[x][y].bits.unit;
bool fixed_occupancy = cur_occupancy;
df::unit *cur_unit = findUnit(map_x, map_y, map_z);
if (cur_occupancy && !cur_unit)
{
out.print("%sFixing occupancy at (%i, %i, %i) - no unit found\n",
opts.dry_run ? "(Dry run) " : "",
map_x, map_y, map_z);
fixed_occupancy = false;
}
// else if (!cur_occupancy && cur_unit)
// {
// out.printerr("Unit found at (%i, %i, %i): %s\n", map_x, map_y, map_z, get_unit_description(cur_unit).c_str());
// fixed_occupancy = true;
// }
if (cur_occupancy != fixed_occupancy && !opts.dry_run)
{
++count;
block->occupancy[x][y].bits.unit = fixed_occupancy;
}
}
}
}
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;
return count;
}
unsigned fix_unit_occupancy (color_ostream &out)
{
uo_opts tmp;
return fix_unit_occupancy(out, tmp);
}
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"fix-unit-occupancy",
"Fix unit occupancy issues such as phantom 'creature blocking site' messages (bug 3499)",
cmd_fix_unit_occupancy,
false, //allow non-interactive use
" enable fix-unit-occupancy: Enable the plugin\n"
" disable fix-unit-occupancy fix-unit-occupancy: Disable the plugin\n"
" fix-unit-occupancy: Run the plugin immediately. Available options:\n"
" -h|here|cursor: Only operate on the tile at the cursor\n"
" -n|dry|dry-run: Do not write changes to map\n"
" fix-unit-occupancy interval X: Run the plugin every X ticks (when enabled).\n"
" Default is 1200, or 1 day. Ticks are only counted when the game is unpaused.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
return CR_OK;
}
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
is_enabled = enable;
if (is_enabled)
fix_unit_occupancy(out);
return CR_OK;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out)
{
static unsigned tick = UINT_MAX;
static decltype(world->frame_counter) old_world_frame = 0;
if (is_enabled && World::isFortressMode())
{
// only increment tick when the world has changed
if (old_world_frame != world->frame_counter)
{
old_world_frame = world->frame_counter;
tick++;
}
if (tick > run_interval)
{
tick = 0;
fix_unit_occupancy(out);
}
}
else
{
tick = INT_MAX;
old_world_frame = 0;
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt)
{
if (evt == SC_MAP_LOADED && is_enabled && World::isFortressMode())
fix_unit_occupancy(out);
return CR_OK;
}
command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector <std::string> & parameters)
{
CoreSuspender suspend;
uo_opts opts;
bool ok = true;
if (parameters.size() >= 1 && (parameters[0] == "-i" || parameters[0].find("interval") != std::string::npos))
{
if (parameters.size() >= 2)
{
int new_interval = atoi(parameters[1].c_str());
if (new_interval < 100)
{
out.printerr("Invalid interval - minimum is 100 ticks\n");
return CR_WRONG_USAGE;
}
run_interval = new_interval;
if (!is_enabled)
out << "note: Plugin not enabled (use `enable fix-unit-occupancy` to enable)" << endl;
return CR_OK;
}
else
return CR_WRONG_USAGE;
}
for (auto opt = parameters.begin(); opt != parameters.end(); ++opt)
{
if (*opt == "-n" || opt->find("dry") != std::string::npos)
opts.dry_run = true;
else if (*opt == "-h" || opt->find("cursor") != std::string::npos || opt->find("here") != std::string::npos)
opts.use_cursor = true;
else if (opt->find("enable") != std::string::npos)
plugin_enable(out, true);
else if (opt->find("disable") != std::string::npos)
plugin_enable(out, false);
else
{
out.printerr("Unknown parameter: %s\n", opt->c_str());
ok = false;
}
}
if (!ok)
return CR_WRONG_USAGE;
unsigned count = fix_unit_occupancy(out, opts);
if (!count)
out << "No occupancy issues found." << endl;
return CR_OK;
}

@ -1,4 +1,5 @@
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/ui.h" #include "df/ui.h"

@ -0,0 +1,481 @@
#include "uicommon.h"
using df::global::enabler;
using df::global::gps;
/*
* List classes
*/
template <typename T>
class ListEntry
{
public:
T elem;
string text, keywords;
bool selected;
UIColor color;
ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) :
elem(elem), text(text), selected(false), keywords(keywords), color(color)
{
}
};
template <typename T>
class ListColumn
{
public:
int highlighted_index;
int display_start_offset;
unsigned short text_clip_at;
int32_t bottom_margin, search_margin, left_margin;
bool multiselect;
bool allow_null;
bool auto_select;
bool allow_search;
bool feed_mouse_set_highlight;
bool feed_changed_highlight;
ListColumn()
{
bottom_margin = 3;
clear();
left_margin = 2;
search_margin = 63;
highlighted_index = 0;
text_clip_at = 0;
multiselect = false;
allow_null = true;
auto_select = false;
allow_search = true;
feed_mouse_set_highlight = false;
feed_changed_highlight = false;
}
void clear()
{
list.clear();
display_list.clear();
display_start_offset = 0;
if (highlighted_index != -1)
highlighted_index = 0;
max_item_width = title.length();
resize();
}
void resize()
{
display_max_rows = gps->dimy - 4 - bottom_margin;
}
void add(ListEntry<T> &entry)
{
list.push_back(entry);
if (entry.text.length() > max_item_width)
max_item_width = entry.text.length();
}
void add(const string &text, const T &elem)
{
list.push_back(ListEntry<T>(text, elem));
if (text.length() > max_item_width)
max_item_width = text.length();
}
int fixWidth()
{
if (text_clip_at > 0 && max_item_width > text_clip_at)
max_item_width = text_clip_at;
for (auto it = list.begin(); it != list.end(); it++)
{
it->text = pad_string(it->text, max_item_width, false);
}
return getMaxItemWidth();
}
int getMaxItemWidth()
{
return left_margin + max_item_width;
}
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
void display(const bool is_selected_column) const
{
int32_t y = 2;
paint_text(COLOR_TITLE, left_margin, y, title);
int last_index_able_to_display = display_start_offset + display_max_rows;
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
{
++y;
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color;
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
string item_label = display_list[i]->text;
if (text_clip_at > 0 && item_label.length() > text_clip_at)
item_label.resize(text_clip_at);
paint_text(fg_color, left_margin, y, item_label, bg_color);
int x = left_margin + display_list[i]->text.length() + 1;
display_extras(display_list[i]->elem, x, y);
}
if (is_selected_column && allow_search)
{
y = gps->dimy - 3;
int32_t x = search_margin;
OutputHotkeyString(x, y, "Search" ,"S");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, search_string);
OutputString(COLOR_LIGHTGREEN, x, y, "_");
}
}
virtual void tokenizeSearch (vector<string> *dest, const string search)
{
if (!search.empty())
split_string(dest, search, " ");
}
virtual bool showEntry(const ListEntry<T> *entry, const vector<string> &search_tokens)
{
if (!search_tokens.empty())
{
string item_string = toLower(entry->text);
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
{
if (!si->empty() && item_string.find(*si) == string::npos &&
entry->keywords.find(*si) == string::npos)
{
return false;
}
}
}
return true;
}
void filterDisplay()
{
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
display_list.clear();
search_string = toLower(search_string);
vector<string> search_tokens;
tokenizeSearch(&search_tokens, search_string);
for (size_t i = 0; i < list.size(); i++)
{
ListEntry<T> *entry = &list[i];
if (showEntry(entry, search_tokens))
{
display_list.push_back(entry);
if (entry == prev_selected)
highlighted_index = display_list.size() - 1;
}
else if (auto_select)
{
entry->selected = false;
}
}
changeHighlight(0);
feed_changed_highlight = true;
}
void selectDefaultEntry()
{
for (size_t i = 0; i < display_list.size(); i++)
{
if (display_list[i]->selected)
{
highlighted_index = i;
break;
}
}
}
void centerSelection()
{
if (display_list.size() == 0)
return;
display_start_offset = highlighted_index - (display_max_rows / 2);
validateDisplayOffset();
validateHighlight();
}
void validateHighlight()
{
set_to_limit(highlighted_index, display_list.size() - 1);
if (highlighted_index < display_start_offset)
display_start_offset = highlighted_index;
else if (highlighted_index >= display_start_offset + display_max_rows)
display_start_offset = highlighted_index - display_max_rows + 1;
if (auto_select || (!allow_null && list.size() == 1))
display_list[highlighted_index]->selected = true;
feed_changed_highlight = true;
}
void changeHighlight(const int highlight_change, const int offset_shift = 0)
{
if (!initHighlightChange())
return;
highlighted_index += highlight_change + offset_shift * display_max_rows;
display_start_offset += offset_shift * display_max_rows;
validateDisplayOffset();
validateHighlight();
}
void validateDisplayOffset()
{
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
}
void setHighlight(const int index)
{
if (!initHighlightChange())
return;
highlighted_index = index;
validateHighlight();
}
bool initHighlightChange()
{
if (display_list.size() == 0)
return false;
if (auto_select && !multiselect)
{
for (auto it = list.begin(); it != list.end(); it++)
{
it->selected = false;
}
}
return true;
}
void toggleHighlighted()
{
if (display_list.size() == 0)
return;
if (auto_select)
return;
ListEntry<T> *entry = display_list[highlighted_index];
if (!multiselect || !allow_null)
{
int selected_count = 0;
for (size_t i = 0; i < list.size(); i++)
{
if (!multiselect && !entry->selected)
list[i].selected = false;
if (!allow_null && list[i].selected)
selected_count++;
}
if (!allow_null && entry->selected && selected_count == 1)
return;
}
entry->selected = !entry->selected;
}
vector<T> getSelectedElems(bool only_one = false)
{
vector<T> results;
for (auto it = list.begin(); it != list.end(); it++)
{
if ((*it).selected)
{
results.push_back(it->elem);
if (only_one)
break;
}
}
return results;
}
T getFirstSelectedElem()
{
vector<T> results = getSelectedElems(true);
if (results.size() == 0)
return (T)nullptr;
else
return results[0];
}
void clearSelection()
{
for_each_(list, clear_fn);
}
void selectItem(const T elem)
{
int i = 0;
for (; i < display_list.size(); i++)
{
if (display_list[i]->elem == elem)
{
setHighlight(i);
break;
}
}
}
void clearSearch()
{
search_string.clear();
filterDisplay();
}
size_t getDisplayListSize()
{
return display_list.size();
}
vector<ListEntry<T>*> &getDisplayList()
{
return display_list;
}
size_t getBaseListSize()
{
return list.size();
}
virtual bool validSearchInput (unsigned char c)
{
return (c >= 'a' && c <= 'z') || c == ' ';
}
bool feed(set<df::interface_key> *input)
{
feed_mouse_set_highlight = feed_changed_highlight = false;
if (input->count(interface_key::STANDARDSCROLL_UP))
{
changeHighlight(-1);
}
else if (input->count(interface_key::STANDARDSCROLL_DOWN))
{
changeHighlight(1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
{
changeHighlight(0, -1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
{
changeHighlight(0, 1);
}
else if (input->count(interface_key::SELECT) && !auto_select)
{
toggleHighlighted();
}
else if (input->count(interface_key::CUSTOM_SHIFT_S))
{
clearSearch();
}
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
{
return setHighlightByMouse();
}
else if (allow_search)
{
// Search query typing mode always on
df::interface_key last_token = get_string_key(input);
int charcode = Screen::keyToChar(last_token);
if (charcode >= 0 && validSearchInput((unsigned char)charcode))
{
// Standard character
search_string += char(charcode);
filterDisplay();
centerSelection();
}
else if (last_token == interface_key::STRING_A000)
{
// Backspace
if (search_string.length() > 0)
{
search_string.erase(search_string.length()-1);
filterDisplay();
centerSelection();
}
}
else
{
return false;
}
return true;
}
else
{
return false;
}
return true;
}
bool setHighlightByMouse()
{
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
{
int new_index = display_start_offset + gps->mouse_y - 3;
if (new_index < display_list.size())
{
setHighlight(new_index);
feed_mouse_set_highlight = true;
}
enabler->mouse_lbut = enabler->mouse_rbut = 0;
return true;
}
return false;
}
void sort(bool force_sort = false)
{
if (force_sort || list.size() < 100)
std::sort(list.begin(), list.end(), sort_fn);
filterDisplay();
}
void setTitle(const string t)
{
title = t;
if (title.length() > max_item_width)
max_item_width = title.length();
}
size_t getDisplayedListSize()
{
return display_list.size();
}
protected:
static void clear_fn(ListEntry<T> &e) { e.selected = false; }
static bool sort_fn(ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; }
vector<ListEntry<T>> list;
vector<ListEntry<T>*> display_list;
string search_string;
string title;
int display_max_rows;
int max_item_width;
};

@ -96,6 +96,48 @@ function tablify(iterableObject)
return t return t
end end
local filename_invalid_regex = '[^A-Za-z0-9 ._-]'
function valid_filename(filename)
return not filename:match(filename_invalid_regex)
end
function sanitize_filename(filename)
local ret = ''
for i = 1, #filename do
local ch = filename:sub(i, i)
if valid_filename(ch) then
ret = ret .. ch
else
ret = ret .. '-'
end
end
return ret
end
FilenameInputBox = defclass(FilenameInputBox, dlg.InputBox)
function FilenameInputBox:onInput(keys)
if not valid_filename(string.char(keys._STRING or 0)) and not keys.STRING_A000 then
keys._STRING = nil
end
FilenameInputBox.super.onInput(self, keys)
end
function showFilenameInputPrompt(title, text, tcolor, input, min_width)
FilenameInputBox{
frame_title = title,
text = text,
text_pen = tcolor,
input = input,
frame_width = min_width,
on_input = script.mkresume(true),
on_cancel = script.mkresume(false),
on_close = script.qresume(nil)
}:show()
return script.wait()
end
function load_settings() function load_settings()
init() init()
local path = get_path() local path = get_path()
@ -132,16 +174,16 @@ function save_settings(stockpile)
if #suggested == 0 then if #suggested == 0 then
suggested = 'Stock1' suggested = 'Stock1'
end end
suggested = sanitize_filename(suggested)
local path = get_path() local path = get_path()
local sok,filename = script.showInputPrompt('Stockpile Settings', 'Enter stockpile name', COLOR_WHITE, suggested) local sok,filename = showFilenameInputPrompt('Stockpile Settings', 'Enter filename', COLOR_WHITE, suggested)
if sok then if sok then
if filename == nil or filename == '' then if filename == nil or filename == '' or not valid_filename(filename) then
script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED) script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED)
else else
if not dfhack.filesystem.exists(path) then if not dfhack.filesystem.exists(path) then
dfhack.filesystem.mkdir(path) dfhack.filesystem.mkdir(path)
end end
print("saving...", path..'/'..filename)
stockpiles_save(path..'/'..filename) stockpiles_save(path..'/'..filename)
end end
end end

@ -33,6 +33,7 @@
#include "df/entity_raw.h" #include "df/entity_raw.h"
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
using std::stringstream; using std::stringstream;
using std::set; using std::set;
@ -997,6 +998,8 @@ public:
} }
void select_profession(size_t selected) void select_profession(size_t selected)
{ {
if (selected >= manager.templates.size())
return;
ProfessionTemplate prof = manager.templates[selected - 1]; ProfessionTemplate prof = manager.templates[selected - 1];
for (auto it = units.begin(); it != units.end(); ++it) for (auto it = units.begin(); it != units.end(); ++it)
@ -1012,6 +1015,11 @@ public:
Screen::clear(); Screen::clear();
int x = 2, y = 2; int x = 2, y = 2;
Screen::drawBorder(" Dwarf Manipulator - Custom Profession "); Screen::drawBorder(" Dwarf Manipulator - Custom Profession ");
if (!manager.templates.size())
{
OutputString(COLOR_LIGHTRED, x, y, "No saved professions");
return;
}
if (selection_empty) if (selection_empty)
{ {
OutputString(COLOR_LIGHTRED, x, y, "No dwarves selected!"); OutputString(COLOR_LIGHTRED, x, y, "No dwarves selected!");

@ -22,6 +22,8 @@
#include "DataFuncs.h" #include "DataFuncs.h"
DFHACK_PLUGIN("mousequery"); DFHACK_PLUGIN("mousequery");
REQUIRE_GLOBAL(enabler);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(ui_build_selector);
@ -815,9 +817,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{ {
if (!gps)
return CR_FAILURE;
if (is_enabled != enable) if (is_enabled != enable)
{ {
last_clicked_x = last_clicked_y = last_clicked_z = -1; last_clicked_x = last_clicked_y = last_clicked_z = -1;

@ -948,6 +948,8 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in,
static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out) static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out)
{ {
if (!Maps::IsValid())
return CR_FAILURE;
uint32_t size_x, size_y, size_z; uint32_t size_x, size_y, size_z;
int32_t pos_x, pos_y, pos_z; int32_t pos_x, pos_y, pos_z;
Maps::getSize(size_x, size_y, size_z); Maps::getSize(size_x, size_y, size_z);

@ -5,6 +5,8 @@
#include <VTableInterpose.h> #include <VTableInterpose.h>
#include "uicommon.h"
#include "df/ui_look_list.h" #include "df/ui_look_list.h"
#include "df/viewscreen_announcelistst.h" #include "df/viewscreen_announcelistst.h"
#include "df/viewscreen_petst.h" #include "df/viewscreen_petst.h"
@ -65,12 +67,6 @@ to use.
*/ */
void OutputString(int8_t color, int &x, int y, const std::string &text)
{
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
x += text.length();
}
void make_text_dim(int x1, int x2, int y) void make_text_dim(int x1, int x2, int y)
{ {
for (int x = x1; x <= x2; x++) for (int x = x1; x <= x2; x++)
@ -225,11 +221,7 @@ public:
{ {
// Query typing mode // Query typing mode
if (input->empty()) df::interface_key last_token = get_string_key(input);
{
return false;
}
df::interface_key last_token = *input->rbegin();
int charcode = Screen::keyToChar(last_token); int charcode = Screen::keyToChar(last_token);
if (charcode >= 32 && charcode <= 126) if (charcode >= 32 && charcode <= 126)
{ {

@ -22,12 +22,9 @@ using df::building_stockpilest;
DFHACK_PLUGIN("stockflow"); DFHACK_PLUGIN("stockflow");
#define AUTOENABLE false #define AUTOENABLE false
#ifdef DFHACK_PLUGIN_IS_ENABLED
DFHACK_PLUGIN_IS_ENABLED(enabled); DFHACK_PLUGIN_IS_ENABLED(enabled);
#else
bool enabled = false;
#endif
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);

@ -40,6 +40,7 @@ using namespace google::protobuf;
using namespace dfstockpiles; using namespace dfstockpiles;
DFHACK_PLUGIN ( "stockpiles" ); DFHACK_PLUGIN ( "stockpiles" );
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(selection_rect); REQUIRE_GLOBAL(selection_rect);
@ -245,7 +246,7 @@ static command_result savestock ( color_ostream &out, vector <string> & paramete
if ( !is_dfstockfile ( file ) ) file += ".dfstock"; if ( !is_dfstockfile ( file ) ) file += ".dfstock";
if ( !cereal.serialize_to_file ( file ) ) if ( !cereal.serialize_to_file ( file ) )
{ {
out.printerr ( "serialize failed\n" ); out.printerr ( "could not save to %s\n", file.c_str() );
return CR_FAILURE; return CR_FAILURE;
} }
return CR_OK; return CR_OK;
@ -509,11 +510,9 @@ static int stockpiles_list_settings ( lua_State *L )
static void stockpiles_load ( color_ostream &out, std::string filename ) static void stockpiles_load ( color_ostream &out, std::string filename )
{ {
out << "stockpiles_load " << filename << " ";
std::vector<std::string> params; std::vector<std::string> params;
params.push_back ( filename ); params.push_back ( filename );
command_result r = loadstock ( out, params ); command_result r = loadstock ( out, params );
out << " result = "<< r << endl;
if ( r != CR_OK ) if ( r != CR_OK )
show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true ); show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true );
} }
@ -521,11 +520,9 @@ static void stockpiles_load ( color_ostream &out, std::string filename )
static void stockpiles_save ( color_ostream &out, std::string filename ) static void stockpiles_save ( color_ostream &out, std::string filename )
{ {
out << "stockpiles_save " << filename << " ";
std::vector<std::string> params; std::vector<std::string> params;
params.push_back ( filename ); params.push_back ( filename );
command_result r = savestock ( out, params ); command_result r = savestock ( out, params );
out << " result = "<< r << endl;
if ( r != CR_OK ) if ( r != CR_OK )
show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true ); show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true );
} }

@ -1,4 +1,5 @@
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
#include <functional> #include <functional>
@ -598,8 +599,115 @@ class StockListColumn : public ListColumn<T>
OutputString(color, x, y, get_quality_name(quality)); OutputString(color, x, y, get_quality_name(quality));
} }
} }
virtual bool validSearchInput (unsigned char c)
{
switch (c)
{
case '(':
case ')':
return true;
break;
default:
break;
}
string &search_string = ListColumn<T>::search_string;
if (c == '^' && !search_string.size())
return true;
else if (c == '$' && search_string.size())
{
if (search_string == "^")
return false;
if (search_string[search_string.size() - 1] != '$')
return true;
}
return ListColumn<T>::validSearchInput(c);
}
std::string getRawSearch(const std::string s)
{
string raw_search = s;
if (raw_search.size() && raw_search[0] == '^')
raw_search.erase(0, 1);
if (raw_search.size() && raw_search[raw_search.size() - 1] == '$')
raw_search.erase(raw_search.size() - 1, 1);
return toLower(raw_search);
}
virtual void tokenizeSearch (vector<string> *dest, const string search)
{
string raw_search = getRawSearch(search);
ListColumn<T>::tokenizeSearch(dest, raw_search);
}
virtual bool showEntry (const ListEntry<T> *entry, const vector<string> &search_tokens)
{
string &search_string = ListColumn<T>::search_string;
if (!search_string.size())
return true;
bool match_start = false, match_end = false;
string raw_search = getRawSearch(search_string);
if (search_string.size() && search_string[0] == '^')
match_start = true;
if (search_string.size() && search_string[search_string.size() - 1] == '$')
match_end = true;
if (!ListColumn<T>::showEntry(entry, search_tokens))
return false;
string item_name = toLower(Items::getDescription(entry->elem->entries[0], 0, false));
if ((match_start || match_end) && raw_search.size() > item_name.size())
return false;
if (match_start && item_name.compare(0, raw_search.size(), raw_search) != 0)
return false;
if (match_end && item_name.compare(item_name.size() - raw_search.size(), raw_search.size(), raw_search) != 0)
return false;
return true;
}
}; };
class search_help : public dfhack_viewscreen
{
public:
void feed (std::set<df::interface_key> *input)
{
if (input->count(interface_key::HELP))
return;
if (Screen::isDismissed(this))
return;
Screen::dismiss(this);
if (!input->count(interface_key::LEAVESCREEN) && !input->count(interface_key::SELECT))
parent->feed(input);
}
void render()
{
static std::string text =
"\7 Flag names can be\n"
" searched for - e.g. job,\n"
" inventory, dump, forbid\n"
"\n"
"\7 Use ^ to match the start\n"
" of a name, and/or $ to\n"
" match the end of a name";
if (Screen::isDismissed(this))
return;
parent->render();
int left_margin = gps->dimx - SIDEBAR_WIDTH;
int x = left_margin, y = 2;
Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, gps->dimx - 2, gps->dimy - 4);
Screen::fillRect(Screen::Pen(' ', 0, 0), left_margin - 1, 1, left_margin - 1, gps->dimy - 2);
OutputString(COLOR_WHITE, x, y, "Search help", true, left_margin);
++y;
vector<string> lines;
split_string(&lines, text, "\n");
for (auto line = lines.begin(); line != lines.end(); ++line)
OutputString(COLOR_WHITE, x, y, line->c_str(), true, left_margin);
}
std::string getFocusString() { return "stocks_view/search_help"; }
};
class ViewscreenStocks : public dfhack_viewscreen class ViewscreenStocks : public dfhack_viewscreen
{ {
@ -661,6 +769,10 @@ public:
Screen::dismiss(this); Screen::dismiss(this);
return; return;
} }
else if (input->count(interface_key::HELP))
{
Screen::show(new search_help);
}
bool key_processed = false; bool key_processed = false;
switch (selected_column) switch (selected_column)
@ -698,7 +810,7 @@ public:
hide_flags.bits.dump = !hide_flags.bits.dump; hide_flags.bits.dump = !hide_flags.bits.dump;
populateItems(); populateItems();
} }
else if (input->count(interface_key::CUSTOM_CTRL_R)) else if (input->count(interface_key::CUSTOM_CTRL_E))
{ {
hide_flags.bits.on_fire = !hide_flags.bits.on_fire; hide_flags.bits.on_fire = !hide_flags.bits.on_fire;
populateItems(); populateItems();
@ -801,6 +913,12 @@ public:
return; return;
Screen::dismiss(this); Screen::dismiss(this);
auto vs = Gui::getCurViewscreen(true);
while (vs && !virtual_cast<df::viewscreen_dwarfmodest>(vs))
{
Screen::dismiss(vs);
vs = vs->parent;
}
// Could be clever here, if item is in a container, to look inside the container. // Could be clever here, if item is in a container, to look inside the container.
// But that's different for built containers vs bags/pots in stockpiles. // But that's different for built containers vs bags/pots in stockpiles.
send_key(interface_key::D_LOOK); send_key(interface_key::D_LOOK);
@ -897,49 +1015,49 @@ public:
y = 2; y = 2;
x = left_margin; x = left_margin;
OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); OutputString(COLOR_BROWN, x, y, "Filters ", false, left_margin);
OutputString(COLOR_LIGHTRED, x, y, "Press Ctrl-Hotkey to toggle", true, left_margin); OutputString(COLOR_LIGHTRED, x, y, "(Ctrl+Key toggles)", true, left_margin);
OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); OutputFilterString(x, y, "In Job ", "J", !hide_flags.bits.in_job, false, left_margin, COLOR_LIGHTBLUE);
OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN);
OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, left_margin, COLOR_GREEN); OutputFilterString(x, y, "Owned ", "O", !hide_flags.bits.owned, false, left_margin, COLOR_GREEN);
OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED); OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED);
OutputFilterString(x, y, "Dump", "D", !hide_flags.bits.dump, true, left_margin, COLOR_LIGHTMAGENTA); OutputFilterString(x, y, "Dump ", "D", !hide_flags.bits.dump, false, left_margin, COLOR_LIGHTMAGENTA);
OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); OutputFilterString(x, y, "On Fire", "E", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED);
OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE); OutputFilterString(x, y, "Melt ", "M", !hide_flags.bits.melt, false, left_margin, COLOR_BLUE);
OutputFilterString(x, y, "In Inventory", "I", !extra_hide_flags.hide_in_inventory, true, left_margin, COLOR_WHITE); OutputFilterString(x, y, "In Inventory", "I", !extra_hide_flags.hide_in_inventory, true, left_margin, COLOR_WHITE);
OutputFilterString(x, y, "Caged", "C", !extra_hide_flags.hide_in_cages, true, left_margin, COLOR_LIGHTRED); OutputFilterString(x, y, "Caged ", "C", !extra_hide_flags.hide_in_cages, false, left_margin, COLOR_LIGHTRED);
OutputFilterString(x, y, "Trade", "T", !extra_hide_flags.hide_trade_marked, true, left_margin, COLOR_LIGHTGREEN); OutputFilterString(x, y, "Trade", "T", !extra_hide_flags.hide_trade_marked, true, left_margin, COLOR_LIGHTGREEN);
OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY); OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY);
if (gps->dimy > 26)
++y; ++y;
OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin);
OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin);
OutputHotkeyString(x, y, "Toggle Grouping", "TAB", true, left_margin); OutputHotkeyString(x, y, "Toggle Grouping", interface_key::CHANGETAB, true, left_margin);
++y; ++y;
OutputHotkeyString(x, y, "Min Qual: ", "-+"); OutputHotkeyString(x, y, "Min Qual: ", "-+");
OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin); OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin);
OutputHotkeyString(x, y, "Max Qual: ", "/*"); OutputHotkeyString(x, y, "Max Qual: ", "/*");
OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin);
++y;
OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); OutputHotkeyString(x, y, "Min Wear: ", "Shift-W");
OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin);
if (gps->dimy > 27)
++y; ++y;
OutputString(COLOR_BROWN, x, y, "Actions ("); OutputString(COLOR_BROWN, x, y, "Actions (");
OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize()));
OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin); OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin);
OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin); OutputHotkeyString(x, y, "Zoom ", "Shift-Z", false, left_margin);
OutputHotkeyString(x, y, "Dump", "-D", true, left_margin);
OutputHotkeyString(x, y, "Forbid ", "Shift-F", false, left_margin);
OutputHotkeyString(x, y, "Melt", "-M", true, left_margin);
OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin,
depot_info.canTrade() ? COLOR_WHITE : COLOR_DARKGREY);
OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); OutputHotkeyString(x, y, "Apply to: ", "Shift-A");
OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin);
OutputHotkeyString(x, y, "Dump", "Shift-D", true, left_margin);
OutputHotkeyString(x, y, "Forbid", "Shift-F", true, left_margin);
OutputHotkeyString(x, y, "Melt", "Shift-M", true, left_margin);
if (depot_info.canTrade())
OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin);
y = gps->dimy - 6; y = gps->dimy - 4;
OutputString(COLOR_LIGHTRED, x, y, "Flag names can also", true, left_margin); OutputHotkeyString(x, y, "Search help", interface_key::HELP, true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, "be searched for", true, left_margin);
} }
std::string getFocusString() { return "stocks_view"; } std::string getFocusString() { return "stocks_view"; }
@ -1307,7 +1425,6 @@ struct stocks_hook : public df::viewscreen_storesst
if (input->count(interface_key::CUSTOM_E)) if (input->count(interface_key::CUSTOM_E))
{ {
Screen::dismiss(this); Screen::dismiss(this);
Screen::dismiss(Gui::getCurViewscreen(true));
Screen::show(new ViewscreenStocks()); Screen::show(new ViewscreenStocks());
return; return;
} }
@ -1320,7 +1437,7 @@ struct stocks_hook : public df::viewscreen_storesst
auto dim = Screen::getWindowSize(); auto dim = Screen::getWindowSize();
int x = 40; int x = 40;
int y = dim.y - 2; int y = dim.y - 2;
OutputHotkeyString(x, y, "Enhanced View", "e"); OutputHotkeyString(x, y, "Enhanced View", "e", false, 0, COLOR_WHITE, COLOR_LIGHTRED);
} }
}; };
@ -1440,6 +1557,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
case SC_MAP_LOADED: case SC_MAP_LOADED:
ViewscreenStocks::reset(); ViewscreenStocks::reset();
break; break;
case SC_BEGIN_UNLOAD:
if (Gui::getCurFocus().find("dfhack/stocks") == 0)
return CR_FAILURE;
break;
default: default:
break; break;
} }

@ -0,0 +1,66 @@
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "VTableInterpose.h"
#include "DFHackVersion.h"
#include "df/graphic.h"
#include "df/viewscreen_titlest.h"
#include "uicommon.h"
using std::vector;
using std::string;
using namespace DFHack;
DFHACK_PLUGIN("title-version");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
struct title_version_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
int x = 0, y = 0;
OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render);
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init (color_ostream &out, vector<PluginCommand> &commands)
{
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
INTERPOSE_HOOK(title_version_hook, render).remove();
return CR_OK;
}

@ -23,9 +23,6 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
{ {
// Adapted from autofarm // Adapted from autofarm
using namespace df::enums::plant_raw_flags; using namespace df::enums::plant_raw_flags;
// Discovered?
if (ui->tasks.discovered_plants[crop_id])
{
// Possible to plant? // Possible to plant?
df::plant_raw* raws = world->raws.plants.all[crop_id]; df::plant_raw* raws = world->raws.plants.all[crop_id];
if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season)) if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season))
@ -44,7 +41,6 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
return true; return true;
} }
} }
}
return false; return false;
} }
@ -53,9 +49,9 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input)) DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{ {
df::building_farmplotst* farm_plot = getFarmPlot(); df::building_farmplotst* farm_plot = getFarmPlot();
if (farm_plot) if (farm_plot && ui->selected_farm_crops.size() > 0)
{ {
if (input->count(interface_key::SELECT_ALL) && ui->selected_farm_crops.size() > 0) if (input->count(interface_key::SELECT_ALL))
{ {
int32_t crop_id = getSelectedCropId(); int32_t crop_id = getSelectedCropId();
for (int season = 0; season < 4; season++) for (int season = 0; season < 4; season++)
@ -80,7 +76,10 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
DEFINE_VMETHOD_INTERPOSE(void, render, ()) DEFINE_VMETHOD_INTERPOSE(void, render, ())
{ {
INTERPOSE_NEXT(render)(); INTERPOSE_NEXT(render)();
if (!getFarmPlot()) auto farm_plot = getFarmPlot();
if (!farm_plot || !ui->selected_farm_crops.size())
return;
if (farm_plot->getBuildStage() != farm_plot->getMaxBuildStage())
return; return;
auto dims = Gui::getDwarfmodeViewDims(); auto dims = Gui::getDwarfmodeViewDims();
int x = dims.menu_x1 + 1, int x = dims.menu_x1 + 1,

@ -37,10 +37,6 @@ using std::set;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::enabler;
using df::global::gps;
#ifndef HAVE_NULLPTR #ifndef HAVE_NULLPTR
#define nullptr 0L #define nullptr 0L
#endif #endif
@ -116,6 +112,12 @@ static void OutputHotkeyString(int &x, int &y, const char *text, const char *hot
OutputString(text_color, x, y, display, newline, left_margin); OutputString(text_color, x, y, display, newline, left_margin);
} }
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)
{
OutputHotkeyString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), newline, left_margin, text_color, hotkey_color);
}
static void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, 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)
{ {
@ -146,6 +148,12 @@ static void OutputToggleString(int &x, int &y, const char *text, const char *hot
OutputString(COLOR_GREY, x, y, "Off", newline, left_margin); OutputString(COLOR_GREY, x, y, "Off", newline, left_margin);
} }
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)
{
OutputToggleString(x, y, text, DFHack::Screen::getKeyDisplay(hotkey).c_str(), state, newline, left_margin, color, hotkey_color);
}
inline string int_to_string(const int n) inline string int_to_string(const int n)
{ {
return static_cast<ostringstream*>( &(ostringstream() << n) )->str(); return static_cast<ostringstream*>( &(ostringstream() << n) )->str();
@ -206,6 +214,20 @@ static string pad_string(string text, const int size, const bool front = true, c
} }
} }
static df::interface_key get_string_key(const std::set<df::interface_key> *input)
{
for (auto it = input->begin(); it != input->end(); ++it)
{
if (DFHack::Screen::keyToChar(*it) >= 0)
return *it;
}
return df::interface_key::NONE;
}
static char get_string_input(const std::set<df::interface_key> *input)
{
return DFHack::Screen::keyToChar(get_string_key(input));
}
/* /*
* Utility Functions * Utility Functions
@ -370,7 +392,7 @@ protected:
y2 = sp->room.y + sp->room.height; y2 = sp->room.y + sp->room.height;
} }
private: protected:
int x1, x2, y1, y2, z; int x1, x2, y1, y2, z;
}; };
@ -414,475 +436,7 @@ public:
DFHack::World::DeletePersistentData(config); DFHack::World::DeletePersistentData(config);
} }
private: protected:
PersistentDataItem config; PersistentDataItem config;
string persistence_key; string persistence_key;
}; };
/*
* List classes
*/
template <typename T>
class ListEntry
{
public:
T elem;
string text, keywords;
bool selected;
UIColor color;
ListEntry(const string text, const T elem, const string keywords = "", const UIColor color = COLOR_UNSELECTED) :
elem(elem), text(text), selected(false), keywords(keywords), color(color)
{
}
};
template <typename T>
class ListColumn
{
public:
int highlighted_index;
int display_start_offset;
unsigned short text_clip_at;
int32_t bottom_margin, search_margin, left_margin;
bool multiselect;
bool allow_null;
bool auto_select;
bool allow_search;
bool feed_mouse_set_highlight;
bool feed_changed_highlight;
ListColumn()
{
bottom_margin = 3;
clear();
left_margin = 2;
search_margin = 63;
highlighted_index = 0;
text_clip_at = 0;
multiselect = false;
allow_null = true;
auto_select = false;
allow_search = true;
feed_mouse_set_highlight = false;
feed_changed_highlight = false;
}
void clear()
{
list.clear();
display_list.clear();
display_start_offset = 0;
if (highlighted_index != -1)
highlighted_index = 0;
max_item_width = title.length();
resize();
}
void resize()
{
display_max_rows = gps->dimy - 4 - bottom_margin;
}
void add(ListEntry<T> &entry)
{
list.push_back(entry);
if (entry.text.length() > max_item_width)
max_item_width = entry.text.length();
}
void add(const string &text, const T &elem)
{
list.push_back(ListEntry<T>(text, elem));
if (text.length() > max_item_width)
max_item_width = text.length();
}
int fixWidth()
{
if (text_clip_at > 0 && max_item_width > text_clip_at)
max_item_width = text_clip_at;
for (auto it = list.begin(); it != list.end(); it++)
{
it->text = pad_string(it->text, max_item_width, false);
}
return getMaxItemWidth();
}
int getMaxItemWidth()
{
return left_margin + max_item_width;
}
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
void display(const bool is_selected_column) const
{
int32_t y = 2;
paint_text(COLOR_TITLE, left_margin, y, title);
int last_index_able_to_display = display_start_offset + display_max_rows;
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
{
++y;
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color;
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
string item_label = display_list[i]->text;
if (text_clip_at > 0 && item_label.length() > text_clip_at)
item_label.resize(text_clip_at);
paint_text(fg_color, left_margin, y, item_label, bg_color);
int x = left_margin + display_list[i]->text.length() + 1;
display_extras(display_list[i]->elem, x, y);
}
if (is_selected_column && allow_search)
{
y = gps->dimy - 3;
int32_t x = search_margin;
OutputHotkeyString(x, y, "Search" ,"S");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, search_string);
OutputString(COLOR_LIGHTGREEN, x, y, "_");
}
}
void filterDisplay()
{
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
display_list.clear();
search_string = toLower(search_string);
vector<string> search_tokens;
if (!search_string.empty())
split_string(&search_tokens, search_string, " ");
for (size_t i = 0; i < list.size(); i++)
{
ListEntry<T> *entry = &list[i];
bool include_item = true;
if (!search_string.empty())
{
string item_string = toLower(list[i].text);
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
{
if (!si->empty() && item_string.find(*si) == string::npos &&
list[i].keywords.find(*si) == string::npos)
{
include_item = false;
break;
}
}
}
if (include_item)
{
display_list.push_back(entry);
if (entry == prev_selected)
highlighted_index = display_list.size() - 1;
}
else if (auto_select)
{
entry->selected = false;
}
}
changeHighlight(0);
feed_changed_highlight = true;
}
void selectDefaultEntry()
{
for (size_t i = 0; i < display_list.size(); i++)
{
if (display_list[i]->selected)
{
highlighted_index = i;
break;
}
}
}
void centerSelection()
{
if (display_list.size() == 0)
return;
display_start_offset = highlighted_index - (display_max_rows / 2);
validateDisplayOffset();
validateHighlight();
}
void validateHighlight()
{
set_to_limit(highlighted_index, display_list.size() - 1);
if (highlighted_index < display_start_offset)
display_start_offset = highlighted_index;
else if (highlighted_index >= display_start_offset + display_max_rows)
display_start_offset = highlighted_index - display_max_rows + 1;
if (auto_select || (!allow_null && list.size() == 1))
display_list[highlighted_index]->selected = true;
feed_changed_highlight = true;
}
void changeHighlight(const int highlight_change, const int offset_shift = 0)
{
if (!initHighlightChange())
return;
highlighted_index += highlight_change + offset_shift * display_max_rows;
display_start_offset += offset_shift * display_max_rows;
validateDisplayOffset();
validateHighlight();
}
void validateDisplayOffset()
{
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
}
void setHighlight(const int index)
{
if (!initHighlightChange())
return;
highlighted_index = index;
validateHighlight();
}
bool initHighlightChange()
{
if (display_list.size() == 0)
return false;
if (auto_select && !multiselect)
{
for (auto it = list.begin(); it != list.end(); it++)
{
it->selected = false;
}
}
return true;
}
void toggleHighlighted()
{
if (display_list.size() == 0)
return;
if (auto_select)
return;
ListEntry<T> *entry = display_list[highlighted_index];
if (!multiselect || !allow_null)
{
int selected_count = 0;
for (size_t i = 0; i < list.size(); i++)
{
if (!multiselect && !entry->selected)
list[i].selected = false;
if (!allow_null && list[i].selected)
selected_count++;
}
if (!allow_null && entry->selected && selected_count == 1)
return;
}
entry->selected = !entry->selected;
}
vector<T> getSelectedElems(bool only_one = false)
{
vector<T> results;
for (auto it = list.begin(); it != list.end(); it++)
{
if ((*it).selected)
{
results.push_back(it->elem);
if (only_one)
break;
}
}
return results;
}
T getFirstSelectedElem()
{
vector<T> results = getSelectedElems(true);
if (results.size() == 0)
return (T)nullptr;
else
return results[0];
}
void clearSelection()
{
for_each_(list, clear_fn);
}
void selectItem(const T elem)
{
int i = 0;
for (; i < display_list.size(); i++)
{
if (display_list[i]->elem == elem)
{
setHighlight(i);
break;
}
}
}
void clearSearch()
{
search_string.clear();
filterDisplay();
}
size_t getDisplayListSize()
{
return display_list.size();
}
vector<ListEntry<T>*> &getDisplayList()
{
return display_list;
}
size_t getBaseListSize()
{
return list.size();
}
bool feed(set<df::interface_key> *input)
{
feed_mouse_set_highlight = feed_changed_highlight = false;
if (input->count(interface_key::CURSOR_UP))
{
changeHighlight(-1);
}
else if (input->count(interface_key::CURSOR_DOWN))
{
changeHighlight(1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
{
changeHighlight(0, -1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
{
changeHighlight(0, 1);
}
else if (input->count(interface_key::SELECT) && !auto_select)
{
toggleHighlighted();
}
else if (input->count(interface_key::CUSTOM_SHIFT_S))
{
clearSearch();
}
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
{
return setHighlightByMouse();
}
else if (allow_search)
{
// Search query typing mode always on
df::interface_key last_token = *input->rbegin();
int charcode = Screen::keyToChar(last_token);
if ((charcode >= 96 && charcode <= 123) || charcode == 32)
{
// Standard character
search_string += char(charcode);
filterDisplay();
centerSelection();
}
else if (last_token == interface_key::STRING_A000)
{
// Backspace
if (search_string.length() > 0)
{
search_string.erase(search_string.length()-1);
filterDisplay();
centerSelection();
}
}
else
{
return false;
}
return true;
}
else
{
return false;
}
return true;
}
bool setHighlightByMouse()
{
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
{
int new_index = display_start_offset + gps->mouse_y - 3;
if (new_index < display_list.size())
{
setHighlight(new_index);
feed_mouse_set_highlight = true;
}
enabler->mouse_lbut = enabler->mouse_rbut = 0;
return true;
}
return false;
}
void sort(bool force_sort = false)
{
if (force_sort || list.size() < 100)
std::sort(list.begin(), list.end(), sort_fn);
filterDisplay();
}
void setTitle(const string t)
{
title = t;
if (title.length() > max_item_width)
max_item_width = title.length();
}
size_t getDisplayedListSize()
{
return display_list.size();
}
private:
static void clear_fn(ListEntry<T> &e) { e.selected = false; }
static bool sort_fn(ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; }
vector<ListEntry<T>> list;
vector<ListEntry<T>*> display_list;
string search_string;
string title;
int display_max_rows;
int max_item_width;
};

@ -41,6 +41,7 @@ using namespace std;
#include "Export.h" #include "Export.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "uicommon.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "DataFuncs.h" #include "DataFuncs.h"
@ -3705,12 +3706,6 @@ DFHACK_PLUGIN_LUA_COMMANDS {
//START zone filters //START zone filters
void OutputString(int8_t color, int &x, int y, const std::string &text)
{
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
x += text.length();
}
class zone_filter class zone_filter
{ {
public: public:
@ -3856,7 +3851,7 @@ public:
return false; return false;
} }
df::interface_key last_token = *input->rbegin(); df::interface_key last_token = get_string_key(input);
int charcode = Screen::keyToChar(last_token); int charcode = Screen::keyToChar(last_token);
if (charcode >= 32 && charcode <= 126) if (charcode >= 32 && charcode <= 126)
{ {

@ -3,6 +3,7 @@
-- author Putnam -- author Putnam
-- edited by expwnent -- edited by expwnent
--@module = true
local function getGenderString(gender) local function getGenderString(gender)
local genderStr local genderStr
@ -112,9 +113,9 @@ end
local function createItem(mat,itemType,quality,creator,description) local function createItem(mat,itemType,quality,creator,description)
local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)) local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator))
if pcall(function() print(item.quality) end) then assert(item, 'failed to create item')
item.quality=quality-1 quality = math.max(0, math.min(5, quality - 1))
end item:setQuality(quality)
if df.item_type[itemType[1]]=='SLAB' then if df.item_type[itemType[1]]=='SLAB' then
item.description=description item.description=description
end end
@ -175,23 +176,29 @@ function hackWish(unit)
local amountok, amount local amountok, amount
local matok,mattype,matindex,matFilter local matok,mattype,matindex,matFilter
local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true) local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true)
if not itemok then return end
if not args.notRestrictive then if not args.notRestrictive then
matFilter=getMatFilter(itemtype) matFilter=getMatFilter(itemtype)
end end
if not usesCreature(itemtype) then if not usesCreature(itemtype) then
matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter) matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter)
if not matok then return end
else else
local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList()) local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList())
if not creatureok then return end
mattype,matindex=getCreatureRaceAndCaste(creatureTable[3]) mattype,matindex=getCreatureRaceAndCaste(creatureTable[3])
end end
local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable()) local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable())
if not qualityok then return end
local description local description
if df.item_type[itemtype]=='SLAB' then if df.item_type[itemtype]=='SLAB' then
local descriptionok local descriptionok
descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE) descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE)
if not descriptionok then return end
end end
if args.multi then if args.multi then
repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount) repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount)
if not amountok then return end
if mattype and itemtype then if mattype and itemtype then
if df.item_type.attrs[itemtype].is_stackable then if df.item_type.attrs[itemtype].is_stackable then
local proper_item=df.item.find(dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit)) local proper_item=df.item.find(dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit))

@ -228,6 +228,8 @@ function GmEditorUi:editSelected(index,choice)
self:updateTarget(true) self:updateTarget(true)
elseif trg_type == 'userdata' or trg_type == 'table' then elseif trg_type == 'userdata' or trg_type == 'table' then
self:pushTarget(trg.target[trg_key]) self:pushTarget(trg.target[trg_key])
elseif trg_type == 'nil' or trg_type == 'function' then
-- ignore
else else
print("Unknown type:"..trg_type) print("Unknown type:"..trg_type)
pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end) pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end)
@ -379,21 +381,45 @@ function GmEditorUi:popTarget()
self:updateTarget() self:updateTarget()
end end
function show_editor(trg) function show_editor(trg)
if not trg then
qerror('Target not found')
end
local screen = GmEditorUi{target=trg} local screen = GmEditorUi{target=trg}
screen:show() screen:show()
end end
eval_env = {}
setmetatable(eval_env, {__index = function(_, k)
if k == 'scr' or k == 'screen' then
return dfhack.gui.getCurViewscreen()
elseif k == 'bld' or k == 'building' then
return dfhack.gui.getSelectedBuilding()
elseif k == 'item' then
return dfhack.gui.getSelectedItem()
elseif k == 'job' then
return dfhack.gui.getSelectedJob()
elseif k == 'wsjob' or k == 'workshop_job' then
return dfhack.gui.getSelectedWorkshopJob()
elseif k == 'unit' then
return dfhack.gui.getSelectedUnit()
else
return _G[k]
end
end})
function eval(s)
local f, err = load("return " .. s, "expression", "t", eval_env)
if err then qerror(err) end
return f()
end
if #args~=0 then if #args~=0 then
if args[1]=="dialog" then if args[1]=="dialog" then
function thunk(entry) function thunk(entry)
local t=load("return "..entry)() show_editor(eval(entry))
show_editor(t)
end end
dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk)
elseif args[1]=="free" then elseif args[1]=="free" then
show_editor(df.reinterpret_cast(df[args[2]],args[3])) show_editor(df.reinterpret_cast(df[args[2]],args[3]))
else else
local t=load("return "..args[1])() show_editor(eval(args[1]))
show_editor(t)
end end
else else
show_editor(getTargetFromScreens()) show_editor(getTargetFromScreens())

@ -32,7 +32,9 @@ def lever_descr(bld, idx=nil)
# lever description # lever description
descr = '' descr = ''
descr << "#{idx}: " if idx descr << "#{idx}: " if idx
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" descr << "lever ##{bld.id} "
descr << "(#{bld.name}) " if bld.name.length != 0
descr << "@[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
bld.jobs.each { |j| bld.jobs.each { |j|
if j.job_type == :PullLever if j.job_type == :PullLever
flags = '' flags = ''
@ -82,10 +84,15 @@ case $script_args[0]
when 'pull' when 'pull'
cheat = $script_args.delete('--cheat') || $script_args.delete('--now') cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
if $script_args[1].nil?
bld = df.building_find(:selected) if not bld
raise 'no lever under cursor and no lever id given' if not bld
else
id = $script_args[1].to_i id = $script_args[1].to_i
id = @lever_list[id] || id id = @lever_list[id] || id
bld = df.building_find(id) bld = df.building_find(id)
raise 'invalid lever id' if not bld raise 'invalid lever id' if not bld
end
if cheat if cheat
lever_pull_cheat(bld) lever_pull_cheat(bld)
@ -111,7 +118,10 @@ Lever control from the dfhack console
Usage: Usage:
lever list lever list
shows the list of levers in the fortress, with their id and links shows the list of levers in the fortress, with their id, name and links
lever pull
order the dwarves to pull the lever under the cursor
lever pull 42 lever pull 42
order the dwarves to pull lever 42 order the dwarves to pull lever 42