Merge remote-tracking branch 'dfhack/develop' into create-unit

develop
expwnent 2015-09-15 03:11:54 -04:00
commit 9107d2be52
85 changed files with 11536 additions and 1484 deletions

6
.gitmodules vendored

@ -13,6 +13,6 @@
[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"] [submodule "scripts/3rdparty/lethosor"]
path = depends/jsonxx path = scripts/3rdparty/lethosor
url = git://github.com/lethosor/jsonxx.git url = https://github.com/DFHack/lethosor-scripts

@ -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})
@ -185,6 +184,8 @@ IF(BUILD_PLUGINS)
add_subdirectory (plugins) add_subdirectory (plugins)
endif() endif()
add_subdirectory(scripts)
# Packaging with CPack! # Packaging with CPack!
IF(UNIX) IF(UNIX)
if(APPLE) if(APPLE)

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

@ -80,6 +80,7 @@ melkor217 melkor217
acwatkins acwatkins acwatkins acwatkins
Wes Malone wesQ3 Wes Malone wesQ3
Michon van Dooren MaienM Michon van Dooren MaienM
Seth Woodworth sethwoodworth
======================= ==================== =========================== ======================= ==================== ===========================
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.
@ -1950,6 +1964,33 @@ and are only documented here for completeness:
Wraps strerror() - returns a string describing a platform-specific error code Wraps strerror() - returns a string describing a platform-specific error code
* ``dfhack.internal.addScriptPath(path, search_before)``
Adds ``path`` to the list of paths searched for scripts (both in Lua and Ruby).
If ``search_before`` is passed and ``true``, the path will be searched before
the default paths (e.g. ``raw/scripts``, ``hack/scripts``); otherwise, it will
be searched after.
Returns ``true`` if successful or ``false`` otherwise (e.g. if the path does
not exist or has already been registered).
* ``dfhack.internal.removeScriptPath(path)``
Removes ``path`` from the script search paths and returns ``true`` if successful.
* ``dfhack.internal.getScriptPaths()``
Returns the list of script paths in the order they are searched, including defaults.
(This can change if a world is loaded.)
* ``dfhack.internal.findScript(name)``
Searches script paths for the script ``name`` and returns the path of the first
file found, or ``nil`` on failure.
Note: This requires an extension to be specified (``.lua`` or ``.rb``) -
use ``dfhack.findScript()`` to include the ``.lua`` extension automatically.
Core interpreter context Core interpreter context
======================== ========================
@ -3211,7 +3252,7 @@ on DF world events.
List of events List of events
-------------- --------------
1. ``onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)`` 1. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)``
Auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. Auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes.
@ -3406,6 +3447,73 @@ Simple mechanical workshop::
} }
} }
Luasocket
=========
A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently
only subset of functions exist and only tcp mode is implemented.
Socket class
------------
This is a base class for ``client`` and ``server`` sockets. You can not create it - it's like a virtual
base class in c++.
* ``socket:close()``
Closes the connection.
* ``socket:setTimeout(sec,msec)``
Sets the operation timeout for this socket. It's possible to set timeout to 0. Then it performs like
a non-blocking socket.
Client class
------------
Client is a connection socket to a server. You can get this object either from ``tcp:connect(address,port)`` or
from ``server:accept()``. It's a subclass of ``socket``.
* ``client:receive(pattern)``
Receives data. If ``pattern`` is a number, it receives that much data. Other supported patterns:
* ``*a``
Read all available data.
* ``*l``
Read one line. This is the default mode (if pattern is nil).
* ``client:send(data)``
Sends data. Data is a string.
Server class
------------
Server is a socket that is waiting for clients. You can get this object from ``tcp:bind(address,port)``.
* ``server:accept()``
Accepts an incoming connection if it exists. Returns a ``client`` object representing that socket.
Tcp class
---------
A class with all the tcp functionality.
* ``tcp:bind(address,port)``
Starts listening on that port for incoming connections. Returns ``server`` object.
* ``tcp:connect(address,port)``
Tries connecting to that address and port. Returns ``client`` object.
======= =======
Scripts Scripts
======= =======

@ -197,7 +197,7 @@ To set keybindings, use the built-in ``keybinding`` command. Like any other
command it can be used at any time from the console, but it is also meaningful command it can be used at any time from the console, but it is also meaningful
in the DFHack init file. in the DFHack init file.
Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z. Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported.
Possible ways to call the command: Possible ways to call the command:
@ -212,9 +212,9 @@ 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 any recognized key and [] denote optional parts.
When multiple commands are bound to the same key combination, DFHack selects When multiple commands are bound to the same key combination, DFHack selects
the first applicable one. Later 'add' commands, and earlier entries within one the first applicable one. Later 'add' commands, and earlier entries within one
@ -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
================ ================
@ -290,6 +291,11 @@ control of the game.
* Activate with 'forcepause 1' * Activate with 'forcepause 1'
* Deactivate with 'forcepause 0' * Deactivate with 'forcepause 0'
hide / show
-----------
Hides or shows the DFHack terminal window, respectively. To use ``show``, use
the in-game console (default keybinding Ctrl-Shift-P). Only available on Windows.
nopause nopause
------- -------
Disables pausing (both manual and automatic) with the exception of pause forced Disables pausing (both manual and automatic) with the exception of pause forced
@ -311,6 +317,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
@ -628,6 +645,39 @@ Options:
:show X: Marks the selected map feature as discovered. :show X: Marks the selected map feature as discovered.
:hide X: Marks the selected map feature as undiscovered. :hide X: Marks the selected map feature as undiscovered.
fortplan
--------
Usage: fortplan [filename]
Designates furniture for building according to a .csv file with
quickfort-style syntax. Companion to digfort.
The first line of the file must contain the following::
#build start(X; Y; <start location description>)
...where X and Y are the offset from the top-left corner of the file's area
where the in-game cursor should be located, and <start location description>
is an optional description of where that is. You may also leave a description
of the contents of the file itself following the closing parenthesis on the
same line.
The syntax of the file itself is similar to digfort or quickfort. At present,
only buildings constructed of an item with the same name as the building
are supported. All other characters are ignored. For example::
`,`,d,`,`
`,f,`,t,`
`,s,b,c,`
This section of a file would designate for construction a door and some
furniture inside a bedroom: specifically, clockwise from top left, a cabinet,
a table, a chair, a bed, and a statue.
All of the building designation uses Planning Mode, so you do not need to
have the items available to construct all the buildings when you run
fortplan with the .csv file.
infiniteSky infiniteSky
----------- -----------
Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once. Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once.
@ -749,10 +799,10 @@ up.
For more details, see the 'help' command while using this. For more details, see the 'help' command while using this.
tiletypes-commands tiletypes-command
------------------ ------------------
Runs tiletypes commands, separated by ;. This makes it possible to change Runs tiletypes commands, separated by ;. This makes it possible to change
tiletypes modes from a hotkey. tiletypes modes from a hotkey or via dfhack-run.
tiletypes-here tiletypes-here
-------------- --------------
@ -1160,6 +1210,7 @@ Options:
:-c: Clear designations instead of setting them :-c: Clear designations instead of setting them
:-x: Apply selected action to all plants except those specified (invert :-x: Apply selected action to all plants except those specified (invert
selection) selection)
:-a: Select every type of plant (obeys -t/-s)
Specifying both -t and -s will have no effect. If no plant IDs are specified, Specifying both -t and -s will have no effect. If no plant IDs are specified,
all valid plant IDs will be listed. all valid plant IDs will be listed.
@ -1252,6 +1303,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.
@ -1302,6 +1369,7 @@ Subcommands that persist until disabled or DF quits:
:civ-view-agreement: Fixes overlapping text on the "view agreement" screen :civ-view-agreement: Fixes overlapping text on the "view agreement" screen
:craft-age-wear: Fixes the behavior of crafted items wearing out over time (bug 6003). :craft-age-wear: Fixes the behavior of crafted items wearing out over time (bug 6003).
With this tweak, items made from cloth and leather will gain a level of wear every 20 years. With this tweak, items made from cloth and leather will gain a level of wear every 20 years.
:embark-profile-name: Allows the use of lowercase letters when saving embark profiles
:eggs-fertile: Displays a fertility indicator on nestboxes :eggs-fertile: Displays a fertility indicator on nestboxes
:farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus :farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus
:fast-heat: Further improves temperature update performance by ensuring that 1 degree :fast-heat: Further improves temperature update performance by ensuring that 1 degree
@ -2087,6 +2155,7 @@ Advanced usage:
:`autolabor reset-all`: Return all labors to the default handling. :`autolabor reset-all`: Return all labors to the default handling.
:`autolabor list`: List current status of all labors. :`autolabor list`: List current status of all labors.
:`autolabor status`: Show basic status information. :`autolabor status`: Show basic status information.
:`autolabor-artisans <command>`: Run a command for labors where skill affects output quality
*Examples:* *Examples:*
@ -2144,7 +2213,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
@ -2219,6 +2287,11 @@ scripts that are obscure, developer-oriented, or should be used as keybindings.
The following scripts are distibuted with DFHack: The following scripts are distibuted with DFHack:
devel/*
=======
Scripts in this subdirectory are intended for developers, or still substantially
under development. If you don't already know what they do, best to leave them alone.
fix/* fix/*
===== =====
Scripts in this subdirectory fix various bugs and issues, some of them obscure. Scripts in this subdirectory fix various bugs and issues, some of them obscure.
@ -2233,19 +2306,17 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
on the same exact tile (bug 5991), designates the tile restricted traffic to on the same exact tile (bug 5991), designates the tile restricted traffic to
hopefully avoid jamming it again, and unsuspends them. hopefully avoid jamming it again, and unsuspends them.
* fix/cloth-stockpile
Fixes erratic behavior of cloth stockpiles by scanning material objects
in memory and patching up some invalid reference fields. Needs to be run
every time a save game is loaded; putting ``fix/cloth-stockpile enable``
in ``dfhack.init`` makes it run automatically.
* fix/dead-units * fix/dead-units
Removes uninteresting dead units from the unit list. Doesn't seem to give any Removes uninteresting dead units from the unit list. Doesn't seem to give any
noticeable performance gain, but migrants normally stop if the unit list grows noticeable performance gain, but migrants normally stop if the unit list grows
to around 3000 units, and this script reduces it back. to around 3000 units, and this script reduces it back.
* fix/fat-dwarves
Avoids 5-10% FPS loss due to constant recalculation of insulation for dwarves at
maximum fatness, by reducing the cap from 1,000,000 to 999,999.
* fix/feeding-timers * fix/feeding-timers
Reset the GiveWater and GiveFood timers of all units as appropriate. Reset the GiveWater and GiveFood timers of all units as appropriate.
@ -2260,6 +2331,10 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
Diagnoses and fixes issues with nonexistant 'items occupying site', usually Diagnoses and fixes issues with nonexistant 'items occupying site', usually
caused by autodump bugs or other hacking mishaps. caused by autodump bugs or other hacking mishaps.
* fix/loyaltycascade
Aborts loyalty cascades by fixing units whose own civ is the enemy.
* fix/population-cap * fix/population-cap
Run this after every migrant wave to ensure your population cap is not exceeded. Run this after every migrant wave to ensure your population cap is not exceeded.
@ -2273,6 +2348,11 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
the environment and stops temperature updates. In order to maintain this efficient the environment and stops temperature updates. In order to maintain this efficient
state however, use ``tweak stable-temp`` and ``tweak fast-heat``. state however, use ``tweak stable-temp`` and ``tweak fast-heat``.
* fix/stuckdoors
Fix doors that are stuck open due to incorrect map occupancy flags, eg due to
incorrect use of teleport.
gui/* gui/*
===== =====
@ -2283,6 +2363,11 @@ directory.
A graphical interface for creating items. A graphical interface for creating items.
* gui/dfstatus
Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars.
Sections can be enabled/disabled/configured by editing ``dfhack-config/dfstatus.lua``.
* gui/stockpiles * gui/stockpiles
Load and save stockpile settings from the 'q' menu. Load and save stockpile settings from the 'q' menu.
@ -2296,6 +2381,40 @@ directory.
Don't forget to `enable stockpiles` and create the `stocksettings` directory in Don't forget to `enable stockpiles` and create the `stocksettings` directory in
the DF folder before trying to use this plugin. the DF folder before trying to use this plugin.
adaptation
==========
View or set level of cavern adaptation for the selected unit or the whole fort.
Usage: ``adaptation <show|set> <him|all> [value]``. The ``value`` must be
between 0 and 800,000 inclusive.
add-thought
===========
Adds a thought or emotion to the selected unit. Can be used by other scripts,
or the gui invoked by running ``add-thought gui`` with a unit selected.
autofarm
========
Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots.
Usage::
autofarm start
autofarm default 30
autofarm threshold 150 helmet_plump tail_pig
autounsuspend
=============
Automatically unsuspend construction jobs, on a recurring basis.
See ``unsuspend`` for one-off use, or ``resume all``.
ban-cooking
===========
A more convenient way to ban cooking various categories of foods than the
kitchen interface. Usage: ``ban-cooking <type>``. Valid types are ``booze``,
``honey``, ``tallow``, ``oil``, and ``seeds`` (non-tree plants with seeds).
binpatch binpatch
======== ========
Checks, applies or removes binary patches directly in memory at runtime:: Checks, applies or removes binary patches directly in memory at runtime::
@ -2338,6 +2457,12 @@ Examples::
create-items bar CREATURE:CAT:SOAP create-items bar CREATURE:CAT:SOAP
create-items bar adamantine create-items bar adamantine
deathcause
==========
Focus a body part ingame, and this script will display the cause of death of
the creature.
Also works when selecting units from the (``u``) unitlist viewscreen.
digfort digfort
======= =======
A script to designate an area for digging according to a plan in csv format. A script to designate an area for digging according to a plan in csv format.
@ -2368,16 +2493,6 @@ drain-aquifer
============= =============
Remove all 'aquifer' tag from the map blocks. Irreversible. Remove all 'aquifer' tag from the map blocks. Irreversible.
deathcause
==========
Focus a body part ingame, and this script will display the cause of death of
the creature.
Also works when selecting units from the (``u``) unitlist viewscreen.
dfstatus
========
Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars.
exterminate exterminate
=========== ===========
Kills any unit of a given race. Kills any unit of a given race.
@ -2417,6 +2532,10 @@ To purify all elves on the map with fire (may have side-effects)::
exterminate elve magma exterminate elve magma
fixnaked
========
Removes all unhappy thoughts due to lack of clothing.
fix-ster fix-ster
======== ========
Utilizes the orientation tag to either fix infertile creatures or inflict Utilizes the orientation tag to either fix infertile creatures or inflict
@ -2429,38 +2548,20 @@ or sterile. Optional arguments specify the target: no argument for the
selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf
creatures, or ``only:<creature>`` to only process matching creatures. creatures, or ``only:<creature>`` to only process matching creatures.
fortplan forum-dwarves
======== =============
Usage: fortplan [filename] Saves a copy of a text screen, formatted in bbcode for posting to the Bay12 Forums.
Use ``forum-dwarves help`` for more information.
Designates furniture for building according to a .csv file with
quickfort-style syntax. Companion to digfort.
The first line of the file must contain the following::
#build start(X; Y; <start location description>)
...where X and Y are the offset from the top-left corner of the file's area
where the in-game cursor should be located, and <start location description>
is an optional description of where that is. You may also leave a description
of the contents of the file itself following the closing parenthesis on the
same line.
The syntax of the file itself is similar to digfort or quickfort. At present,
only buildings constructed of an item with the same name as the building
are supported. All other characters are ignored. For example::
`,`,d,`,`
`,f,`,t,`
`,s,b,c,`
This section of a file would designate for construction a door and some full-heal
furniture inside a bedroom: specifically, clockwise from top left, a cabinet, =========
a table, a chair, a bed, and a statue. Attempts to fully heal the selected unit. ``full-heal -r`` attempts to resurrect the unit.
All of the building designation uses Planning Mode, so you do not need to gaydar
have the items available to construct all the buildings when you run ======
fortplan with the .csv file. Shows the sexual orientation of units, useful for social engineering or checking
the viability of livestock breeding programs. Use ``gaydar -help`` for information
on available filters for orientation, citizenship, species, etc.
growcrops growcrops
========= =========
@ -2544,6 +2645,15 @@ There are the following ways to invoke this command:
Parses and executes the lua statement like the interactive interpreter would. Parses and executes the lua statement like the interactive interpreter would.
make-monarch
============
Make the selected unit King or Queen of your civilisation.
markdown
========
Save a copy of a text screen in markdown (for reddit among others).
Use 'markdown help' for more details.
masspit masspit
======= =======
Designate all creatures in cages on top of a pit/pond activity zone for pitting. Designate all creatures in cages on top of a pit/pond activity zone for pitting.
@ -2555,13 +2665,19 @@ or with the game cursor on top of the area.
multicmd multicmd
======== ========
Run multiple dfhack commands. The argument is split around the Run multiple dfhack commands. The argument is split around the
character ; and all parts are run sequencially as independent character ; and all parts are run sequentially as independent
dfhack commands. Useful for hotkeys. dfhack commands. Useful for hotkeys.
Example:: Example::
multicmd locate-ore iron ; digv multicmd locate-ore iron ; digv
points
======
Sets available points at the embark screen to the specified number. Eg.
``points 1000000`` would allow you to buy everything, or ``points 0`` would
make life quite difficult.
position position
======== ========
Reports the current time: date, clock time, month, and season. Also reports Reports the current time: date, clock time, month, and season. Also reports
@ -2577,15 +2693,52 @@ quicksave
If called in dwarf mode, makes DF immediately auto-save the game by setting a flag If called in dwarf mode, makes DF immediately auto-save the game by setting a flag
normally used in seasonal auto-save. normally used in seasonal auto-save.
region-pops
===========
Show or modify the populations of animals in the region. Use ``region-pops`` for details.
remove-stress remove-stress
============= =============
Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or very stressed dwarves taking on negative or greater values respectively. Applies to the selected unit, or use "remove-stress -all" to apply to all units. Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or
very stressed dwarves taking on negative or greater values respectively.
Applies to the selected unit, or use "remove-stress -all" to apply to all units.
remove-wear
===========
Sets the wear on all items in your fort to zero.
repeat
======
Repeatedly calls a lua script at the specified interval.
This allows neat background changes to the function of the game, especially when
invoked from an init file. For detailed usage instructions, use ``repeat -help``.
Usage examples::
repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ]
repeat -time 1 -timeUnits months -command [ multicmd cleanowned scattered x; clean all ] -name clean
The first example is abstract; the second will regularly remove all contaminants
and worn items from the game.
``-name`` sets the name for the purposes of cancelling and making sure you don't schedule the
same repeating event twice. If not specified, it's set to the first argument after ``-command``.
``-time delay -timeUnits units``; delay is some positive integer, and units is some valid time
unit for ``dfhack.timeout(delay,timeUnits,function)``. ``-command [ ... ]`` specifies the
command to be run.
setfps setfps
====== ======
Run ``setfps <number>`` to set the FPS cap at runtime, in case you want to watch Run ``setfps <number>`` to set the FPS cap at runtime, in case you want to watch
combat in slow motion or something :) combat in slow motion or something :)
show-unit-syndromes
===================
Show syndromes affecting units and the remaining and maximum duration, along
with (optionally) substantial detail on the effects. Call
``show-unit-syndromes help`` for further options.
siren siren
===== =====
Wakes up sleeping units, cancels breaks and stops parties either everywhere, Wakes up sleeping units, cancels breaks and stops parties either everywhere,
@ -2631,17 +2784,12 @@ Ex::
source add magma 7 - magma source source add magma 7 - magma source
source add water 0 - water drain source add water 0 - water drain
superdwarf startdwarf
========== ==========
Similar to fastdwarf, per-creature. Use at the embark screen to embark with the specified number of dwarves. Eg.
``startdwarf 500`` would lead to a severe food shortage and FPS issues, while
To make any creature superfast, target it ingame using 'v' and:: ``startdwarf 10`` would just allow a few more warm bodies to dig in.
The number must be 7 or greater.
superdwarf add
Other options available: ``del``, ``clear``, ``list``.
This plugin also shortens the 'sleeping' and 'on break' periods of targets.
stripcaged stripcaged
========== ==========
@ -2663,6 +2811,18 @@ alternatively pass cage IDs as arguments::
stripcaged weapons 25321 34228 stripcaged weapons 25321 34228
superdwarf
==========
Similar to fastdwarf, per-creature.
To make any creature superfast, target it ingame using 'v' and::
superdwarf add
Other options available: ``del``, ``clear``, ``list``.
This plugin also shortens the 'sleeping' and 'on break' periods of targets.
teleport teleport
======== ========
Teleports a unit to given coordinates. Teleports a unit to given coordinates.
@ -2677,6 +2837,11 @@ undump-buildings
================ ================
Undesignates building base materials for dumping. Undesignates building base materials for dumping.
unsuspend
=========
Unsuspend construction jobs, on a one-off basis. See ``autounsuspend`` for regular use.
Equivalent to ``resume all``.
view-item-info view-item-info
============== ==============
A script to extend the item or unit viewscreen with additional information A script to extend the item or unit viewscreen with additional information

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

@ -0,0 +1,28 @@
-- dfstatus config
-- the dfstatus script can be found in hack/scripts/gui/
--[[
The following variables can be set to true/false to enable/disable categories (all true by default)
* drink
* wood
* fuel
* prepared_meals
* tanned_hides
* cloth
* metals
Example:
drink = false
fuel = true
To add metals:
* metal 'IRON'
* metals "GOLD" 'SILVER'
* metal('COPPER')
* metals("BRONZE", 'HORN_SILVER')
Use '-' for a blank line:
* metal '-'
]]
metals 'IRON' 'PIG_IRON' 'STEEL'
metals '-'
metals 'GOLD' 'SILVER' 'COPPER'

@ -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) IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ELSEIF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF()
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)
@ -374,6 +376,7 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
DESTINATION ${DFHACK_DATA_DESTINATION} DESTINATION ${DFHACK_DATA_DESTINATION}
FILES_MATCHING PATTERN "*.lua" FILES_MATCHING PATTERN "*.lua"
PATTERN "*.rb" PATTERN "*.rb"
PATTERN "3rdparty" EXCLUDE
) )
install(DIRECTORY ${dfhack_SOURCE_DIR}/patches install(DIRECTORY ${dfhack_SOURCE_DIR}/patches

@ -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"
@ -385,12 +386,12 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
std::vector<std::string> possible; std::vector<std::string> possible;
auto plug_mgr = Core::getInstance().getPluginManager(); auto plug_mgr = Core::getInstance().getPluginManager();
for(size_t i = 0; i < plug_mgr->size(); i++) for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it)
{ {
const Plugin * plug = (plug_mgr->operator[](i)); const Plugin * plug = it->second;
for (size_t j = 0; j < plug->size(); j++) for (size_t j = 0; j < plug->size(); j++)
{ {
const PluginCommand &pcmd = plug->operator[](j); const PluginCommand &pcmd = (*plug)[j];
if (pcmd.isHotkeyCommand()) if (pcmd.isHotkeyCommand())
continue; continue;
if (pcmd.name.substr(0, first.size()) == first) if (pcmd.name.substr(0, first.size()) == first)
@ -426,24 +427,65 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
return false; return false;
} }
string findScript(string path, string name) { bool Core::addScriptPath(string path, bool search_before)
if (df::global::world) { {
//first try the save folder if it exists lock_guard<mutex> lock(*script_path_mutex);
string save = World::ReadWorldFolder(); vector<string> &vec = script_paths[search_before ? 0 : 1];
if ( save != "" ) { if (std::find(vec.begin(), vec.end(), path) != vec.end())
string file = path + "/data/save/" + save + "/raw/scripts/" + name; return false;
if (fileExists(file)) { if (!Filesystem::isdir(path))
return file; return false;
} vec.push_back(path);
return true;
}
bool Core::removeScriptPath(string path)
{
lock_guard<mutex> lock(*script_path_mutex);
bool found = false;
for (int i = 0; i < 2; i++)
{
vector<string> &vec = script_paths[i];
while (1)
{
auto it = std::find(vec.begin(), vec.end(), path);
if (it == vec.end())
break;
vec.erase(it);
found = true;
} }
} }
string file = path + "/raw/scripts/" + name; return found;
if (fileExists(file)) { }
return file;
void Core::getScriptPaths(std::vector<std::string> *dest)
{
lock_guard<mutex> lock(*script_path_mutex);
dest->clear();
string df_path = this->p->getPath();
for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it)
dest->push_back(*it);
if (df::global::world) {
string save = World::ReadWorldFolder();
if (save.size())
dest->push_back(df_path + "/data/save/" + save + "/raw/scripts");
} }
file = path + "/hack/scripts/" + name; dest->push_back(df_path + "/raw/scripts");
if (fileExists(file)) { dest->push_back(df_path + "/hack/scripts");
return file; for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it)
dest->push_back(*it);
}
string Core::findScript(string name)
{
vector<string> paths;
getScriptPaths(&paths);
for (auto it = paths.begin(); it != paths.end(); ++it)
{
string path = *it + "/" + name;
if (Filesystem::isfile(path))
return path;
} }
return ""; return "";
} }
@ -484,6 +526,43 @@ static std::string sc_event_name (state_change_event id) {
return "SC_UNKNOWN"; return "SC_UNKNOWN";
} }
string getBuiltinCommand(std::string cmd)
{
std::string builtin = "";
if (cmd == "ls" ||
cmd == "help" ||
cmd == "type" ||
cmd == "load" ||
cmd == "unload" ||
cmd == "reload" ||
cmd == "enable" ||
cmd == "disable" ||
cmd == "plug" ||
cmd == "type" ||
cmd == "keybinding" ||
cmd == "fpause" ||
cmd == "cls" ||
cmd == "die" ||
cmd == "kill-lua" ||
cmd == "script" ||
cmd == "hide" ||
cmd == "show" ||
cmd == "sc-script"
)
builtin = cmd;
else if (cmd == "?" || cmd == "man")
builtin = "help";
else if (cmd == "dir")
builtin = "ls";
else if (cmd == "clear")
builtin = "cls";
return builtin;
}
command_result Core::runCommand(color_ostream &con, const std::string &first_, vector<string> &parts) command_result Core::runCommand(color_ostream &con, const std::string &first_, vector<string> &parts)
{ {
std::string first = first_; std::string first = first_;
@ -498,8 +577,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
first[i] = '/'; first[i] = '/';
} }
} }
// let's see what we actually got // let's see what we actually got
if(first=="help" || first == "?" || first == "man") string builtin = getBuiltinCommand(first);
if (builtin == "help")
{ {
if(!parts.size()) if(!parts.size())
{ {
@ -515,21 +596,26 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
" help|?|man - This text.\n" " help|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\n" " help COMMAND - Usage help for the given command.\n"
" ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n"
" cls - Clear the console.\n" " cls|clear - Clear the console.\n"
" fpause - Force DF to pause.\n" " fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n" " die - Force DF to close immediately\n"
" keybinding - Modify bindings of commands to keys\n" " keybinding - Modify bindings of commands to keys\n"
"Plugin management (useful for developers):\n" "Plugin management (useful for developers):\n"
" plug [PLUGIN|v] - List plugin state and description.\n" " plug [PLUGIN|v] - List plugin state and description.\n"
" load PLUGIN|all - Load a plugin by name or load all possible plugins.\n" " load PLUGIN|-all - Load a plugin by name or load all possible plugins.\n"
" unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" " unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" " reload PLUGIN|-all - Reload a plugin or all loaded plugins.\n"
); );
con.print("\nDFHack version %s.\n", Version::dfhack_version()); con.print("\nDFHack version %s.\n", Version::dfhack_version());
} }
else if (parts.size() == 1) else if (parts.size() == 1)
{ {
if (getBuiltinCommand(parts[0]).size())
{
con << parts[0] << ": built-in command; Use `ls`, `help`, or check hack/Readme.html for more information" << std::endl;
return CR_NOT_IMPLEMENTED;
}
Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (plug) { if (plug) {
for (size_t j = 0; j < plug->size();j++) for (size_t j = 0; j < plug->size();j++)
@ -547,15 +633,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_OK; return CR_OK;
} }
} }
string path = this->p->getPath(); string file = findScript(parts[0] + ".lua");
string file = findScript(path, parts[0] + ".lua");
if ( file != "" ) { if ( file != "" ) {
string help = getScriptHelp(file, "-- "); string help = getScriptHelp(file, "-- ");
con.print("%s: %s\n", parts[0].c_str(), help.c_str()); con.print("%s: %s\n", parts[0].c_str(), help.c_str());
return CR_OK; return CR_OK;
} }
if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) { if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) {
file = findScript(path, parts[0] + ".rb"); file = findScript(parts[0] + ".rb");
if ( file != "" ) { if ( file != "" ) {
string help = getScriptHelp(file, "# "); string help = getScriptHelp(file, "# ");
con.print("%s: %s\n", parts[0].c_str(), help.c_str()); con.print("%s: %s\n", parts[0].c_str(), help.c_str());
@ -563,97 +648,58 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
} }
con.printerr("Unknown command: %s\n", parts[0].c_str()); con.printerr("Unknown command: %s\n", parts[0].c_str());
return CR_FAILURE;
} }
else else
{ {
con.printerr("not implemented yet\n"); con.printerr("not implemented yet\n");
return CR_NOT_IMPLEMENTED;
} }
} }
else if( first == "load" ) else if (builtin == "load" || builtin == "unload" || builtin == "reload")
{
if(parts.size())
{
string & plugname = parts[0];
if(plugname == "all")
{
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->load(con);
}
}
else
{
Plugin * plug = plug_mgr->getPluginByName(plugname);
if(!plug)
{
con.printerr("No such plugin\n");
}
else
{
plug->load(con);
}
}
}
}
else if( first == "reload" )
{ {
if(parts.size()) bool all = false;
bool load = (builtin == "load");
bool unload = (builtin == "unload");
if (parts.size())
{ {
string & plugname = parts[0]; for (auto p = parts.begin(); p != parts.end(); p++)
if(plugname == "all")
{ {
for(size_t i = 0; i < plug_mgr->size();i++) if (p->size() && (*p)[0] == '-')
{ {
Plugin * plug = (plug_mgr->operator[](i)); if (p->find('a') != string::npos)
plug->reload(con); all = true;
} }
} }
else if (all)
{ {
Plugin * plug = plug_mgr->getPluginByName(plugname); if (load)
if(!plug) plug_mgr->loadAll();
{ else if (unload)
con.printerr("No such plugin\n"); plug_mgr->unloadAll();
}
else else
{ plug_mgr->reloadAll();
plug->reload(con); return CR_OK;
}
}
}
}
else if( first == "unload" )
{
if(parts.size())
{
string & plugname = parts[0];
if(plugname == "all")
{
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->unload(con);
}
} }
else for (auto p = parts.begin(); p != parts.end(); p++)
{ {
Plugin * plug = plug_mgr->getPluginByName(plugname); if (!p->size() || (*p)[0] == '-')
if(!plug) continue;
{ if (load)
con.printerr("No such plugin\n"); plug_mgr->load(*p);
} else if (unload)
plug_mgr->unload(*p);
else else
{ plug_mgr->reload(*p);
plug->unload(con);
}
} }
} }
else
con.printerr("%s: no arguments\n", builtin.c_str());
} }
else if( first == "enable" || first == "disable" ) else if( builtin == "enable" || builtin == "disable" )
{ {
CoreSuspender suspend; CoreSuspender suspend;
bool enable = (first == "enable"); bool enable = (builtin == "enable");
if(parts.size()) if(parts.size())
{ {
@ -672,11 +718,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
} }
Plugin * plug = plug_mgr->getPluginByName(part); Plugin * plug = (*plug_mgr)[part];
if(!plug) if(!plug)
{ {
std::string lua = findScript(this->p->getPath(), part + ".lua"); std::string lua = findScript(part + ".lua");
if (lua.size()) if (lua.size())
{ {
res = enableLuaScript(con, part, enable); res = enableLuaScript(con, part, enable);
@ -690,14 +736,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
else if (!plug->can_set_enabled()) else if (!plug->can_set_enabled())
{ {
res = CR_NOT_IMPLEMENTED; res = CR_NOT_IMPLEMENTED;
con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); con.printerr("Cannot %s plugin: %s\n", builtin.c_str(), part.c_str());
} }
else else
{ {
res = plug->set_enabled(con, enable); res = plug->set_enabled(con, enable);
if (res != CR_OK || plug->is_enabled() != enable) if (res != CR_OK || plug->is_enabled() != enable)
con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); con.printerr("Could not %s plugin: %s\n", builtin.c_str(), part.c_str());
} }
} }
@ -705,9 +751,9 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
else else
{ {
for(size_t i = 0; i < plug_mgr->size();i++) for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it)
{ {
Plugin * plug = (plug_mgr->operator[](i)); Plugin * plug = it->second;
if (!plug->can_be_enabled()) continue; if (!plug->can_be_enabled()) continue;
con.print( con.print(
@ -719,7 +765,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
} }
} }
else if(first == "ls" || first == "dir") else if (builtin == "ls" || builtin == "dir")
{ {
bool all = false; bool all = false;
if (parts.size() && parts[0] == "-a") if (parts.size() && parts[0] == "-a")
@ -730,10 +776,18 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
if(parts.size()) if(parts.size())
{ {
string & plugname = parts[0]; string & plugname = parts[0];
const Plugin * plug = plug_mgr->getPluginByName(plugname); const Plugin * plug = (*plug_mgr)[plugname];
if(!plug) if(!plug)
{ {
con.printerr("There's no plugin called %s!\n",plugname.c_str()); con.printerr("There's no plugin called %s!\n", plugname.c_str());
}
else if (plug->getState() != Plugin::PS_LOADED)
{
con.printerr("Plugin %s is not loaded.\n", plugname.c_str());
}
else if (!plug->size())
{
con.printerr("Plugin %s is loaded but does not implement any commands.\n", plugname.c_str());
} }
else for (size_t j = 0; j < plug->size();j++) else for (size_t j = 0; j < plug->size();j++)
{ {
@ -748,27 +802,28 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
con.print( con.print(
"builtin:\n" "builtin:\n"
" help|?|man - This text or help specific to a plugin.\n" " help|?|man - This text or help specific to a plugin.\n"
" ls [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n"
" cls - Clear the console.\n" " cls|clear - Clear the console.\n"
" fpause - Force DF to pause.\n" " fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n" " die - Force DF to close immediately\n"
" kill-lua - Stop an active Lua script\n" " kill-lua - Stop an active Lua script\n"
" keybinding - Modify bindings of commands to keys\n" " keybinding - Modify bindings of commands to keys\n"
" script FILENAME - Run the commands specified in a file.\n" " script FILENAME - Run the commands specified in a file.\n"
" sc-script - Automatically run specified scripts on state change events\n" " sc-script - Automatically run specified scripts on state change events\n"
" plug [PLUGIN|v] - List plugin state and detailed description.\n" " plug [PLUGIN|v] - List plugin state and detailed description.\n"
" load PLUGIN|all - Load a plugin by name or load all possible plugins.\n" " load PLUGIN|-all [...] - Load a plugin by name or load all possible plugins.\n"
" unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" " unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" " reload PLUGIN|-all [...] - Reload a plugin or all loaded plugins.\n"
" enable/disable PLUGIN - Enable or disable a plugin if supported.\n" " enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n"
" type COMMAND - Display information about where a command is implemented\n"
"\n" "\n"
"plugins:\n" "plugins:\n"
); );
std::set <sortable> out; std::set <sortable> out;
for(size_t i = 0; i < plug_mgr->size();i++) for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it)
{ {
const Plugin * plug = (plug_mgr->operator[](i)); const Plugin * plug = it->second;
if(!plug->size()) if(!plug->size())
continue; continue;
for (size_t j = 0; j < plug->size();j++) for (size_t j = 0; j < plug->size();j++)
@ -794,17 +849,85 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
} }
} }
else if(first == "plug") else if (builtin == "plug")
{ {
for(size_t i = 0; i < plug_mgr->size();i++) const char *header_format = "%25s %10s %4s\n";
const char *row_format = "%25s %10s %4i\n";
con.print(header_format, "Name", "State", "Cmds");
plug_mgr->refresh();
for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it)
{ {
const Plugin * plug = (plug_mgr->operator[](i)); const Plugin * plug = it->second;
if(!plug->size()) if (!plug)
continue;
if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end())
continue; continue;
con.print("%s\n", plug->getName().c_str()); color_value color;
switch (plug->getState())
{
case Plugin::PS_LOADED:
color = COLOR_LIGHTGREEN;
break;
case Plugin::PS_UNLOADED:
case Plugin::PS_UNLOADING:
color = COLOR_YELLOW;
break;
case Plugin::PS_LOADING:
color = COLOR_LIGHTBLUE;
break;
case Plugin::PS_BROKEN:
color = COLOR_LIGHTRED;
break;
default:
color = COLOR_LIGHTMAGENTA;
break;
}
con.color(color);
con.print(row_format,
plug->getName().c_str(),
Plugin::getStateDescription(plug->getState()),
plug->size()
);
con.color(COLOR_RESET);
}
}
else if (builtin == "type")
{
if (!parts.size())
{
con.printerr("type: no argument\n");
return CR_WRONG_USAGE;
}
con << parts[0];
string builtin_cmd = getBuiltinCommand(parts[0]);
Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (builtin_cmd.size())
{
con << " is a built-in command";
if (builtin_cmd != parts[0])
con << " (aliased to " << builtin_cmd << ")";
con << "." << std::endl;
}
else if (plug)
{
con << " is part of plugin " << plug->getName() << "." << std::endl;
}
else if (findScript(parts[0] + ".lua").size())
{
con << " is a Lua script." << std::endl;
}
else if (findScript(parts[0] + ".rb").size())
{
con << " is a Ruby script." << std::endl;
}
else
{
con << " is not recognized." << std::endl;
return CR_FAILURE;
} }
} }
else if(first == "keybinding") else if (builtin == "keybinding")
{ {
if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add"))
{ {
@ -852,12 +975,12 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
<< Gui::getFocusString(Core::getTopViewscreen()) << endl; << Gui::getFocusString(Core::getTopViewscreen()) << endl;
} }
} }
else if(first == "fpause") else if (builtin == "fpause")
{ {
World::SetPauseState(true); World::SetPauseState(true);
con.print("The game was forced to pause!\n"); con.print("The game was forced to pause!\n");
} }
else if(first == "cls") else if (builtin == "cls")
{ {
if (con.is_console()) if (con.is_console())
((Console&)con).clear(); ((Console&)con).clear();
@ -867,11 +990,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_NEEDS_CONSOLE; return CR_NEEDS_CONSOLE;
} }
} }
else if(first == "die") else if (builtin == "die")
{ {
_exit(666); _exit(666);
} }
else if(first == "kill-lua") else if (builtin == "kill-lua")
{ {
bool force = false; bool force = false;
for (auto it = parts.begin(); it != parts.end(); ++it) for (auto it = parts.begin(); it != parts.end(); ++it)
@ -882,7 +1005,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
if (!Lua::Interrupt(force)) if (!Lua::Interrupt(force))
con.printerr("Failed to register hook - use 'kill-lua force' to force\n"); con.printerr("Failed to register hook - use 'kill-lua force' to force\n");
} }
else if(first == "script") else if (builtin == "script")
{ {
if(parts.size() == 1) if(parts.size() == 1)
{ {
@ -895,7 +1018,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
} }
else if(first=="hide") else if (builtin=="hide")
{ {
if (!getConsole().hide()) if (!getConsole().hide())
{ {
@ -904,7 +1027,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
return CR_OK; return CR_OK;
} }
else if(first=="show") else if (builtin=="show")
{ {
if (!getConsole().show()) if (!getConsole().show())
{ {
@ -913,7 +1036,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
return CR_OK; return CR_OK;
} }
else if(first == "sc-script") else if (builtin == "sc-script")
{ {
if (parts.size() < 1) if (parts.size() < 1)
{ {
@ -1017,11 +1140,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
if(res == CR_NOT_IMPLEMENTED) if(res == CR_NOT_IMPLEMENTED)
{ {
string completed; string completed;
string path = this->p->getPath(); string filename = findScript(first + ".lua");
string filename = findScript(path, first + ".lua");
bool lua = filename != ""; bool lua = filename != "";
if ( !lua ) { if ( !lua ) {
filename = findScript(path, first + ".rb"); filename = findScript(first + ".rb");
} }
if ( lua ) if ( lua )
res = runLuaScript(con, first, parts); res = runLuaScript(con, first, parts);
@ -1190,6 +1312,8 @@ Core::Core()
server = NULL; server = NULL;
color_ostream::log_errors_to_stderr = true; color_ostream::log_errors_to_stderr = true;
script_path_mutex = new mutex();
}; };
void Core::fatal (std::string output, bool deactivate) void Core::fatal (std::string output, bool deactivate)
@ -1314,6 +1438,8 @@ bool Core::Init()
if (std::find(config_files.begin(), config_files.end(), filename) == config_files.end()) if (std::find(config_files.begin(), config_files.end(), filename) == config_files.end())
{ {
std::string src_file = std::string("dfhack-config/default/") + filename; std::string src_file = std::string("dfhack-config/default/") + filename;
if (!Filesystem::isfile(src_file))
continue;
std::string dest_file = std::string("dfhack-config/") + filename; std::string dest_file = std::string("dfhack-config/") + filename;
std::ifstream src(src_file, std::ios::binary); std::ifstream src(src_file, std::ios::binary);
std::ofstream dest(dest_file, std::ios::binary); std::ofstream dest(dest_file, std::ios::binary);
@ -1337,7 +1463,7 @@ bool Core::Init()
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(this); plug_mgr->init();
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
@ -1755,6 +1881,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 +2042,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 +2088,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);
@ -2012,9 +2200,15 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string
if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') {
*psym = SDL::K_a + (keyspec[0]-'A'); *psym = SDL::K_a + (keyspec[0]-'A');
return true; return true;
} else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') {
*psym = SDL::K_0 + (keyspec[0]-'0');
return true;
} else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') { } else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') {
*psym = SDL::K_F1 + (keyspec[1]-'1'); *psym = SDL::K_F1 + (keyspec[1]-'1');
return true; return true;
} else if (keyspec.size() == 3 && keyspec.substr(0, 2) == "F1" && keyspec[2] >= '0' && keyspec[2] <= '2') {
*psym = SDL::K_F10 + (keyspec[2]-'0');
return true;
} else if (keyspec == "Enter") { } else if (keyspec == "Enter") {
*psym = SDL::K_RETURN; *psym = SDL::K_RETURN;
return true; return true;
@ -2042,6 +2236,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 +2304,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;
}
} }
} }

@ -38,6 +38,8 @@ distribution.
#include "DataDefs.h" #include "DataDefs.h"
#include "DataIdentity.h" #include "DataIdentity.h"
#include "DataFuncs.h" #include "DataFuncs.h"
#include "DFHackVersion.h"
#include "PluginManager.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Gui.h" #include "modules/Gui.h"
@ -1396,6 +1398,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 +1412,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 +2210,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 +2240,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;
@ -2617,6 +2637,47 @@ static int internal_getModifiers(lua_State *L)
return 1; return 1;
} }
static int internal_addScriptPath(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
bool search_before = (lua_gettop(L) > 1 && lua_toboolean(L, 2));
lua_pushboolean(L, Core::getInstance().addScriptPath(path, search_before));
return 1;
}
static int internal_removeScriptPath(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
lua_pushboolean(L, Core::getInstance().removeScriptPath(path));
return 1;
}
static int internal_getScriptPaths(lua_State *L)
{
int i = 1;
lua_newtable(L);
std::vector<std::string> paths;
Core::getInstance().getScriptPaths(&paths);
for (auto it = paths.begin(); it != paths.end(); ++it)
{
lua_pushinteger(L, i++);
lua_pushstring(L, it->c_str());
lua_settable(L, -3);
}
return 1;
}
static int internal_findScript(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
std::string path = Core::getInstance().findScript(name);
if (path.size())
lua_pushstring(L, path.c_str());
else
lua_pushnil(L);
return 1;
}
static const luaL_Reg dfhack_internal_funcs[] = { static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress }, { "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress }, { "setAddress", internal_setAddress },
@ -2632,6 +2693,10 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getDir", filesystem_listdir }, { "getDir", filesystem_listdir },
{ "runCommand", internal_runCommand }, { "runCommand", internal_runCommand },
{ "getModifiers", internal_getModifiers }, { "getModifiers", internal_getModifiers },
{ "addScriptPath", internal_addScriptPath },
{ "removeScriptPath", internal_removeScriptPath },
{ "getScriptPaths", internal_getScriptPaths },
{ "findScript", internal_findScript },
{ NULL, NULL } { NULL, NULL }
}; };

@ -49,6 +49,7 @@ distribution.
#include "MiscUtils.h" #include "MiscUtils.h"
#include "DFHackVersion.h" #include "DFHackVersion.h"
#include "PluginManager.h"
#include "df/job.h" #include "df/job.h"
#include "df/job_item.h" #include "df/job_item.h"

@ -40,6 +40,7 @@ distribution.
#include "LuaTools.h" #include "LuaTools.h"
#include "DataFuncs.h" #include "DataFuncs.h"
#include "PluginManager.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include <lua.h> #include <lua.h>
@ -48,6 +49,16 @@ distribution.
using namespace DFHack; using namespace DFHack;
using namespace DFHack::LuaWrapper; using namespace DFHack::LuaWrapper;
#ifdef _DARWIN
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_6
size_t strnlen (const char *str, size_t max)
{
const char *end = (const char*)memchr(str, 0, max);
return end ? (size_t)(end - str) : max;
}
#endif
#endif
/************************************** /**************************************
* Identity object read/write methods * * Identity object read/write methods *
**************************************/ **************************************/

@ -1,12 +0,0 @@
/*
* MacPool.h
* Handles creation and destruction of autorelease pool for DFHack on the Mac
*/
#ifndef MACPOOL_H
#define MACPOOL_H
int create_pool();
int destroy_pool();
#endif

@ -1,22 +0,0 @@
/*
* MacPool.m
*
*/
#import <Foundation/Foundation.h>
#import "MacPool.h"
NSAutoreleasePool *thePool;
int create_pool() {
fprintf(stderr,"Creating autorelease pool\n");
thePool = [[NSAutoreleasePool alloc] init];
return 1;
}
int destroy_pool() {
fprintf(stderr,"Draining and releasing autorelease pool\n");
[thePool drain];
[thePool release];
return 0;
}

@ -52,6 +52,31 @@ using namespace tthread;
#include <assert.h> #include <assert.h>
#define MUTEX_GUARD(lock) auto lock_##__LINE__ = make_mutex_guard(lock);
template <typename T>
tthread::lock_guard<T> make_mutex_guard (T *mutex)
{
return tthread::lock_guard<T>(*mutex);
}
#if defined(_LINUX)
static const string plugin_suffix = ".plug.so";
#elif defined(_DARWIN)
static const string plugin_suffix = ".plug.dylib";
#else
static const string plugin_suffix = ".plug.dll";
#endif
static string getPluginPath()
{
return Core::getInstance().getHackPath() + "plugins/";
}
static string getPluginPath (std::string name)
{
return getPluginPath() + name + plugin_suffix;
}
struct Plugin::RefLock struct Plugin::RefLock
{ {
RefLock() RefLock()
@ -156,18 +181,12 @@ struct Plugin::LuaEvent : public Lua::Event::Owner {
} }
}; };
Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) Plugin::Plugin(Core * core, const std::string & path,
const std::string &name, PluginManager * pm)
:name(name),
path(path),
parent(pm)
{ {
filename = filepath;
parent = pm;
name.reserve(_filename.size());
for(size_t i = 0; i < _filename.size();i++)
{
char ch = _filename[i];
if(ch == '.')
break;
name.append(1,ch);
}
plugin_lib = 0; plugin_lib = 0;
plugin_init = 0; plugin_init = 0;
plugin_globals = 0; plugin_globals = 0;
@ -191,6 +210,24 @@ Plugin::~Plugin()
delete access; delete access;
} }
const char *Plugin::getStateDescription (plugin_state state)
{
switch (state)
{
#define map(s, desc) case s: return desc; break;
map(PS_LOADED, "loaded")
map(PS_UNLOADED, "unloaded")
map(PS_BROKEN, "broken")
map(PS_LOADING, "loading")
map(PS_UNLOADING, "unloading")
map(PS_DELETED, "deleted")
#undef map
default:
return "unknown";
break;
}
}
bool Plugin::load(color_ostream &con) bool Plugin::load(color_ostream &con)
{ {
{ {
@ -199,8 +236,10 @@ bool Plugin::load(color_ostream &con)
{ {
return true; return true;
} }
else if(state != PS_UNLOADED) else if(state != PS_UNLOADED && state != PS_DELETED)
{ {
if (state == PS_BROKEN)
con.printerr("Plugin %s is broken - cannot be loaded\n", name.c_str());
return false; return false;
} }
state = PS_LOADING; state = PS_LOADING;
@ -208,16 +247,24 @@ bool Plugin::load(color_ostream &con)
// enter suspend // enter suspend
CoreSuspender suspend; CoreSuspender suspend;
// open the library, etc // open the library, etc
fprintf(stderr, "loading plugin %s\n", filename.c_str()); fprintf(stderr, "loading plugin %s\n", name.c_str());
DFLibrary * plug = OpenPlugin(filename.c_str()); DFLibrary * plug = OpenPlugin(path.c_str());
if(!plug) if(!plug)
{ {
con.printerr("Can't load plugin %s\n", filename.c_str());
RefAutolock lock(access); RefAutolock lock(access);
state = PS_BROKEN; if (!Filesystem::isfile(path))
return false; {
con.printerr("Plugin %s does not exist on disk\n", name.c_str());
state = PS_DELETED;
return false;
}
else {
con.printerr("Can't load plugin %s\n", name.c_str());
state = PS_UNLOADED;
return false;
}
} }
#define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_BROKEN #define plugin_abort_load ClosePlugin(plug); RefAutolock lock(access); state = PS_UNLOADED
#define plugin_check_symbol(sym) \ #define plugin_check_symbol(sym) \
if (!LookupPlugin(plug, sym)) \ if (!LookupPlugin(plug, sym)) \
{ \ { \
@ -226,14 +273,20 @@ bool Plugin::load(color_ostream &con)
return false; \ return false; \
} }
plugin_check_symbol("name") plugin_check_symbol("plugin_name")
plugin_check_symbol("version") plugin_check_symbol("plugin_version")
plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_self")
plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_init")
plugin_check_symbol("plugin_globals") plugin_check_symbol("plugin_globals")
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); if (name != *plug_name)
const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "git_description"); {
con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name);
plugin_abort_load;
return false;
}
const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version");
const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description");
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
const char *dfhack_version = Version::dfhack_version(); const char *dfhack_version = Version::dfhack_version();
const char *dfhack_git_desc = Version::git_description(); const char *dfhack_git_desc = Version::git_description();
@ -252,10 +305,7 @@ bool Plugin::load(color_ostream &con)
*plug_name, plug_git_desc, dfhack_git_desc); *plug_name, plug_git_desc, dfhack_git_desc);
} }
else else
{
con.printerr("Warning: Plugin %s missing git information\n", *plug_name); con.printerr("Warning: Plugin %s missing git information\n", *plug_name);
plug_git_desc = "unknown";
}
bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev"); bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev");
if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS")) if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS"))
{ {
@ -292,7 +342,6 @@ bool Plugin::load(color_ostream &con)
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby");
plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports"); plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports");
index_lua(plug); index_lua(plug);
this->name = *plug_name;
plugin_lib = plug; plugin_lib = plug;
commands.clear(); commands.clear();
if(plugin_init(con,commands) == CR_OK) if(plugin_init(con,commands) == CR_OK)
@ -303,6 +352,7 @@ bool Plugin::load(color_ostream &con)
if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled)
con.printerr("Plugin %s has no enabled var!\n", name.c_str()); con.printerr("Plugin %s has no enabled var!\n", name.c_str());
fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc); fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc);
fflush(stderr);
return true; return true;
} }
else else
@ -326,7 +376,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();
@ -364,11 +414,13 @@ bool Plugin::unload(color_ostream &con)
return false; return false;
} }
} }
else if(state == PS_UNLOADED) else if(state == PS_UNLOADED || state == PS_DELETED)
{ {
access->unlock(); access->unlock();
return true; return true;
} }
else if (state == PS_BROKEN)
con.printerr("Plugin %s is broken - cannot be unloaded\n", name.c_str());
access->unlock(); access->unlock();
return false; return false;
} }
@ -741,64 +793,155 @@ bool PluginExports::bind(DFLibrary *lib)
return true; return true;
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core) : core(core)
{ {
plugin_mutex = new recursive_mutex();
cmdlist_mutex = new mutex(); cmdlist_mutex = new mutex();
ruby = NULL; ruby = NULL;
} }
PluginManager::~PluginManager() PluginManager::~PluginManager()
{ {
for(size_t i = 0; i < all_plugins.size();i++) for (auto it = begin(); it != end(); ++it)
{ {
delete all_plugins[i]; Plugin *p = it->second;
delete p;
} }
all_plugins.clear(); all_plugins.clear();
delete plugin_mutex;
delete cmdlist_mutex; delete cmdlist_mutex;
} }
void PluginManager::init(Core * core) void PluginManager::init()
{ {
#ifdef LINUX_BUILD loadAll();
string path = core->getHackPath() + "plugins/"; }
#ifdef _DARWIN
const string searchstr = ".plug.dylib"; bool PluginManager::addPlugin(string name)
#else {
const string searchstr = ".plug.so"; if (all_plugins.find(name) != all_plugins.end())
#endif {
#else Core::printerr("Plugin already exists: %s\n", name.c_str());
string path = core->getHackPath() + "plugins\\"; return false;
const string searchstr = ".plug.dll"; }
#endif string path = getPluginPath(name);
vector <string> filez; if (!Filesystem::isfile(path))
Filesystem::listdir(path, filez); {
for(size_t i = 0; i < filez.size();i++) Core::printerr("Plugin does not exist: %s\n", name.c_str());
return false;
}
Plugin * p = new Plugin(core, path, name, this);
all_plugins[name] = p;
return true;
}
vector<string> PluginManager::listPlugins()
{
vector<string> results;
vector<string> files;
Filesystem::listdir(getPluginPath(), files);
for (auto file = files.begin(); file != files.end(); ++file)
{ {
if(hasEnding(filez[i],searchstr)) if (hasEnding(*file, plugin_suffix))
{ {
Plugin * p = new Plugin(core, path + filez[i], filez[i], this); string shortname = file->substr(0, file->find(plugin_suffix));
all_plugins.push_back(p); results.push_back(shortname);
// make all plugins load by default (until a proper design emerges).
p->load(core->getConsole());
} }
} }
return results;
}
void PluginManager::refresh()
{
MUTEX_GUARD(plugin_mutex);
auto files = listPlugins();
for (auto f = files.begin(); f != files.end(); ++f)
{
if (!(*this)[*f])
addPlugin(*f);
}
}
bool PluginManager::load (const string &name)
{
MUTEX_GUARD(plugin_mutex);
if (!(*this)[name] && !addPlugin(name))
return false;
Plugin *p = (*this)[name];
if (!p)
{
Core::printerr("Plugin failed to register: %s\n", name.c_str());
return false;
}
return p->load(core->getConsole());
} }
Plugin *PluginManager::getPluginByName (const std::string & name) bool PluginManager::loadAll()
{ {
for(size_t i = 0; i < all_plugins.size(); i++) MUTEX_GUARD(plugin_mutex);
auto files = listPlugins();
bool ok = true;
// load all plugins in hack/plugins
for (auto f = files.begin(); f != files.end(); ++f)
{ {
if(name == all_plugins[i]->name) if (!load(*f))
return all_plugins[i]; ok = false;
} }
return 0; return ok;
}
bool PluginManager::unload (const string &name)
{
MUTEX_GUARD(plugin_mutex);
if (!(*this)[name])
{
Core::printerr("Plugin does not exist: %s\n", name.c_str());
return false;
}
return (*this)[name]->unload(core->getConsole());
}
bool PluginManager::unloadAll()
{
MUTEX_GUARD(plugin_mutex);
bool ok = true;
// only try to unload plugins that are in all_plugins
for (auto it = begin(); it != end(); ++it)
{
if (!unload(it->first))
ok = false;
}
return ok;
}
bool PluginManager::reload (const string &name)
{
// equivalent to "unload(name); load(name);" if plugin is recognized,
// "load(name);" otherwise
MUTEX_GUARD(plugin_mutex);
if (!(*this)[name])
return load(name);
if (!unload(name))
return false;
return load(name);
}
bool PluginManager::reloadAll()
{
MUTEX_GUARD(plugin_mutex);
bool ok = true;
if (!unloadAll())
ok = false;
if (!loadAll())
ok = false;
return ok;
} }
Plugin *PluginManager::getPluginByCommand(const std::string &command) Plugin *PluginManager::getPluginByCommand(const std::string &command)
{ {
tthread::lock_guard<tthread::mutex> lock(*cmdlist_mutex); tthread::lock_guard<tthread::mutex> lock(*cmdlist_mutex);
map <string, Plugin *>::iterator iter = belongs.find(command); map <string, Plugin *>::iterator iter = command_map.find(command);
if (iter != belongs.end()) if (iter != command_map.end())
return iter->second; return iter->second;
else else
return NULL; return NULL;
@ -829,50 +972,71 @@ bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *
void PluginManager::OnUpdate(color_ostream &out) void PluginManager::OnUpdate(color_ostream &out)
{ {
for(size_t i = 0; i < all_plugins.size(); i++) for (auto it = begin(); it != end(); ++it)
{ it->second->on_update(out);
all_plugins[i]->on_update(out);
}
} }
void PluginManager::OnStateChange(color_ostream &out, state_change_event event) void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{ {
for(size_t i = 0; i < all_plugins.size(); i++) for (auto it = begin(); it != end(); ++it)
{ it->second->on_state_change(out, event);
all_plugins[i]->on_state_change(out, event);
}
} }
// FIXME: doesn't check name collisions!
void PluginManager::registerCommands( Plugin * p ) void PluginManager::registerCommands( Plugin * p )
{ {
cmdlist_mutex->lock(); cmdlist_mutex->lock();
vector <PluginCommand> & cmds = p->commands; vector <PluginCommand> & cmds = p->commands;
for(size_t i = 0; i < cmds.size();i++) for (size_t i = 0; i < cmds.size();i++)
{ {
std::string name = cmds[i].name; std::string name = cmds[i].name;
if (belongs.find(name) != belongs.end()) if (command_map.find(name) != command_map.end())
{ {
fprintf(stderr, "Plugin %s re-implements command \"%s\" (from plugin %s)\n", core->printerr("Plugin %s re-implements command \"%s\" (from plugin %s)\n",
p->getName().c_str(), name.c_str(), belongs[name]->getName().c_str()); p->getName().c_str(), name.c_str(), command_map[name]->getName().c_str());
continue;
} }
belongs[name] = p; command_map[name] = p;
} }
if (p->plugin_eval_ruby) if (p->plugin_eval_ruby)
ruby = p; ruby = p;
cmdlist_mutex->unlock(); cmdlist_mutex->unlock();
} }
// FIXME: doesn't check name collisions!
void PluginManager::unregisterCommands( Plugin * p ) void PluginManager::unregisterCommands( Plugin * p )
{ {
cmdlist_mutex->lock(); cmdlist_mutex->lock();
vector <PluginCommand> & cmds = p->commands; vector <PluginCommand> & cmds = p->commands;
for(size_t i = 0; i < cmds.size();i++) for(size_t i = 0; i < cmds.size();i++)
{ {
belongs.erase(cmds[i].name); command_map.erase(cmds[i].name);
} }
if (p->plugin_eval_ruby) if (p->plugin_eval_ruby)
ruby = NULL; ruby = NULL;
cmdlist_mutex->unlock(); cmdlist_mutex->unlock();
} }
Plugin *PluginManager::operator[] (std::string name)
{
MUTEX_GUARD(plugin_mutex);
if (all_plugins.find(name) == all_plugins.end())
{
if (Filesystem::isfile(getPluginPath(name)))
addPlugin(name);
}
return (all_plugins.find(name) != all_plugins.end()) ? all_plugins[name] : NULL;
}
size_t PluginManager::size()
{
return all_plugins.size();
}
std::map<std::string, Plugin*>::iterator PluginManager::begin()
{
return all_plugins.begin();
}
std::map<std::string, Plugin*>::iterator PluginManager::end()
{
return all_plugins.end();
}

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

@ -159,6 +159,11 @@ namespace DFHack
command_result runCommand(color_ostream &out, const std::string &command); command_result runCommand(color_ostream &out, const std::string &command);
bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false); bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false);
bool addScriptPath(std::string path, bool search_before = false);
bool removeScriptPath(std::string path);
std::string findScript(std::string name);
void getScriptPaths(std::vector<std::string> *dest);
bool ClearKeyBindings(std::string keyspec); bool ClearKeyBindings(std::string keyspec);
bool AddKeyBinding(std::string keyspec, std::string cmdline); bool AddKeyBinding(std::string keyspec, std::string cmdline);
std::vector<std::string> ListKeyBindings(std::string keyspec); std::vector<std::string> ListKeyBindings(std::string keyspec);
@ -231,6 +236,9 @@ namespace DFHack
std::vector <Module *> allModules; std::vector <Module *> allModules;
DFHack::PluginManager * plug_mgr; DFHack::PluginManager * plug_mgr;
std::vector<std::string> script_paths[2];
tthread::mutex *script_path_mutex;
// hotkey-related stuff // hotkey-related stuff
struct KeyBinding { struct KeyBinding {
int modifiers; int modifiers;

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

@ -30,7 +30,6 @@ distribution.
#include <map> #include <map>
#include "DataDefs.h" #include "DataDefs.h"
#include "PluginManager.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -39,7 +38,10 @@ distribution.
* Internal header file of the lua wrapper. * Internal header file of the lua wrapper.
*/ */
namespace DFHack { namespace LuaWrapper {
namespace DFHack {
struct FunctionReg;
namespace LuaWrapper {
struct LuaToken; struct LuaToken;
/** /**
@ -232,7 +234,7 @@ namespace DFHack { namespace LuaWrapper {
/** /**
* Wrap functions and add them to the table on the top of the stack. * Wrap functions and add them to the table on the top of the stack.
*/ */
using DFHack::FunctionReg; typedef DFHack::FunctionReg FunctionReg;
void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); void SetFunctionWrappers(lua_State *state, const FunctionReg *reg);
int method_wrapper_core(lua_State *state, function_identity_base *id); int method_wrapper_core(lua_State *state, function_identity_base *id);

@ -139,22 +139,25 @@ namespace DFHack
struct RefLock; struct RefLock;
struct RefAutolock; struct RefAutolock;
struct RefAutoinc; struct RefAutoinc;
enum plugin_state
{
PS_UNLOADED,
PS_LOADED,
PS_BROKEN,
PS_LOADING,
PS_UNLOADING
};
friend class PluginManager; friend class PluginManager;
friend class RPCService; friend class RPCService;
Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm); Plugin(DFHack::Core* core, const std::string& filepath,
const std::string &plug_name, PluginManager * pm);
~Plugin(); ~Plugin();
command_result on_update(color_ostream &out); command_result on_update(color_ostream &out);
command_result on_state_change(color_ostream &out, state_change_event event); command_result on_state_change(color_ostream &out, state_change_event event);
void detach_connection(RPCService *svc); void detach_connection(RPCService *svc);
public: public:
enum plugin_state
{
PS_UNLOADED,
PS_LOADED,
PS_BROKEN,
PS_LOADING,
PS_UNLOADING,
PS_DELETED
};
static const char *getStateDescription (plugin_state state);
bool load(color_ostream &out); bool load(color_ostream &out);
bool unload(color_ostream &out); bool unload(color_ostream &out);
bool reload(color_ostream &out); bool reload(color_ostream &out);
@ -183,6 +186,10 @@ namespace DFHack
{ {
return name; return name;
} }
plugin_state getState()
{
return state;
}
void open_lua(lua_State *state, int table); void open_lua(lua_State *state, int table);
@ -196,7 +203,7 @@ namespace DFHack
RefLock * access; RefLock * access;
std::vector <PluginCommand> commands; std::vector <PluginCommand> commands;
std::vector <RPCService*> services; std::vector <RPCService*> services;
std::string filename; std::string path;
std::string name; std::string name;
DFLibrary * plugin_lib; DFLibrary * plugin_lib;
PluginManager * parent; PluginManager * parent;
@ -247,34 +254,44 @@ namespace DFHack
friend class Plugin; friend class Plugin;
PluginManager(Core * core); PluginManager(Core * core);
~PluginManager(); ~PluginManager();
void init(Core* core); void init();
void OnUpdate(color_ostream &out); void OnUpdate(color_ostream &out);
void OnStateChange(color_ostream &out, state_change_event event); void OnStateChange(color_ostream &out, state_change_event event);
void registerCommands( Plugin * p ); void registerCommands( Plugin * p );
void unregisterCommands( Plugin * p ); void unregisterCommands( Plugin * p );
// PUBLIC METHODS // PUBLIC METHODS
public: public:
Plugin *getPluginByName (const std::string & name); // list names of all plugins present in hack/plugins
std::vector<std::string> listPlugins();
// create Plugin instances for any plugins in hack/plugins that aren't present in all_plugins
void refresh();
bool load (const std::string &name);
bool loadAll();
bool unload (const std::string &name);
bool unloadAll();
bool reload (const std::string &name);
bool reloadAll();
Plugin *getPluginByName (const std::string &name) { return (*this)[name]; }
Plugin *getPluginByCommand (const std::string &command); Plugin *getPluginByCommand (const std::string &command);
void *getPluginExports(const std::string &name); void *getPluginExports(const std::string &name);
command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top);
Plugin* operator[] (std::size_t index) Plugin* operator[] (const std::string name);
{ std::size_t size();
if(index >= all_plugins.size())
return 0;
return all_plugins[index];
};
std::size_t size()
{
return all_plugins.size();
}
Plugin *ruby; Plugin *ruby;
std::map<std::string, Plugin*>::iterator begin();
std::map<std::string, Plugin*>::iterator end();
// DATA // DATA
private: private:
Core *core;
bool addPlugin(std::string name);
tthread::recursive_mutex * plugin_mutex;
tthread::mutex * cmdlist_mutex; tthread::mutex * cmdlist_mutex;
std::map <std::string, Plugin *> belongs; std::map <std::string, Plugin*> command_map;
std::vector <Plugin *> all_plugins; std::map <std::string, Plugin*> all_plugins;
std::string plugin_path; std::string plugin_path;
}; };
@ -287,10 +304,10 @@ namespace DFHack
} }
}; };
#define DFHACK_PLUGIN_AUX(plugin_name, is_dev) \ #define DFHACK_PLUGIN_AUX(m_plugin_name, is_dev) \
DFhackDataExport const char * name = plugin_name;\ DFhackDataExport const char * plugin_name = m_plugin_name;\
DFhackDataExport const char * version = DFHack::Version::dfhack_version();\ DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\
DFhackDataExport const char * git_description = DFHack::Version::git_description();\ DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\
DFhackDataExport Plugin *plugin_self = NULL;\ DFhackDataExport Plugin *plugin_self = NULL;\
std::vector<std::string> _plugin_globals;\ std::vector<std::string> _plugin_globals;\
DFhackDataExport std::vector<std::string>* plugin_globals = &_plugin_globals; \ DFhackDataExport std::vector<std::string>* plugin_globals = &_plugin_globals; \
@ -298,9 +315,9 @@ namespace DFHack
/// You have to include DFHACK_PLUGIN("plugin_name") in every plugin you write - just once. Ideally at the top of the main file. /// You have to include DFHACK_PLUGIN("plugin_name") in every plugin you write - just once. Ideally at the top of the main file.
#ifdef DEV_PLUGIN #ifdef DEV_PLUGIN
#define DFHACK_PLUGIN(plugin_name) DFHACK_PLUGIN_AUX(plugin_name, true) #define DFHACK_PLUGIN(m_plugin_name) DFHACK_PLUGIN_AUX(m_plugin_name, true)
#else #else
#define DFHACK_PLUGIN(plugin_name) DFHACK_PLUGIN_AUX(plugin_name, false) #define DFHACK_PLUGIN(m_plugin_name) DFHACK_PLUGIN_AUX(m_plugin_name, false)
#endif #endif
#define DFHACK_PLUGIN_IS_ENABLED(varname) \ #define DFHACK_PLUGIN_IS_ENABLED(varname) \
@ -330,7 +347,11 @@ namespace DFHack
#define DFHACK_LUA_EVENT(name) { #name, &name##_event } #define DFHACK_LUA_EVENT(name) { #name, &name##_event }
#define DFHACK_LUA_END { NULL, NULL } #define DFHACK_LUA_END { NULL, NULL }
#define REQUIRE_GLOBAL(global_name) \
using df::global::global_name; \ #define REQUIRE_GLOBAL_NO_USE(global_name) \
static int VARIABLE_IS_NOT_USED CONCAT_TOKENS(required_globals_, __LINE__) = \ static int VARIABLE_IS_NOT_USED CONCAT_TOKENS(required_globals_, __LINE__) = \
(plugin_globals->push_back(#global_name), 0); (plugin_globals->push_back(#global_name), 0);
#define REQUIRE_GLOBAL(global_name) \
using df::global::global_name; \
REQUIRE_GLOBAL_NO_USE(global_name)

@ -420,24 +420,7 @@ local scripts = internal.scripts
local hack_path = dfhack.getHackPath() local hack_path = dfhack.getHackPath()
function dfhack.findScript(name) function dfhack.findScript(name)
local file return dfhack.internal.findScript(name .. '.lua')
file = dfhack.getSavePath()
if file then
file = file .. '/raw/scripts/' .. name .. '.lua'
if dfhack.filesystem.exists(file) then
return file
end
end
local path = dfhack.getDFPath()
file = path..'/raw/scripts/' .. name .. '.lua'
if dfhack.filesystem.exists(file) then
return file
end
file = path..'/hack/scripts/'..name..'.lua'
if dfhack.filesystem.exists(file) then
return file
end
return nil
end end
local valid_script_flags = { local valid_script_flags = {
@ -477,7 +460,7 @@ function dfhack.script_environment(name, strict)
if not scripts[path] or scripts[path]:needs_update() then if not scripts[path] or scripts[path]:needs_update() then
local _, env = dfhack.run_script_with_env(nil, name, { local _, env = dfhack.run_script_with_env(nil, name, {
module=true, module=true,
module_strict=strict and true or false -- ensure that this key is present if 'strict' is nil module_strict=(strict and true or false) -- ensure that this key is present if 'strict' is nil
}) })
return env return env
else else

@ -1 +1 @@
Subproject commit 5c4d627d7306b8425493f8ebbfe66c0f347f6871 Subproject commit 2a49a0761e4413603d9a72542077755cea2ad56d

@ -4,11 +4,11 @@ cd "${PWD}"
#thanks to Iriel for figuring this out #thanks to Iriel for figuring this out
OSREV=`uname -r | cut -d. -f1` OSREV=`uname -r | cut -d. -f1`
if [ "$OSREV" -ge 11 ] ; then if [ "$OSREV" -ge 11 ] ; then
export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs
export DYLD_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs export DYLD_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs
else else
export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs
export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs:${PWD}/hack/libs
fi fi
old_tty_settings=$(stty -g) old_tty_settings=$(stty -g)

@ -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)
@ -133,6 +135,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mode mode.cpp)
#DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(misery misery.cpp)
@ -143,7 +146,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(prospector prospector.cpp) DFHACK_PLUGIN(prospector prospector.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(regrass regrass.cpp) DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(remotefortressreader remotefortressreader.cpp PROTOBUFS RemoteFortressReader) DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp PROTOBUFS RemoteFortressReader)
DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename)
add_subdirectory(rendermax) add_subdirectory(rendermax)
DFHACK_PLUGIN(resume resume.cpp) DFHACK_PLUGIN(resume resume.cpp)
@ -159,6 +162,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()
@ -657,4 +654,4 @@ void Planner::cycleDefaultQuality(df::building_type type)
*quality = static_cast<df::item_quality>(*quality + 1); *quality = static_cast<df::item_quality>(*quality + 1);
if (*quality == item_quality::Artifact) if (*quality == item_quality::Artifact)
(*quality) = item_quality::Ordinary; (*quality) = item_quality::Ordinary;
} }

@ -2,6 +2,7 @@
#define BUILDINGPLAN_H #define BUILDINGPLAN_H
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h"
#include <functional> #include <functional>
@ -492,4 +493,4 @@ static Planner planner;
static RoomMonitor roomMonitor; static RoomMonitor roomMonitor;
#endif #endif

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

@ -330,6 +330,13 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event e)
{
if (e == SC_BEGIN_UNLOAD && Gui::getCurFocus() == "dfhack/commandprompt")
return CR_FAILURE;
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
return CR_OK; return CR_OK;

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

@ -31,7 +31,7 @@ command_result df_counters (color_ostream &out, vector <string> & parameters)
return CR_OK; return CR_OK;
} }
DFHACK_PLUGIN("probe"); DFHACK_PLUGIN("counters");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
@ -44,4 +44,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
return CR_OK; return CR_OK;
} }

@ -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), min_x + ((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:
}; };
}; };
void add_tool (EmbarkTool *t)
{
tools[t->getId()] = t;
}
bool tool_exists (std::string tool_name) bool tool_exists (std::string tool_name)
{ {
FOR_ITER_TOOLS(iter) return tools.find(tool_name) != tools.end();
{
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return true;
}
return false;
} }
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_FAILURE;
}
}
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;
};

@ -0,0 +1,67 @@
local _ENV = mkmodule('plugins.luasocket')
local _funcs={}
for k,v in pairs(_ENV) do
if type(v)=="function" then
_funcs[k]=v
_ENV[k]=nil
end
end
local socket=defclass(socket)
socket.ATTRS={
server_id=-1,
client_id=-1,
}
function socket:close( )
if self.client_id==-1 then
_funcs.lua_server_close(self.server_id)
else
_funcs.lua_client_close(self.server_id,self.client_id)
end
end
function socket:setTimeout( sec,msec )
msec=msec or 0
_funcs.lua_socket_set_timeout(self.server_id,self.client_id,sec,msec)
end
local client=defclass(client,socket)
function client:receive( pattern )
local pattern=pattern or "*l"
local bytes=-1
if type(pattern)== number then
bytes=pattern
end
local ret=_funcs.lua_client_receive(self.server_id,self.client_id,bytes,pattern,false)
if ret=="" then
return
else
return ret
end
end
function client:send( data )
_funcs.lua_client_send(self.server_id,self.client_id,data)
end
local server=defclass(server,socket)
function server:accept()
local id=_funcs.lua_server_accept(self.server_id,false)
if id~=nil then
return client{server_id=self.server_id,client_id=id}
else
return
end
end
tcp={}
function tcp:bind( address,port )
local id=_funcs.lua_socket_bind(address,port)
return server{server_id=id}
end
function tcp:connect( address,port )
local id=_funcs.lua_socket_connect(address,port)
return client{client_id=id}
end
--TODO garbage collect stuff
return _ENV

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

@ -0,0 +1,352 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include <vector>
#include <string>
#include <map>
#include <memory>
#include <PassiveSocket.h>
#include <ActiveSocket.h>
#include "MiscUtils.h"
#include "LuaTools.h"
#include "DataFuncs.h"
#include <stdexcept> //todo convert errors to lua-errors and co. Then remove this
using namespace DFHack;
using namespace df::enums;
struct server
{
CPassiveSocket *socket;
std::map<int,CActiveSocket*> clients;
int last_client_id;
void close();
};
std::map<int,server> servers;
typedef std::map<int,CActiveSocket*> clients_map;
clients_map clients; //free clients, i.e. non-server spawned clients
DFHACK_PLUGIN("luasocket");
// The error messages are taken from the clsocket source code
const char * translate_socket_error(CSimpleSocket::CSocketError err) {
switch (err) {
case CSimpleSocket::SocketError:
return "Generic socket error translates to error below.";
case CSimpleSocket::SocketSuccess:
return "No socket error.";
case CSimpleSocket::SocketInvalidSocket:
return "Invalid socket handle.";
case CSimpleSocket::SocketInvalidAddress:
return "Invalid destination address specified.";
case CSimpleSocket::SocketInvalidPort:
return "Invalid destination port specified.";
case CSimpleSocket::SocketConnectionRefused:
return "No server is listening at remote address.";
case CSimpleSocket::SocketTimedout:
return "Timed out while attempting operation.";
case CSimpleSocket::SocketEwouldblock:
return "Operation would block if socket were blocking.";
case CSimpleSocket::SocketNotconnected:
return "Currently not connected.";
case CSimpleSocket::SocketEinprogress:
return "Socket is non-blocking and the connection cannot be completed immediately";
case CSimpleSocket::SocketInterrupted:
return "Call was interrupted by a signal that was caught before a valid connection arrived.";
case CSimpleSocket::SocketConnectionAborted:
return "The connection has been aborted.";
case CSimpleSocket::SocketProtocolError:
return "Invalid protocol for operation.";
case CSimpleSocket::SocketFirewallError:
return "Firewall rules forbid connection.";
case CSimpleSocket::SocketInvalidSocketBuffer:
return "The receive buffer point outside the process's address space.";
case CSimpleSocket::SocketConnectionReset:
return "Connection was forcibly closed by the remote host.";
case CSimpleSocket::SocketAddressInUse:
return "Address already in use.";
case CSimpleSocket::SocketInvalidPointer:
return "Pointer type supplied as argument is invalid.";
case CSimpleSocket::SocketEunknown:
return "Unknown error please report to mark@carrierlabs.com";
default:
return "No such CSimpleSocket error";
}
}
void server::close()
{
for(auto it=clients.begin();it!=clients.end();it++)
{
CActiveSocket* sock=it->second;
sock->Close();
delete sock;
}
clients.clear();
socket->Close();
delete socket;
}
std::pair<CActiveSocket*,clients_map*> get_client(int server_id,int client_id)
{
std::map<int,CActiveSocket*>* target=&clients;
if(server_id>0)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
target=&cur_server.clients;
}
if(target->count(client_id)==0)
{
throw std::runtime_error("Client does with this id not exist");
}
CActiveSocket *sock=(*target)[client_id];
return std::make_pair(sock,target);
}
void handle_error(CSimpleSocket::CSocketError err,bool skip_timeout=true)
{
if(err==CSimpleSocket::SocketSuccess)
return;
if(err==CSimpleSocket::SocketTimedout && skip_timeout)
return;
throw std::runtime_error(translate_socket_error(err));
}
static int lua_socket_bind(std::string ip,int port)
{
static int server_id=0;
CPassiveSocket* sock=new CPassiveSocket;
if(!sock->Initialize())
{
CSimpleSocket::CSocketError err=sock->GetSocketError();
delete sock;
handle_error(err,false);
}
sock->SetBlocking();
if(!sock->Listen((uint8_t*)ip.c_str(),port))
{
handle_error(sock->GetSocketError(),false);
}
server_id++;
server& cur_server=servers[server_id];
cur_server.socket=sock;
cur_server.last_client_id=0;
return server_id;
}
static int lua_server_accept(int id,bool fail_on_timeout)
{
if(servers.count(id)==0)
{
throw std::runtime_error("Server not bound");
}
server &cur_server=servers[id];
CActiveSocket* sock=cur_server.socket->Accept();
if(!sock)
{
handle_error(sock->GetSocketError(),!fail_on_timeout);
return 0;
}
else
{
cur_server.last_client_id++;
cur_server.clients[cur_server.last_client_id]=sock;
return cur_server.last_client_id;
}
}
static void lua_client_close(int server_id,int client_id)
{
auto info=get_client(server_id,client_id);
CActiveSocket *sock=info.first;
std::map<int,CActiveSocket*>* target=info.second;
target->erase(client_id);
CSimpleSocket::CSocketError err=CSimpleSocket::SocketSuccess;
if(!sock->Close())
err=sock->GetSocketError();
delete sock;
if(err!=CSimpleSocket::SocketSuccess)
{
throw std::runtime_error(translate_socket_error(err));
}
}
static void lua_server_close(int server_id)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
try{
cur_server.close();
}
catch(...)
{
servers.erase(server_id);
throw;
}
}
static std::string lua_client_receive(int server_id,int client_id,int bytes,std::string pattern,bool fail_on_timeout)
{
auto info=get_client(server_id,client_id);
CActiveSocket *sock=info.first;
if(bytes>0)
{
if(sock->Receive(bytes)<=0)
{
throw std::runtime_error(translate_socket_error(sock->GetSocketError()));
}
return std::string((char*)sock->GetData(),bytes);
}
else
{
std::string ret;
if(pattern=="*a") //??
{
while(true)
{
int received=sock->Receive(1);
if(received<0)
{
handle_error(sock->GetSocketError(),!fail_on_timeout);
return "";//maybe return partial string?
}
else if(received==0)
{
break;
}
ret+=(char)*sock->GetData();
}
return ret;
}
else if (pattern=="" || pattern=="*l")
{
while(true)
{
if(sock->Receive(1)<=0)
{
handle_error(sock->GetSocketError(),!fail_on_timeout);
return "";//maybe return partial string?
}
char rec=(char)*sock->GetData();
if(rec=='\n')
break;
ret+=rec;
}
return ret;
}
else
{
throw std::runtime_error("Unsupported receive pattern");
}
}
}
static void lua_client_send(int server_id,int client_id,std::string data)
{
if(data.size()==0)
return;
std::map<int,CActiveSocket*>* target=&clients;
if(server_id>0)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
target=&cur_server.clients;
}
if(target->count(client_id)==0)
{
throw std::runtime_error("Client does with this id not exist");
}
CActiveSocket *sock=(*target)[client_id];
if(sock->Send((const uint8_t*)data.c_str(),data.size())!=data.size())
{
throw std::runtime_error(translate_socket_error(sock->GetSocketError()));
}
}
static int lua_socket_connect(std::string ip,int port)
{
static int last_client_id=0;
CActiveSocket* sock=new CActiveSocket;
if(!sock->Initialize())
{
CSimpleSocket::CSocketError err=sock->GetSocketError();
delete sock;
throw std::runtime_error(translate_socket_error(err));
}
if(!sock->Open((const uint8_t*)ip.c_str(),port))
{
CSimpleSocket::CSocketError err=sock->GetSocketError();
delete sock;
throw std::runtime_error(translate_socket_error(err));
}
last_client_id++;
clients[last_client_id]=sock;
return last_client_id;
}
static void lua_socket_set_timeout(int server_id,int client_id,int32_t sec,int32_t msec)
{
std::map<int,CActiveSocket*>* target=&clients;
if(server_id>0)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
if(client_id==-1)
{
cur_server.socket->SetConnectTimeout(sec,msec);
cur_server.socket->SetReceiveTimeout(sec,msec);
cur_server.socket->SetSendTimeout(sec,msec);
return;
}
target=&cur_server.clients;
}
if(target->count(client_id)==0)
{
throw std::runtime_error("Client does with this id not exist");
}
CActiveSocket *sock=(*target)[client_id];
sock->SetConnectTimeout(sec,msec);
sock->SetReceiveTimeout(sec,msec);
sock->SetSendTimeout(sec,msec);
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(lua_socket_bind), //spawn a server
DFHACK_LUA_FUNCTION(lua_socket_connect),//spawn a client (i.e. connection)
DFHACK_LUA_FUNCTION(lua_socket_set_timeout),
DFHACK_LUA_FUNCTION(lua_server_accept),
DFHACK_LUA_FUNCTION(lua_server_close),
DFHACK_LUA_FUNCTION(lua_client_close),
DFHACK_LUA_FUNCTION(lua_client_send),
DFHACK_LUA_FUNCTION(lua_client_receive),
DFHACK_LUA_END
};
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
for(auto it=clients.begin();it!=clients.end();it++)
{
CActiveSocket* sock=it->second;
sock->Close();
delete sock;
}
clients.clear();
for(auto it=servers.begin();it!=servers.end();it++)
{
it->second.close();
}
servers.clear();
return CR_OK;
}

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

@ -84,6 +84,14 @@ enum TiletypeVariant
VAR_4 = 3; VAR_4 = 3;
}; };
enum WorldPoles
{
NO_POLES = 0;
NORTH_POLE = 1;
SOUTH_POLE = 2;
BOTH_POLES = 3;
}
message Tiletype message Tiletype
{ {
required int32 id = 1; required int32 id = 1;
@ -169,6 +177,12 @@ message UnitDefinition
optional int32 pos_x = 3; optional int32 pos_x = 3;
optional int32 pos_y = 4; optional int32 pos_y = 4;
optional int32 pos_z = 5; optional int32 pos_z = 5;
optional MatPair race = 6;
optional ColorDefinition profession_color = 7;
optional uint32 flags1 = 8;
optional uint32 flags2 = 9;
optional uint32 flags3 = 10;
optional bool is_soldier = 11;
} }
message UnitList message UnitList
@ -231,3 +245,94 @@ message MapInfo
optional string world_name_english = 8; optional string world_name_english = 8;
optional string save_name = 9; optional string save_name = 9;
} }
enum FrontType
{
FRONT_NONE = 0;
FRONT_WARM = 1;
FRONT_COLD = 2;
FRONT_OCCLUDED = 3;
}
enum CumulusType
{
CUMULUS_NONE = 0;
CUMULUS_MEDIUM = 1;
CUMULUS_MULTI = 2;
CUMULUS_NIMBUS = 3;
}
enum StratusType
{
STRATUS_NONE = 0;
STRATUS_ALTO = 1;
STRATUS_PROPER = 2;
STRATUS_NIMBUS = 3;
}
enum FogType
{
FOG_NONE = 0;
FOG_MIST = 1;
FOG_NORMAL = 2;
F0G_THICK = 3;
}
message Cloud
{
optional FrontType front = 1;
optional CumulusType cumulus = 2;
optional bool cirrus = 3;
optional StratusType stratus = 4;
optional FogType fog = 5;
}
message WorldMap
{
required int32 world_width = 1;
required int32 world_height = 2;
optional string name = 3;
optional string name_english = 4;
repeated int32 elevation = 5;
repeated int32 rainfall = 6;
repeated int32 vegetation = 7;
repeated int32 temperature = 8;
repeated int32 evilness = 9;
repeated int32 drainage = 10;
repeated int32 volcanism = 11;
repeated int32 savagery = 12;
repeated Cloud clouds = 13;
repeated int32 salinity = 14;
optional int32 map_x = 15;
optional int32 map_y = 16;
}
message RegionMaps
{
repeated WorldMap world_maps = 1;
}
message CasteRaw
{
optional int32 index = 1;
optional string caste_id = 2;
repeated string caste_name = 3;
repeated string baby_name = 4;
repeated string child_name = 5;
}
message CreatureRaw
{
optional int32 index = 1;
optional string creature_id = 2;
repeated string name = 3;
repeated string general_baby_name = 4;
repeated string general_child_name = 5;
optional int32 creature_tile = 6;
optional int32 creature_soldier_tile = 7;
optional ColorDefinition color = 8;
optional int32 adultsize = 9;
repeated CasteRaw caste = 10;
}
message CreatureRawList
{
repeated CreatureRaw creature_raws = 1;
}

@ -1,7 +1,4 @@
#define DF_VERSION 40024
//define which version of DF this is being built for.
#define DF_VER_040
//#define DF_VER_034
// some headers required for a plugin. Nothing special, just the basics. // some headers required for a plugin. Nothing special, just the basics.
#include "Core.h" #include "Core.h"
@ -27,8 +24,10 @@
#include "df/builtin_mats.h" #include "df/builtin_mats.h"
#include "df/map_block_column.h" #include "df/map_block_column.h"
#include "df/plant.h" #include "df/plant.h"
#if DF_VERSION > 40001
#include "df/plant_tree_info.h" #include "df/plant_tree_info.h"
#include "df/plant_growth.h" #include "df/plant_growth.h"
#endif
#include "df/itemdef.h" #include "df/itemdef.h"
#include "df/building_def_workshopst.h" #include "df/building_def_workshopst.h"
#include "df/building_def_furnacest.h" #include "df/building_def_furnacest.h"
@ -39,9 +38,16 @@
#include "df/physical_attribute_type.h" #include "df/physical_attribute_type.h"
#include "df/mental_attribute_type.h" #include "df/mental_attribute_type.h"
#include <df/color_modifier_raw.h> #include "df/color_modifier_raw.h"
#include "df/region_map_entry.h"
#include "df/world_region_details.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/enabler.h"
//DFhack specific headers //DFhack specific headers
#include "modules/Maps.h" #include "modules/Maps.h"
@ -51,6 +57,7 @@
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/Units.h"
#include "TileTypes.h" #include "TileTypes.h"
#include "MiscUtils.h" #include "MiscUtils.h"
@ -67,7 +74,11 @@ using namespace RemoteFortressReader;
using namespace std; using namespace std;
DFHACK_PLUGIN("RemoteFortressReader"); DFHACK_PLUGIN("RemoteFortressReader");
#if DF_VERSION < 40024
using namespace df::global;
#else
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
#endif
// Here go all the command declarations... // Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom // mostly to allow having the mandatory stuff on top of the file and commands on the bottom
@ -84,6 +95,9 @@ static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in,
static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in); static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in);
static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out); static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out);
static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out); static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessage *in, BuildingList *out);
static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out);
static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out);
static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out);
void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos);
@ -132,6 +146,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
svc->addFunction("ResetMapHashes", ResetMapHashes); svc->addFunction("ResetMapHashes", ResetMapHashes);
svc->addFunction("GetItemList", GetItemList); svc->addFunction("GetItemList", GetItemList);
svc->addFunction("GetBuildingDefList", GetBuildingDefList); svc->addFunction("GetBuildingDefList", GetBuildingDefList);
svc->addFunction("GetWorldMap", GetWorldMap);
svc->addFunction("GetRegionMaps", GetRegionMaps);
svc->addFunction("GetCreatureRaws", GetCreatureRaws);
return svc; return svc;
} }
@ -163,6 +180,25 @@ uint16_t fletcher16(uint8_t const *data, size_t bytes)
return sum2 << 8 | sum1; return sum2 << 8 | sum1;
} }
void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out)
{
if (!df::global::enabler)
return;
auto enabler = df::global::enabler;
out->set_red((int)(enabler->ccolor[index][0] * 255));
out->set_green((int)(enabler->ccolor[index][1] * 255));
out->set_blue((int)(enabler->ccolor[index][2] * 255));
}
void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out)
{
int index = in[0] + 8 * in[1];
ConvertDfColor(index, out);
}
RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material) RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material material)
{ {
switch (material) switch (material)
@ -236,6 +272,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m
case df::enums::tiletype_material::RIVER: case df::enums::tiletype_material::RIVER:
return RemoteFortressReader::RIVER; return RemoteFortressReader::RIVER;
break; break;
#if DF_VERSION > 40001
case df::enums::tiletype_material::ROOT: case df::enums::tiletype_material::ROOT:
return RemoteFortressReader::ROOT; return RemoteFortressReader::ROOT;
break; break;
@ -248,6 +285,7 @@ RemoteFortressReader::TiletypeMaterial TranslateMaterial(df::tiletype_material m
case df::enums::tiletype_material::UNDERWORLD_GATE: case df::enums::tiletype_material::UNDERWORLD_GATE:
return RemoteFortressReader::UNDERWORLD_GATE; return RemoteFortressReader::UNDERWORLD_GATE;
break; break;
#endif
default: default:
return RemoteFortressReader::NO_MATERIAL; return RemoteFortressReader::NO_MATERIAL;
break; break;
@ -295,9 +333,11 @@ RemoteFortressReader::TiletypeSpecial TranslateSpecial(df::tiletype_special spec
case df::enums::tiletype_special::TRACK: case df::enums::tiletype_special::TRACK:
return RemoteFortressReader::TRACK; return RemoteFortressReader::TRACK;
break; break;
#if DF_VERSION > 40001
case df::enums::tiletype_special::SMOOTH_DEAD: case df::enums::tiletype_special::SMOOTH_DEAD:
return RemoteFortressReader::SMOOTH_DEAD; return RemoteFortressReader::SMOOTH_DEAD;
break; break;
#endif
default: default:
return RemoteFortressReader::NO_SPECIAL; return RemoteFortressReader::NO_SPECIAL;
break; break;
@ -351,20 +391,25 @@ RemoteFortressReader::TiletypeShape TranslateShape(df::tiletype_shape shape)
case df::enums::tiletype_shape::BROOK_TOP: case df::enums::tiletype_shape::BROOK_TOP:
return RemoteFortressReader::BROOK_TOP; return RemoteFortressReader::BROOK_TOP;
break; break;
#if DF_VERSION > 40001
case df::enums::tiletype_shape::BRANCH: case df::enums::tiletype_shape::BRANCH:
return RemoteFortressReader::BRANCH; return RemoteFortressReader::BRANCH;
break; break;
#ifdef DF_VER_034 #endif
#if DF_VERSION < 40001
case df::enums::tiletype_shape::TREE: case df::enums::tiletype_shape::TREE:
return RemoteFortressReader::TREE; return RemoteFortressReader::TREE_SHAPE;
break; break;
#endif #endif
#if DF_VERSION > 40001
case df::enums::tiletype_shape::TRUNK_BRANCH: case df::enums::tiletype_shape::TRUNK_BRANCH:
return RemoteFortressReader::TRUNK_BRANCH; return RemoteFortressReader::TRUNK_BRANCH;
break; break;
case df::enums::tiletype_shape::TWIG: case df::enums::tiletype_shape::TWIG:
return RemoteFortressReader::TWIG; return RemoteFortressReader::TWIG;
break; break;
#endif
case df::enums::tiletype_shape::SAPLING: case df::enums::tiletype_shape::SAPLING:
return RemoteFortressReader::SAPLING; return RemoteFortressReader::SAPLING;
break; break;
@ -622,6 +667,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i
basePlant->set_name(pp->name); basePlant->set_name(pp->name);
basePlant->mutable_mat_pair()->set_mat_type(-1); basePlant->mutable_mat_pair()->set_mat_type(-1);
basePlant->mutable_mat_pair()->set_mat_index(i); basePlant->mutable_mat_pair()->set_mat_index(i);
#if DF_VERSION > 40001
for (int g = 0; g < pp->growths.size(); g++) for (int g = 0; g < pp->growths.size(); g++)
{ {
df::plant_growth* growth = pp->growths[g]; df::plant_growth* growth = pp->growths[g];
@ -636,6 +682,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i
out_growth->mutable_mat_pair()->set_mat_index(i); out_growth->mutable_mat_pair()->set_mat_index(i);
} }
} }
#endif
} }
return CR_OK; return CR_OK;
} }
@ -741,7 +788,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
int max_x = in->max_x(); int max_x = in->max_x();
int max_y = in->max_y(); int max_y = in->max_y();
//stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z()); //stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z());
for (int zz = in->max_z()-1; zz >= in->min_z(); zz--) for (int zz = in->max_z() - 1; zz >= in->min_z(); zz--)
{ {
// (di, dj) is a vector - direction in which we move right now // (di, dj) is a vector - direction in which we move right now
int di = 1; int di = 1;
@ -860,6 +907,9 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in
int max_y = in->max_y() / 3; int max_y = in->max_y() / 3;
int max_z = in->max_z(); int max_z = in->max_z();
#if DF_VERSION < 40001
//plants are gotten differently here
#else
for (int xx = min_x; xx < max_x; xx++) for (int xx = min_x; xx < max_x; xx++)
for (int yy = min_y; yy < max_y; yy++) for (int yy = min_y; yy < max_y; yy++)
{ {
@ -894,6 +944,7 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in
out_plant->set_pos_z(plant->pos.z); out_plant->set_pos_z(plant->pos.z);
} }
} }
#endif
return CR_OK; return CR_OK;
} }
@ -908,6 +959,13 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in,
send_unit->set_pos_x(unit->pos.x); send_unit->set_pos_x(unit->pos.x);
send_unit->set_pos_y(unit->pos.y); send_unit->set_pos_y(unit->pos.y);
send_unit->set_pos_z(unit->pos.z); send_unit->set_pos_z(unit->pos.z);
send_unit->mutable_race()->set_mat_type(unit->race);
send_unit->mutable_race()->set_mat_index(unit->caste);
ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color());
send_unit->set_flags1(unit->flags1.whole);
send_unit->set_flags2(unit->flags2.whole);
send_unit->set_flags3(unit->flags3.whole);
send_unit->set_is_soldier(ENUM_ATTR(profession, military, unit->profession));
} }
return CR_OK; return CR_OK;
} }
@ -931,6 +989,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);
@ -1158,5 +1218,225 @@ static command_result GetBuildingDefList(color_ostream &stream, const EmptyMessa
break; break;
} }
} }
return CR_OK;
}
static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out)
{
if (!df::global::world->world_data)
{
out->set_world_width(0);
out->set_world_height(0);
return CR_FAILURE;
}
df::world_data * data = df::global::world->world_data;
int width = data->world_width;
int height = data->world_height;
out->set_world_width(width);
out->set_world_height(height);
out->set_name(Translation::TranslateName(&(data->name), false));
out->set_name_english(Translation::TranslateName(&(data->name), true));
for (int yy = 0; yy < height; yy++)
for (int xx = 0; xx < width; xx++)
{
df::region_map_entry * map_entry = &data->region_map[xx][yy];
out->add_elevation(map_entry->elevation);
out->add_rainfall(map_entry->rainfall);
out->add_vegetation(map_entry->vegetation);
out->add_temperature(map_entry->temperature);
out->add_evilness(map_entry->evilness);
out->add_drainage(map_entry->drainage);
out->add_volcanism(map_entry->volcanism);
out->add_savagery(map_entry->savagery);
out->add_salinity(map_entry->salinity);
auto clouds = out->add_clouds();
clouds->set_cirrus(map_entry->clouds.bits.cirrus);
clouds->set_cumulus((RemoteFortressReader::CumulusType)map_entry->clouds.bits.cumulus);
clouds->set_fog((RemoteFortressReader::FogType)map_entry->clouds.bits.fog);
clouds->set_front((RemoteFortressReader::FrontType)map_entry->clouds.bits.front);
clouds->set_stratus((RemoteFortressReader::StratusType)map_entry->clouds.bits.stratus);
}
return CR_OK;
}
static void AddAveragedRegionTiles(WorldMap * out, df::region_map_entry * e1, df::region_map_entry * e2, df::region_map_entry * e3, df::region_map_entry * e4)
{
out->add_rainfall((e1->rainfall + e2->rainfall + e3->rainfall + e4->rainfall) / 4);
out->add_vegetation((e1->vegetation + e2->vegetation + e3->vegetation + e4->vegetation) / 4);
out->add_temperature((e1->temperature + e2->temperature + e3->temperature + e4->temperature) / 4);
out->add_evilness((e1->evilness + e2->evilness + e3->evilness + e4->evilness) / 4);
out->add_drainage((e1->drainage + e2->drainage + e3->drainage + e4->drainage) / 4);
out->add_volcanism((e1->volcanism + e2->volcanism + e3->volcanism + e4->volcanism) / 4);
out->add_savagery((e1->savagery + e2->savagery + e3->savagery + e4->savagery) / 4);
out->add_salinity((e1->salinity + e2->salinity + e3->salinity + e4->salinity) / 4);
}
static void AddAveragedRegionTiles(WorldMap * out, df::region_map_entry * e1, df::region_map_entry * e2)
{
AddAveragedRegionTiles(out, e1, e1, e2, e2);
}
static void AddAveragedRegionTiles(WorldMap * out, df::region_map_entry * e1)
{
AddAveragedRegionTiles(out, e1, e1, e1, e1);
}
static void CopyLocalMap(df::world_data * worldData, df::world_region_details* worldRegionDetails, WorldMap * out)
{
int pos_x = worldRegionDetails->pos.x;
int pos_y = worldRegionDetails->pos.y;
out->set_map_x(pos_x);
out->set_map_y(pos_y);
out->set_world_width(17);
out->set_world_height(17);
char name[256];
sprintf(name, "Region %d, %d", pos_x, pos_y);
out->set_name_english(name);
out->set_name(name);
df::world_region_details * south = NULL;
df::world_region_details * east = NULL;
df::world_region_details * southEast = NULL;
for (int i = 0; i < worldData->region_details.size(); i++)
{
auto region = worldData->region_details[i];
if (region->pos.x == pos_x + 1 && region->pos.y == pos_y + 1)
southEast = region;
else if (region->pos.x == pos_x + 1 && region->pos.y == pos_y)
east = region;
else if (region->pos.x == pos_x && region->pos.y == pos_y + 1)
south = region;
}
df::region_map_entry * maps[] =
{
&worldData->region_map[pos_x][pos_y], &worldData->region_map[pos_x + 1][pos_y],
&worldData->region_map[pos_x][pos_y + 1], &worldData->region_map[pos_x + 1][pos_y + 1]
};
for (int yy = 0; yy < 17; yy++)
for (int xx = 0; xx < 17; xx++)
{
//This is because the bottom row doesn't line up.
if (xx == 16 && yy == 16 && southEast != NULL)
out->add_elevation(southEast->elevation[0][0]);
else if (xx == 16 && east != NULL)
out->add_elevation(east->elevation[0][yy]);
else if (yy == 16 && south != NULL)
out->add_elevation(south->elevation[xx][0]);
else
out->add_elevation(worldRegionDetails->elevation[xx][yy]);
switch (worldRegionDetails->biome[xx][yy])
{
case 1:
AddAveragedRegionTiles(out, maps[1]);
break;
case 2:
AddAveragedRegionTiles(out, maps[2], maps[3]);
break;
case 3:
AddAveragedRegionTiles(out, maps[3]);
break;
case 4:
AddAveragedRegionTiles(out, maps[0], maps[2]);
break;
case 5:
AddAveragedRegionTiles(out, maps[0], maps[1], maps[2], maps[3]);
break;
case 6:
AddAveragedRegionTiles(out, maps[1], maps[3]);
break;
case 7:
AddAveragedRegionTiles(out, maps[0]);
break;
case 8:
AddAveragedRegionTiles(out, maps[0], maps[1]);
break;
case 9:
AddAveragedRegionTiles(out, maps[2]);
break;
default:
AddAveragedRegionTiles(out, maps[0], maps[1], maps[2], maps[3]);
break;
}
}
}
static command_result GetRegionMaps(color_ostream &stream, const EmptyMessage *in, RegionMaps *out)
{
if (!df::global::world->world_data)
{
return CR_FAILURE;
}
df::world_data * data = df::global::world->world_data;
for (int i = 0; i < data->region_details.size(); i++)
{
df::world_region_details * region = data->region_details[i];
if (!region)
continue;
WorldMap * regionMap = out->add_world_maps();
CopyLocalMap(data, region, regionMap);
}
return CR_OK;
}
static command_result GetCreatureRaws(color_ostream &stream, const EmptyMessage *in, CreatureRawList *out)
{
if (!df::global::world)
return CR_FAILURE;
df::world * world = df::global::world;
for (int i = 0; i < world->raws.creatures.all.size(); i++)
{
df::creature_raw * orig_creature = world->raws.creatures.all[i];
auto send_creature = out->add_creature_raws();
send_creature->set_index(i);
send_creature->set_creature_id(orig_creature->creature_id);
send_creature->add_name(orig_creature->name[0]);
send_creature->add_name(orig_creature->name[1]);
send_creature->add_name(orig_creature->name[2]);
send_creature->add_general_baby_name(orig_creature->general_baby_name[0]);
send_creature->add_general_baby_name(orig_creature->general_baby_name[1]);
send_creature->add_general_child_name(orig_creature->general_child_name[0]);
send_creature->add_general_child_name(orig_creature->general_child_name[1]);
send_creature->set_creature_tile(orig_creature->creature_tile);
send_creature->set_creature_soldier_tile(orig_creature->creature_soldier_tile);
ConvertDfColor(orig_creature->color, send_creature->mutable_color());
send_creature->set_adultsize(orig_creature->adultsize);
for (int j = 0; j < orig_creature->caste.size(); j++)
{
auto orig_caste = orig_creature->caste[j];
if (!orig_caste)
continue;
auto send_caste = send_creature->add_caste();
send_caste->set_index(j);
send_caste->set_caste_id(orig_caste->caste_id);
send_caste->add_caste_name(orig_caste->caste_name[0]);
send_caste->add_caste_name(orig_caste->caste_name[1]);
send_caste->add_caste_name(orig_caste->caste_name[2]);
send_caste->add_baby_name(orig_caste->baby_name[0]);
send_caste->add_baby_name(orig_caste->baby_name[1]);
send_caste->add_child_name(orig_caste->child_name[0]);
send_caste->add_child_name(orig_caste->child_name[1]);
}
}
return CR_OK; return CR_OK;
} }

@ -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);
@ -346,7 +343,7 @@ static command_result stockflow_cmd(color_ostream &out, vector <string> & parame
desired = true; desired = true;
fast = true; fast = true;
} else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") { } else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") {
out.print("%s: %s\nUsage:\n%s", name, tagline, usage); out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage);
return CR_OK; return CR_OK;
} else if (parameters[0] == "list") { } else if (parameters[0] == "list") {
if (!enabled) { if (!enabled) {
@ -420,7 +417,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
enabled = true; enabled = true;
} }
commands.push_back(PluginCommand(name, tagline, stockflow_cmd, false, usage)); commands.push_back(PluginCommand(plugin_name, tagline, stockflow_cmd, false, usage));
return CR_OK; return CR_OK;
} }

@ -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);
++y; if (gps->dimy > 26)
++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);
++y; if (gps->dimy > 27)
++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;
}

@ -81,6 +81,7 @@
#include "tweaks/civ-agreement-ui.h" #include "tweaks/civ-agreement-ui.h"
#include "tweaks/craft-age-wear.h" #include "tweaks/craft-age-wear.h"
#include "tweaks/eggs-fertile.h" #include "tweaks/eggs-fertile.h"
#include "tweaks/embark-profile-name.h"
#include "tweaks/farm-plot-select.h" #include "tweaks/farm-plot-select.h"
#include "tweaks/fast-heat.h" #include "tweaks/fast-heat.h"
#include "tweaks/fast-trade.h" #include "tweaks/fast-trade.h"
@ -177,6 +178,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Fixes overlapping text on the \"view agreement\" screen\n" " Fixes overlapping text on the \"view agreement\" screen\n"
" tweak craft-age-wear [disable]\n" " tweak craft-age-wear [disable]\n"
" Makes cloth and leather items wear out at the correct rate (bug 6003).\n" " Makes cloth and leather items wear out at the correct rate (bug 6003).\n"
" tweak embark-profile-name [disable]\n"
" Allows the use of lowercase letters when saving embark profiles\n"
" tweak eggs-fertile [disable]\n" " tweak eggs-fertile [disable]\n"
" Displays a fertile/infertile indicator on nestboxes\n" " Displays a fertile/infertile indicator on nestboxes\n"
" tweak farm-plot-select [disable]\n" " tweak farm-plot-select [disable]\n"
@ -235,6 +238,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("eggs-fertile", egg_fertile_hook, render); TWEAK_HOOK("eggs-fertile", egg_fertile_hook, render);
TWEAK_HOOK("embark-profile-name", embark_profile_name_hook, feed);
TWEAK_HOOK("farm-plot-select", farm_select_hook, feed); TWEAK_HOOK("farm-plot-select", farm_select_hook, feed);
TWEAK_HOOK("farm-plot-select", farm_select_hook, render); TWEAK_HOOK("farm-plot-select", farm_select_hook, render);

@ -0,0 +1,22 @@
#include "df/viewscreen_setupdwarfgamest.h"
using namespace DFHack;
struct embark_profile_name_hook : df::viewscreen_setupdwarfgamest {
typedef df::viewscreen_setupdwarfgamest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
int ch = -1;
for (auto it = input->begin(); ch == -1 && it != input->end(); ++it)
ch = Screen::keyToChar(*it);
if (in_save_profile && ch >= 32 && ch <= 126)
{
profile_name.push_back((char)ch);
}
else
{
if (input->count(df::interface_key::LEAVESCREEN))
input->insert(df::interface_key::SETUPGAME_SAVE_PROFILE_ABORT);
INTERPOSE_NEXT(feed)(input);
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(embark_profile_name_hook, feed);

@ -23,26 +23,22 @@ 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? // Possible to plant?
if (ui->tasks.discovered_plants[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))
{ {
// Possible to plant? // Right depth?
df::plant_raw* raws = world->raws.plants.all[crop_id]; DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z);
if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season)) MapExtras::MapCache mc;
MapExtras::Block * b = mc.BlockAt(cursor / 16);
if (!b || !b->is_valid())
return false;
auto &block = *b->getRaw();
df::tile_designation &des =
block.designation[farm_plot->centerx % 16][farm_plot->centery % 16];
if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean)
{ {
// Right depth? return true;
DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z);
MapExtras::MapCache mc;
MapExtras::Block * b = mc.BlockAt(cursor / 16);
if (!b || !b->is_valid())
return false;
auto &block = *b->getRaw();
df::tile_designation &des =
block.designation[farm_plot->centerx % 16][farm_plot->centery % 16];
if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean)
{
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)
{ {

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

@ -0,0 +1,2 @@
include(Scripts.cmake)
DFHACK_3RDPARTY_SCRIPT_REPO(lethosor)

@ -0,0 +1,19 @@
include(../plugins/Plugins.cmake)
MACRO(DFHACK_SCRIPTS)
PARSE_ARGUMENTS(SCRIPT
"SUBDIRECTORY"
"SOME_OPT"
${ARGN}
)
CAR(SCRIPT_SUBDIRECTORY ${SCRIPT_SUBDIRECTORY})
install(FILES ${SCRIPT_DEFAULT_ARGS}
DESTINATION ${DFHACK_DATA_DESTINATION}/scripts/${SCRIPT_SUBDIRECTORY})
ENDMACRO()
MACRO(DFHACK_3RDPARTY_SCRIPT_REPO repo_path)
if(NOT EXISTS ${dfhack_SOURCE_DIR}/scripts/3rdparty/${repo_path}/CMakeLists.txt)
MESSAGE(FATAL_ERROR "Script submodule scripts/3rdparty/${repo_path} does not exist - run `git submodule update`.")
endif()
add_subdirectory(3rdparty/${repo_path})
ENDMACRO()

@ -1,3 +1,5 @@
# un-suspend construction jobs, on a recurring basis
class AutoUnsuspend class AutoUnsuspend
attr_accessor :running attr_accessor :running

@ -1,3 +1,5 @@
# convenient way to ban cooking categories of food
already_banned = {} already_banned = {}
kitchen = df.ui.kitchen kitchen = df.ui.kitchen
kitchen.item_types.length.times { |i| kitchen.item_types.length.times { |i|
@ -84,5 +86,3 @@ $script_args.each do |arg|
puts "ban-cooking seeds - bans cooking of plants that have seeds (tree seeds don't count)" puts "ban-cooking seeds - bans cooking of plants that have seeds (tree seeds don't count)"
end end
end end
# vim: et:sw=4:ts=4

@ -564,7 +564,8 @@ function export_site_maps()
end end
-- main() -- main()
if dfhack.gui.getCurFocus() == "legends" then if dfhack.gui.getCurFocus() == "legends" or dfhack.gui.getCurFocus() == "dfhack/lua/legends" then
-- either native legends mode, or using the open-legends.lua script
if args[1] == "all" then if args[1] == "all" then
export_legends_info() export_legends_info()
export_site_maps() export_site_maps()

@ -1,3 +1,5 @@
--removes unhappy thoughts due to lack of clothing
function fixnaked() function fixnaked()
local total_fixed = 0 local total_fixed = 0
local total_removed = 0 local total_removed = 0

@ -1,8 +1,24 @@
-- allows to do jobs in adv. mode. -- allows to do jobs in adv. mode.
--[==[ --[==[
version: 0.012 version: 0.03
changelog: changelog:
*0.031
- make forbiding optional (-s)afe mode
*0.03
- forbid doing anything in non-sites unless you are (-c)heating
- a bit more documentation and tidying
- add a deadlock fix
*0.021
- advfort_items now autofills items
- tried out few things to fix gather plants
*0.02
- fixed axles not being able to be placed in other direction (thanks SyrusLD)
- added lever linking
- restructured advfort_items, don't forget to update that too!
- Added clutter view if shop is cluttered.
*0.013
- fixed siege weapons and traps (somewhat). Now you can load them with new menu :)
*0.012 *0.012
- fix for some jobs not finding correct building. - fix for some jobs not finding correct building.
*0.011 *0.011
@ -21,6 +37,16 @@
- kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer)
- gather plants still not working... Other jobs seem to work. - gather plants still not working... Other jobs seem to work.
- added new-and-improved waiting. Interestingly it could be improved to be interuptable. - added new-and-improved waiting. Interestingly it could be improved to be interuptable.
todo list:
- document everything! Maybe somebody would understand what is happening then and help me :<
- when building trap add to known traps (or known adventurers?) so that it does not trigger on adventurer
bugs list:
- items blocking construction stuck the game
- burning charcoal crashed game
- gem thingies probably broken
- custom reactions semibroken
- gathering plants still broken
--]==] --]==]
--keybinding, change to your hearts content. Only the key part. --keybinding, change to your hearts content. Only the key part.
@ -47,6 +73,7 @@ build_filter.HUMANish={
forbid={} forbid={}
} }
--economic stone fix: just disable all of them
--[[ FIXME: maybe let player select which to disable?]] --[[ FIXME: maybe let player select which to disable?]]
for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end
@ -97,14 +124,17 @@ for k,v in ipairs({...}) do --setting parsing
if v=="-c" or v=="--cheat" then if v=="-c" or v=="--cheat" then
settings.build_by_items=true settings.build_by_items=true
settings.df_assign=false settings.df_assign=false
elseif v=="-s" or v=="--safe" then
settings.safe=true
elseif v=="-i" or v=="--inventory" then elseif v=="-i" or v=="--inventory" then
settings.check_inv=true settings.check_inv=true
settings.df_assign=false settings.df_assign=false
elseif v=="-a" or v=="--nodfassign" then elseif v=="-a" or v=="--nodfassign" then
settings.df_assign=false settings.df_assign=false
elseif v=="-h" or v=="--help" then
settings.help=true
else else
mode_name=v mode_name=v
end end
end end
@ -139,6 +169,12 @@ function showHelp()
Disclaimer(helptext) Disclaimer(helptext)
dialog.showMessage("Help!?!",helptext) dialog.showMessage("Help!?!",helptext)
end end
if settings.help then
showHelp()
return
end
--[[ Util functions ]]-- --[[ Util functions ]]--
function advGlobalPos() function advGlobalPos()
local map=df.global.world.map local map=df.global.world.map
@ -152,14 +188,13 @@ function advGlobalPos()
return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48) return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48)
end end
function inSite() function inSite()
local tx,ty=advGlobalPos() local tx,ty=advGlobalPos()
--print(tx,ty)
for k,v in pairs(df.global.world.world_data.sites) do for k,v in pairs(df.global.world.world_data.sites) do
local tp={v.pos.x,v.pos.y} local tp={v.pos.x,v.pos.y}
if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and
ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then
--print(k)
return v return v
end end
end end
@ -285,7 +320,8 @@ function SetWebRef(args)
local pos=args.pos local pos=args.pos
for k,v in pairs(df.global.world.items.other.ANY_WEBS) do for k,v in pairs(df.global.world.items.other.ANY_WEBS) do
if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then
job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) args.job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id})
return
end end
end end
end end
@ -460,7 +496,7 @@ function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog
local all=makeset{"w","h","d"} local all=makeset{"w","h","d"}
local needs={[btype.FarmPlot]=area,[btype.Bridge]=all, local needs={[btype.FarmPlot]=area,[btype.Bridge]=all,
[btype.RoadDirt]=area,[btype.RoadPaved]=area,[btype.ScrewPump]=makeset{"d"}, [btype.RoadDirt]=area,[btype.RoadPaved]=area,[btype.ScrewPump]=makeset{"d"},
[btype.AxleHorizontal]=makeset{"w","h"},[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}} [btype.AxleHorizontal]=all,[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}}
local myneeds=needs[args.type] local myneeds=needs[args.type]
if myneeds==nil then return end if myneeds==nil then return end
if args.width==nil and myneeds.w then if args.width==nil and myneeds.w then
@ -499,7 +535,6 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id)
last_building.custom=args.custom last_building.custom=args.custom
if chooseBuildingWidthHeightDir(args) then if chooseBuildingWidthHeightDir(args) then
return return
end end
--if settings.build_by_items then --if settings.build_by_items then
@ -530,7 +565,6 @@ function isSuitableItem(job_item,item)
--todo butcher test --todo butcher test
if job_item.item_type~=-1 then if job_item.item_type~=-1 then
if item:getType()~= job_item.item_type then if item:getType()~= job_item.item_type then
return false, "type" return false, "type"
elseif job_item.item_subtype~=-1 then elseif job_item.item_subtype~=-1 then
if item:getSubtype()~=job_item.item_subtype then if item:getSubtype()~=job_item.item_subtype then
@ -558,7 +592,7 @@ function isSuitableItem(job_item,item)
--print(matinfo:getCraftClass()) --print(matinfo:getCraftClass())
--print("Matching ",item," vs ",job_item) --print("Matching ",item," vs ",job_item)
if not matinfo:matches(job_item) then if type(job_item) ~= "table" and not matinfo:matches(job_item) then
--[[ --[[
local true_flags={} local true_flags={}
for k,v in pairs(job_item.flags1) do for k,v in pairs(job_item.flags1) do
@ -580,7 +614,7 @@ function isSuitableItem(job_item,item)
print(v) print(v)
end end
--]] --]]
return false,"matinfo" return false,"matinfo"
end end
-- some bonus checks: -- some bonus checks:
@ -595,8 +629,8 @@ function isSuitableItem(job_item,item)
end end
if job_item.min_dimension~=-1 then if job_item.min_dimension~=-1 then
end end
if #job_item.contains~=0 then -- if #job_item.contains~=0 then
end -- end
if job_item.has_tool_use~=-1 then if job_item.has_tool_use~=-1 then
if not item:hasToolUse(job_item.has_tool_use) then if not item:hasToolUse(job_item.has_tool_use) then
return false,"tool use" return false,"tool use"
@ -719,39 +753,30 @@ function finish_item_assign(args)
uncollected[1].is_fetching=1 uncollected[1].is_fetching=1
end end
end end
function AssignJobItems(args) function EnumItems_with_settings( args )
print("----")
if settings.df_assign then --use df default logic and hope that it would work
return true
end
-- first find items that you want to use for the job
local job=args.job
local its
if settings.check_inv then if settings.check_inv then
its=EnumItems{pos=args.from_pos,unit=args.unit, return EnumItems{pos=args.from_pos,unit=args.unit,
inv={[df.unit_inventory_item.T_mode.Hauled]=settings.use_worn,[df.unit_inventory_item.T_mode.Worn]=settings.use_worn, inv={[df.unit_inventory_item.T_mode.Hauled]=settings.use_worn,[df.unit_inventory_item.T_mode.Worn]=settings.use_worn,
[df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true} [df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true}
else else
its=EnumItems{pos=args.from_pos} return EnumItems{pos=args.from_pos}
end end
end
--[[while(#job.items>0) do --clear old job items function find_suitable_items(job,items,job_items)
job.items[#job.items-1]:delete() job_items=job_items or job.job_items
job.items:erase(#job.items-1)
end]]
local item_counts={} local item_counts={}
for job_id, trg_job_item in ipairs(job.job_items) do for job_id, trg_job_item in ipairs(job_items) do
item_counts[job_id]=trg_job_item.quantity item_counts[job_id]=trg_job_item.quantity
end end
local item_suitability={} local item_suitability={}
local used_item_id={} local used_item_id={}
for job_id, trg_job_item in ipairs(job.job_items) do for job_id, trg_job_item in ipairs(job_items) do
item_suitability[job_id]={} item_suitability[job_id]={}
for _,cur_item in pairs(its) do for _,cur_item in pairs(items) do
if not used_item_id[cur_item.id] then if not used_item_id[cur_item.id] then
local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) local item_suitable,msg=isSuitableItem(trg_job_item,cur_item)
if item_suitable or settings.build_by_items then if item_suitable or settings.build_by_items then
table.insert(item_suitability[job_id],cur_item) table.insert(item_suitability[job_id],cur_item)
@ -773,21 +798,32 @@ function AssignJobItems(args)
end end
end end
end end
print("before block")
return item_suitability,item_counts
end
function AssignJobItems(args)
if settings.df_assign then --use df default logic and hope that it would work
return true
end
-- first find items that you want to use for the job
local job=args.job
local its=EnumItems_with_settings(args)
local item_suitability,item_counts=find_suitable_items(job,its)
--[[while(#job.items>0) do --clear old job items
job.items[#job.items-1]:delete()
job.items:erase(#job.items-1)
end]]
if settings.gui_item_select and #job.job_items>0 then if settings.gui_item_select and #job.job_items>0 then
local item_dialog=require('hack.scripts.gui.advfort_items') local item_dialog=require('hack.scripts.gui.advfort_items')
--local rr=require('gui.script').start(function()
print("before dialog")
local ret=item_dialog.showItemEditor(job,item_suitability) local ret=item_dialog.showItemEditor(job,item_suitability)
print("post dialog",ret)
--showItemEditor(job,item_suitability)
if ret then if ret then
finish_item_assign(args) finish_item_assign(args)
return true return true
else else
print("Failed job, i'm confused...") print("Failed job, i'm confused...")
end end
--end) --end)
return false,"Selecting items" return false,"Selecting items"
else else
@ -803,8 +839,6 @@ function AssignJobItems(args)
return true return true
end end
end end
CheckAndFinishBuilding=function (args,bld) CheckAndFinishBuilding=function (args,bld)
@ -877,7 +911,125 @@ function ContinueJob(unit)
--unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!!
addJobAction(c_job,unit) addJobAction(c_job,unit)
end end
--TODO: in far far future maybe add real linking?
-- function assign_link_refs(args )
-- local job=args.job
-- --job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=args.building.id})
-- job.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=args.triggertarget.id})
-- printall(job)
-- end
-- function assign_link_roles( args )
-- if #args.job.items~=2 then
-- print("AAA FAILED!")
-- return false
-- end
-- args.job.items[0].role=df.job_item_ref.T_role.LinkToTarget
-- args.job.items[1].role=df.job_item_ref.T_role.LinkToTrigger
-- end
function fake_linking(lever,building,slots)
local item1=slots[1].items[1]
local item2=slots[2].items[1]
if not dfhack.items.moveToBuilding(item1,lever,2) then
qerror("failed to move item to building")
end
if not dfhack.items.moveToBuilding(item2,building,2) then
qerror("failed to move item2 to building")
end
item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id})
item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id})
lever.linked_mechanisms:insert("#",item2)
--fixes...
if building:getType()==df.building_type.Door then
building.door_flags.operated_by_mechanisms=true
end
dfhack.gui.showAnnouncement("Linked!",COLOR_YELLOW,true)
end
function LinkBuilding(args)
local bld=args.building or dfhack.buildings.findAtTile(args.pos)
args.building=bld
local lever_bld
if lever_id then --intentionally global!
lever_bld=df.building.find(lever_id)
if lever_bld==nil then
lever_id=nil
end
end
if lever_bld==nil then
if bld:getType()==df.building_type.Trap and bld:getSubtype()==df.trap_type.Lever then
lever_id=bld.id
dfhack.gui.showAnnouncement("Selected lever for linking",COLOR_YELLOW,true)
return
else
dfhack.gui.showAnnouncement("You first need a lever",COLOR_RED,true)
end
else
if lever_bld==bld then
dfhack.gui.showAnnouncement("Invalid target",COLOR_RED,true) --todo more invalid targets
return
end
-- args.job_type=df.job_type.LinkBuildingToTrigger
-- args.building=lever_bld
-- args.triggertarget=bld
-- args.pre_actions={
-- dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.item_type.TRAPPARTS},{quantity=1,item_type=df.item_type.TRAPPARTS}}}),
-- AssignJobItems,
-- assign_link_refs,}
-- args.post_actions={AssignBuildingRef,assign_link_roles}
-- makeJob(args)
local input_filter_defaults = { --stolen from buildings lua to better customize...
item_type = df.item_type.TRAPPARTS,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
flags2 = { allow_artifact = true },
flags3 = {},
flags4 = 0,
flags5 = 0,
reaction_class = '',
has_material_reaction_product = '',
metal_ore = -1,
min_dimension = -1,
has_tool_use = -1,
quantity = 1
}
local job_items={copyall(input_filter_defaults),copyall(input_filter_defaults)}
local its=EnumItems_with_settings(args)
local suitability=find_suitable_items(nil,its,job_items)
require('hack.scripts.gui.advfort_items').jobitemEditor{items=suitability,job_items=job_items,on_okay=dfhack.curry(fake_linking,lever_bld,bld)}:show()
lever_id=nil
end
--one item as LinkToTrigger role
--one item as LinkToTarget
--genref for holder(lever)
--genref for triggertarget
end
--[[ Plant gathering attemped fix No. 35]] --[=[ still did not work!]=]
function get_design_block_ev(blk)
for i,v in ipairs(blk.block_events) do
if v:getType()==df.block_square_event_type.designation_priority then
return v
end
end
end
function PlantGatherFix(args)
args.job.flags[17]=true --??
local pos=args.pos
local block=dfhack.maps.getTileBlock(pos)
local ev=get_design_block_ev(block)
if ev==nil then
block.block_events:insert("#",{new=df.block_square_event_designation_priorityst})
ev=block.block_events[#block.block_events-1]
end
ev.priority[pos.x % 16][pos.y % 16]=bit32.bor(ev.priority[pos.x % 16][pos.y % 16],4000)
args.job.item_category:assign{furniture=true,corpses=true,ammo=true} --this is actually required in fort mode
end
actions={ actions={
{"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}},
{"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}},
@ -895,7 +1047,7 @@ actions={
--{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}},
--{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}},
{"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}},
{"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare},{PlantGatherFix}},
{"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}},
{"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}},
{"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}},
@ -904,7 +1056,7 @@ actions={
{"BuildLast" ,BuildLast,{NoConstructedBuilding}}, {"BuildLast" ,BuildLast,{NoConstructedBuilding}},
{"Clean" ,df.job_type.Clean,{}}, {"Clean" ,df.job_type.Clean,{}},
{"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}},
{"Link Buildings" ,LinkBuilding,{IsBuilding}},
} }
for id,action in pairs(actions) do for id,action in pairs(actions) do
@ -922,7 +1074,23 @@ function usetool:getModeName()
else else
return actions[(mode or 0)+1][1] or " " return actions[(mode or 0)+1][1] or " "
end end
end
function usetool:update_site()
local site=inSite()
self.current_site=site
local site_label=self.subviews.siteLabel
if site then
site_label:itemById("site").text=dfhack.TranslateName(site.name)
else
if settings.safe then
site_label:itemById("site").text="<none, advfort disabled>"
else
site_label:itemById("site").text="<none, changes will not persist>"
end
end
end end
function usetool:init(args) function usetool:init(args)
@ -934,19 +1102,17 @@ function usetool:init(args)
} }
}, },
wid.Label{ wid.Label{
view_id="shopLabel", view_id="shopLabel",
frame = {l=35,xalign=0,yalign=0}, frame = {l=35,xalign=0,yalign=0},
visible=false, visible=false,
text={ text={
{id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}} {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{id="clutter"}}
}, },
wid.Label{ wid.Label{
view_id="siteLabel", view_id="siteLabel",
frame = {t=1,xalign=-1,yalign=0}, frame = {t=1,xalign=-1,yalign=0},
visible=false,
text={ text={
{id="text1", text="Site:"},{id="site", text="name"} {id="text1", text="Site:"},{id="site", text="name"}
} }
@ -956,6 +1122,7 @@ function usetool:init(args)
for i,v in ipairs(labors) do for i,v in ipairs(labors) do
labors[i]=true labors[i]=true
end end
self:update_site()
end end
MOVEMENT_KEYS = { MOVEMENT_KEYS = {
A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 },
@ -1007,40 +1174,37 @@ function onWorkShopJobChosen(args,idx,choice)
args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems}
makeJob(args) makeJob(args)
end end
function siegeWeaponActionChosen(building,actionid) function siegeWeaponActionChosen(args,actionid)
local args local building=args.building
if actionid==1 then if actionid==1 then --Turn
building.facing=(building.facing+1)%4 building.facing=(args.building.facing+1)%4
elseif actionid==2 then return
elseif actionid==2 then --Load
local action=df.job_type.LoadBallista local action=df.job_type.LoadBallista
if building:getSubtype()==df.siegeengine_type.Catapult then if building:getSubtype()==df.siegeengine_type.Catapult then
action=df.job_type.LoadCatapult action=df.job_type.LoadCatapult
args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1}}}),AssignJobItems} --TODO just boulders here
else
args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.SIEGEAMMO}}}),AssignJobItems}
end end
args={}
args.job_type=action args.job_type=action
args.unit=df.global.world.units.active[0] args.unit=df.global.world.units.active[0]
local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z}
args.from_pos=from_pos args.from_pos=from_pos
args.pos=from_pos args.pos=from_pos
args.pre_actions={dfhack.curry(setFiltersUp,{items={{}}})} elseif actionid==3 then --Fire
--issue a job...
elseif actionid==3 then
local action=df.job_type.FireBallista local action=df.job_type.FireBallista
if building:getSubtype()==df.siegeengine_type.Catapult then if building:getSubtype()==df.siegeengine_type.Catapult then
action=df.job_type.FireCatapult action=df.job_type.FireCatapult
end end
args={}
args.job_type=action args.job_type=action
args.unit=df.global.world.units.active[0] args.unit=df.global.world.units.active[0]
local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z}
args.from_pos=from_pos args.from_pos=from_pos
args.pos=from_pos args.pos=from_pos
--another job?
end
if args~=nil then
args.post_actions={AssignBuildingRef}
makeJob(args)
end end
args.post_actions={AssignBuildingRef}
makeJob(args)
end end
function putItemToBuilding(building,item) function putItemToBuilding(building,item)
if building:getType()==df.building_type.Table then if building:getType()==df.building_type.Table then
@ -1051,7 +1215,6 @@ function putItemToBuilding(building,item)
end end
end end
function usetool:openPutWindow(building) function usetool:openPutWindow(building)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local items=EnumItems{pos=adv.pos,unit=adv, local items=EnumItems{pos=adv.pos,unit=adv,
inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true, inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true,
@ -1063,16 +1226,15 @@ function usetool:openPutWindow(building)
dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end)
end end
function usetool:openSiegeWindow(building) function usetool:openSiegeWindow(building)
local args={building=building,screen=self}
dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"},
dfhack.curry(siegeWeaponActionChosen,building)) dfhack.curry(siegeWeaponActionChosen,args))
end end
function usetool:onWorkShopButtonClicked(building,index,choice) function usetool:onWorkShopButtonClicked(building,index,choice)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local args={unit=adv,building=building} local args={unit=adv,building=building}
if df.interface_button_building_new_jobst:is_instance(choice.button) then if df.interface_button_building_new_jobst:is_instance(choice.button) then
print("pre-click")
choice.button:click() choice.button:click()
print("post-click",#building.jobs)
if #building.jobs>0 then if #building.jobs>0 then
local job=building.jobs[#building.jobs-1] local job=building.jobs[#building.jobs-1]
args.job=job args.job=job
@ -1106,7 +1268,7 @@ function usetool:openShopWindowButtoned(building,no_reset)
--]] --]]
end end
building:fillSidebarMenu() building:fillSidebarMenu()
local list={} local list={}
for id,choice in pairs(wui.choices_visible) do for id,choice in pairs(wui.choices_visible) do
table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice}) table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice})
@ -1122,7 +1284,7 @@ function usetool:openShopWindowButtoned(building,no_reset)
end end
function usetool:openShopWindow(building) function usetool:openShopWindow(building)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType())
if filter_pile then if filter_pile then
local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building
@ -1159,17 +1321,17 @@ function usetool:armCleanTrap(building)
end end
--building.trap_type==df.trap_type.PressurePlate then --building.trap_type==df.trap_type.PressurePlate then
--settings/link --settings/link
local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos, local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,
building=building,job_type=df.job_type.CleanTrap} building=building,job_type=df.job_type.CleanTrap}
if building.trap_type==df.trap_type.CageTrap then if building.trap_type==df.trap_type.CageTrap then
args.job_type=df.job_type.LoadCageTrap args.job_type=df.job_type.LoadCageTrap
local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} }
args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems}
elseif building.trap_type==df.trap_type.StoneFallTrap then elseif building.trap_type==df.trap_type.StoneFallTrap then
args.job_type=df.job_type.LoadStoneTrap args.job_type=df.job_type.LoadStoneTrap
local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} } local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} }
args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems}
elseif building.trap_type==df.trap_type.WeaponTrap then elseif building.trap_type==df.trap_type.WeaponTrap then
qerror("TODO") qerror("TODO")
else else
@ -1181,16 +1343,16 @@ function usetool:armCleanTrap(building)
end end
function usetool:hiveActions(building) function usetool:hiveActions(building)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos, local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,
from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self} from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self}
local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} }
args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems}
makeJob(args) makeJob(args)
--InstallColonyInHive, --InstallColonyInHive,
--CollectHiveProducts, --CollectHiveProducts,
end end
function usetool:operatePump(building) function usetool:operatePump(building)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self} makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self}
end end
@ -1205,7 +1367,7 @@ function usetool:farmPlot(building)
end end
end end
--check if there tile is without plantseeds,add job --check if there tile is without plantseeds,add job
local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self} local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self}
if do_harvest then if do_harvest then
args.job_type=df.job_type.HarvestPlants args.job_type=df.job_type.HarvestPlants
@ -1298,6 +1460,11 @@ function usetool:shopMode(enable,mode,building)
self.subviews.shopLabel.visible=enable self.subviews.shopLabel.visible=enable
if mode then if mode then
self.subviews.shopLabel:itemById("text1").text=mode.name self.subviews.shopLabel:itemById("text1").text=mode.name
if building:getClutterLevel()<=1 then
self.subviews.shopLabel:itemById("clutter").text=""
else
self.subviews.shopLabel:itemById("clutter").text=" Clutter:"..tostring(building:getClutterLevel())
end
self.building=building self.building=building
end end
self.mode=mode self.mode=mode
@ -1333,6 +1500,13 @@ function usetool:setupFields()
ui.site_id=site.id ui.site_id=site.id
end end
end end
function usetool:siteCheck()
if self.site ~= nil or not settings.safe then --TODO: add check if it's correct site (the persistant ones)
return true
end
return false, "You are not on site"
end
--movement and co... Also passes on allowed keys
function usetool:fieldInput(keys) function usetool:fieldInput(keys)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local cur_mode=actions[(mode or 0)+1] local cur_mode=actions[(mode or 0)+1]
@ -1340,24 +1514,40 @@ function usetool:fieldInput(keys)
for code,_ in pairs(keys) do for code,_ in pairs(keys) do
--print(code) --print(code)
if MOVEMENT_KEYS[code] then if MOVEMENT_KEYS[code] then
local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code],
from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_actions=cur_mode[4],pre_actions=cur_mode[5],job_type=cur_mode[2],screen=self} local state={
if code=="SELECT" then unit=adv,
pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),
dir=MOVEMENT_KEYS[code],
from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},
post_actions=cur_mode[4],
pre_actions=cur_mode[5],
job_type=cur_mode[2],
screen=self}
if code=="SELECT" then --do job in the distance, TODO: check if you can still cheat-mine (and co.) remotely
if df.global.cursor.x~=-30000 then if df.global.cursor.x~=-30000 then
state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z}
else else
break break
end end
end end
for _,p in pairs(cur_mode[3] or {}) do --First check site
local ok,msg=p(state) local ok,msg=self:siteCheck() --TODO: some jobs might be possible without a site?
if ok==false then if not ok then
dfhack.gui.showAnnouncement(msg,5,1) dfhack.gui.showAnnouncement(msg,5,1)
failed=true failed=true
else
for _,p in pairs(cur_mode[3] or {}) do --then check predicates
local ok,msg=p(state)
if not ok then
dfhack.gui.showAnnouncement(msg,5,1)
failed=true
end
end end
end end
if not failed then if not failed then
local ok,msg local ok,msg
if type(cur_mode[2])=="function" then if type(cur_mode[2])=="function" then
@ -1365,9 +1555,9 @@ function usetool:fieldInput(keys)
else else
makeJob(state) makeJob(state)
--(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4])
end end
if code=="SELECT" then if code=="SELECT" then
self:sendInputToParent("LEAVESCREEN") self:sendInputToParent("LEAVESCREEN")
end end
@ -1381,25 +1571,27 @@ function usetool:fieldInput(keys)
end end
end end
end end
end end
function usetool:onInput(keys) function usetool:onInput(keys)
local adv=df.global.world.units.active[0]
self:update_site()
local adv=df.global.world.units.active[0]
if keys.LEAVESCREEN then if keys.LEAVESCREEN then
if df.global.cursor.x~=-30000 then if df.global.cursor.x~=-30000 then --if not poiting at anything
self:sendInputToParent("LEAVESCREEN") self:sendInputToParent("LEAVESCREEN") --leave poiting
else else
self:dismiss() self:dismiss() --leave the adv-tools all together
CancelJob(adv) CancelJob(adv)
end end
elseif keys[keybinds.nextJob.key] then elseif keys[keybinds.nextJob.key] then --next job with looping
mode=(mode+1)%#actions mode=(mode+1)%#actions
elseif keys[keybinds.prevJob.key] then elseif keys[keybinds.prevJob.key] then --prev job with looping
mode=mode-1 mode=mode-1
if mode<0 then mode=#actions-1 end if mode<0 then mode=#actions-1 end
--elseif keys.A_LOOK then
-- self:sendInputToParent("A_LOOK")
elseif keys["A_SHORT_WAIT"] then elseif keys["A_SHORT_WAIT"] then
--ContinueJob(adv) --ContinueJob(adv)
self:sendInputToParent("A_SHORT_WAIT") self:sendInputToParent("A_SHORT_WAIT")
@ -1417,14 +1609,7 @@ function usetool:onInput(keys)
self:fieldInput(keys) self:fieldInput(keys)
end end
end end
local site=inSite()
if site then
self.subviews.siteLabel.visible=true
self.subviews.siteLabel:itemById("site").text=dfhack.TranslateName(site.name)
else
self.subviews.siteLabel.visible=false
end
end end
function usetool:onIdle() function usetool:onIdle()
@ -1432,7 +1617,19 @@ function usetool:onIdle()
local job_ptr=adv.job.current_job local job_ptr=adv.job.current_job
local job_action=findAction(adv,df.unit_action_type.Job) local job_action=findAction(adv,df.unit_action_type.Job)
if self.long_wait and self.long_wait_timer==nil then
self.long_wait_timer=1000 --TODO tweak this
end
if job_ptr and self.long_wait and not job_action then if job_ptr and self.long_wait and not job_action then
if self.long_wait_timer<=0 then --fix deadlocks with force-canceling of waiting
self.long_wait_timer=nil
self.long_wait=false
else
self.long_wait_timer=self.long_wait_timer-1
end
if adv.job.current_job.completion_timer==-1 then if adv.job.current_job.completion_timer==-1 then
self.long_wait=false self.long_wait=false
end end

@ -12,8 +12,10 @@ jobitemEditor.ATTRS{
allow_remove=false, allow_remove=false,
allow_any_item=false, allow_any_item=false,
job=DEFAULT_NIL, job=DEFAULT_NIL,
job_items=DEFAULT_NIL,
items=DEFAULT_NIL, items=DEFAULT_NIL,
on_okay=DEFAULT_NIL, on_okay=DEFAULT_NIL,
autofill=true,
} }
function update_slot_text(slot) function update_slot_text(slot)
local items="" local items=""
@ -29,7 +31,7 @@ end
--items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref) --items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref)
function jobitemEditor:init(args) function jobitemEditor:init(args)
--self.job=args.job --self.job=args.job
if self.job==nil then qerror("This screen must have job target") end if self.job==nil and self.job_items==nil then qerror("This screen must have job target or job_items list") end
if self.items==nil then qerror("This screen must have item list") end if self.items==nil then qerror("This screen must have item list") end
self:addviews{ self:addviews{
@ -74,6 +76,9 @@ function jobitemEditor:init(args)
} }
self.assigned={} self.assigned={}
self:fill() self:fill()
if self.autofill then
self:fill_slots()
end
end end
function jobitemEditor:get_slot() function jobitemEditor:get_slot()
local idx,choice=self.subviews.itemList:getSelected() local idx,choice=self.subviews.itemList:getSelected()
@ -104,6 +109,23 @@ function jobitemEditor:add_item()
end end
) )
end end
function jobitemEditor:fill_slots()
for i,v in ipairs(self.slots) do
while v.filled_amount<v.job_item.quantity do
local added=false
for _,it in ipairs(v.choices) do
if not self.assigned[it.id] then
self:add_item_to_slot(v,it)
added=true
break
end
end
if not added then
break
end
end
end
end
function jobitemEditor:add_item_to_slot(slot,item) function jobitemEditor:add_item_to_slot(slot,item)
table.insert(slot.items,item) table.insert(slot.items,item)
slot.filled_amount=slot.filled_amount+item:getTotalDimension() slot.filled_amount=slot.filled_amount+item:getTotalDimension()
@ -124,7 +146,15 @@ end
function jobitemEditor:fill() function jobitemEditor:fill()
self.slots={} self.slots={}
for k,v in pairs(self.items) do for k,v in pairs(self.items) do
table.insert(self.slots,{job_item=self.job.job_items[k], id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots}) local job_item
if self.job then
job_item=self.job.job_items[k]
else
job_item=self.job_items[k]
end
table.insert(self.slots,{job_item=job_item, id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots})
update_slot_text(self.slots[#self.slots]) update_slot_text(self.slots[#self.slots])
end end
self.subviews.itemList:setChoices(self.slots) self.subviews.itemList:setChoices(self.slots)
@ -139,13 +169,15 @@ function jobitemEditor:jobValid()
return true return true
end end
function jobitemEditor:commit() function jobitemEditor:commit()
for _,slot in pairs(self.slots) do if self.job then
for _1,cur_item in pairs(slot.items) do for _,slot in pairs(self.slots) do
self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id}) for _1,cur_item in pairs(slot.items) do
self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id})
end
end end
end end
self:dismiss() self:dismiss()
if self.on_okay then self:on_okay() end if self.on_okay then self.on_okay(self.slots) end
end end
function showItemEditor(job,item_selections) function showItemEditor(job,item_selections)

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

@ -1,8 +1,87 @@
-- dfstatus 1.5 - a quick access status screen. -- a quick access status screen
-- originally written by enjia2000@gmail.com -- originally written by enjia2000@gmail.com (stolencatkarma)
local gui = require 'gui' local gui = require 'gui'
function warn(msg)
dfhack.color(COLOR_LIGHTRED)
print(msg)
dfhack.color(nil)
end
config = {
flags = {
drink = true,
wood = true,
fuel = true,
prepared_meals = true,
tanned_hides = true,
cloth = true,
metals = true,
},
metal_ids = {},
}
function parse_config()
local metal_map = {}
for id, raw in pairs(df.global.world.raws.inorganics) do
if raw.material.flags.IS_METAL then
metal_map[raw.id:upper()] = id
metal_map[id] = raw.id:upper()
end
end
local function add_metal(...)
for _, m in pairs({...}) do
id = metal_map[tostring(m):upper()]
if id ~= nil then
table.insert(config.metal_ids, id)
elseif m == '-' then
table.insert(config.metal_ids, '-')
else
warn('Invalid metal: ' .. tostring(m))
end
end
return add_metal
end
local env = {}
setmetatable(env, {
__index = function(_, k)
if k == 'metal' or k == 'metals' then
return add_metal
elseif k == 'flags' then
return config.flags
else
error('unknown name: ' .. k, 2)
end
end,
__newindex = function(_, k, v)
if config.flags[k] ~= nil then
if v ~= nil then
config.flags[k] = v
else
config.flags[k] = false
end
else
error('unknown flag: ' .. k, 2)
end
end,
})
local f, err = loadfile('dfhack-config/dfstatus.lua', 't', env)
if not f then
qerror('error loading config: ' .. err)
end
local ok, err = pcall(f)
if not ok then
qerror('error parsing config: ' .. err)
end
end
function getInorganicName(id)
return (df.inorganic_raw.find(id).material.state_name.Solid:gsub('^[a-z]', string.upper))
end
dfstatus = defclass(dfstatus, gui.FramedScreen) dfstatus = defclass(dfstatus, gui.FramedScreen)
dfstatus.ATTRS = { dfstatus.ATTRS = {
frame_style = gui.GREY_LINE_FRAME, frame_style = gui.GREY_LINE_FRAME,
@ -13,95 +92,126 @@ dfstatus.ATTRS = {
focus_path = 'dfstatus', focus_path = 'dfstatus',
} }
function dfstatus:onRenderBody(dc) function dfstatus:init()
self.text = {}
self.start = 1
local function write(line)
table.insert(self.text, line)
-- ensure that the window is wide enough for this line plus a scroll arrow
if #line + 1 > self.frame_width then
self.frame_width = #line + 1
end
end
local function newline() write('') end
local f = config.flags
local drink = 0 local drink = 0
local wood = 0 local wood = 0
--local meat = 0
--local raw_fish = 0
--local plants = 0
local prepared_meals = 0
local fuel = 0 local fuel = 0
local pigiron = 0
local iron = 0
local steel = 0
local silver = 0
local copper = 0
local gold = 0
local tannedhides = 0 local prepared_meals = 0
local tanned_hides = 0
local cloth = 0 local cloth = 0
local metals = {}
for _, id in pairs(config.metal_ids) do
metals[id] = 0
end
for _, item in ipairs(df.global.world.items.all) do for _, item in ipairs(df.global.world.items.all) do
if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then
if (item:getType() == df.item_type.WOOD) then wood = wood + item:getStackSize() if item:getType() == df.item_type.WOOD then
elseif (item:getType() == df.item_type.DRINK) then drink = drink + item:getStackSize() wood = wood + item:getStackSize()
elseif (item:getType() == df.item_type.SKIN_TANNED) then tannedhides = tannedhides + item:getStackSize() elseif item:getType() == df.item_type.DRINK then
elseif (item:getType() == df.item_type.CLOTH) then cloth = cloth + item:getStackSize() drink = drink + item:getStackSize()
--elseif (item:getType() == df.item_type.MEAT) then meat = meat + item:getStackSize() elseif item:getType() == df.item_type.SKIN_TANNED then
--elseif (item:getType() == df.item_type.FISH_RAW) then raw_fish = raw_fish + item:getStackSize() tanned_hides = tanned_hides + item:getStackSize()
--elseif (item:getType() == df.item_type.PLANT) then plants = plants + item:getStackSize() elseif item:getType() == df.item_type.CLOTH then
elseif (item:getType() == df.item_type.FOOD) then prepared_meals = prepared_meals + item:getStackSize() cloth = cloth + item:getStackSize()
elseif (item:getType() == df.item_type.BAR) then elseif item:getType() == df.item_type.FOOD then
for token in string.gmatch(dfhack.items.getDescription(item,0),"[^%s]+") do prepared_meals = prepared_meals + item:getStackSize()
if (token == "silver") then silver = silver + item:getStackSize() elseif item:getType() == df.item_type.BAR then
elseif (token == "charcoal" or token == "coke") then fuel = fuel + item:getStackSize() if item:getMaterial() == df.builtin_mats.COAL then
elseif (token == "iron") then iron = iron + item:getStackSize() fuel = fuel + item:getStackSize()
elseif (token == "pig") then pigiron = pigiron + item:getStackSize() elseif item:getMaterial() == df.builtin_mats.INORGANIC then
elseif (token == "copper") then copper = copper + item:getStackSize() local mat_idx = item:getMaterialIndex()
elseif (token == "gold") then gold = gold + item:getStackSize() if metals[mat_idx] ~= nil then
elseif (token == "steel") then steel = steel + item:getStackSize() metals[mat_idx] = metals[mat_idx] + item:getStackSize()
end end
break -- only need to look at the 1st token of each item.
end end
end end
end end
end end
if f.drink then
write("Drinks: " .. drink)
end
if f.prepared_meals then
write("Meals: " .. prepared_meals)
end
if f.drink or f.prepared_meals then
newline()
end
if f.wood then
write("Wood: " .. wood)
end
if f.fuel then
write("Fuel: " .. fuel)
end
if f.wood or f.fuel then
newline()
end
if f.tanned_hides then
write("Hides: " .. tanned_hides)
end
if f.cloth then
write("Cloth: " .. cloth)
end
if f.tanned_hides or f.cloth then
newline()
end
if f.metals then
write("Metal bars:")
for _, id in pairs(config.metal_ids) do
if id == '-' then
newline()
else
write(' ' .. ('%-10s'):format(getInorganicName(id) .. ': ') .. metals[id])
end
end
end
self.start_min = 1
self.start_max = #self.text - self.frame_height + 1
end
function dfstatus:onRenderBody(dc)
dc:pen(COLOR_LIGHTGREEN) dc:pen(COLOR_LIGHTGREEN)
dc:string("Drinks: " .. drink) for id, line in pairs(self.text) do
dc:newline(0) if id >= self.start then
dc:string("Meals: " .. prepared_meals) dc:string(line):newline()
dc:newline(0) end
dc:newline(0) end
dc:string("Wood: " .. wood) dc:pen(COLOR_LIGHTCYAN)
dc:newline(0) if self.start > self.start_min then
dc:newline(0) dc:seek(self.frame_width - 1, 0):char(24)
dc:string("Hides: " .. tannedhides) end
dc:newline(0) if self.start < self.start_max then
dc:string("Cloth: " .. cloth) dc:seek(self.frame_width - 1, self.frame_height - 1):char(25)
dc:newline(0) end
-- dc:string("Raw Fish: ".. raw_fish)
-- dc:newline(0)
-- dc:string("Plants: ".. plants)
-- dc:newline(0)
dc:newline(0)
dc:string("Bars:")
dc:newline(1)
dc:string("Fuel: " .. fuel)
dc:newline(1)
dc:string("Pig Iron: " .. pigiron)
dc:newline(1)
dc:string("Steel: " .. steel)
dc:newline(1)
dc:string("Iron: " .. iron)
dc:newline(1)
dc:newline(1)
dc:string("Copper: " .. copper)
dc:newline(1)
dc:string("Silver: " .. silver)
dc:newline(1)
dc:string("Gold: " .. gold)
end end
function dfstatus:onInput(keys) function dfstatus:onInput(keys)
if keys.LEAVESCREEN or keys.SELECT then if keys.LEAVESCREEN or keys.SELECT then
self:dismiss() self:dismiss()
scr = nil scr = nil
elseif keys.STANDARDSCROLL_UP then
self.start = math.max(self.start - 1, self.start_min)
elseif keys.STANDARDSCROLL_DOWN then
self.start = math.min(self.start + 1, self.start_max)
end end
end end
if not scr then if not scr then
parse_config()
scr = dfstatus() scr = dfstatus()
scr:show() scr:show()
else else

@ -15,7 +15,7 @@ local keybindings={
start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"}, start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"},
help={key="HELP",desc="Show this help"}, help={key="HELP",desc="Show this help"},
displace={key="STRING_A093",desc="Open reference offseted by index"}, displace={key="STRING_A093",desc="Open reference offseted by index"},
NOT_USED={key="SEC_SELECT",desc="Choose an enum value from a list"}, --not a binding... NOT_USED={key="SEC_SELECT",desc="Edit selected entry as a number (for enums)"}, --not a binding...
} }
function getTargetFromScreens() function getTargetFromScreens()
local my_trg local my_trg
@ -72,7 +72,7 @@ function GmEditorUi:init(args)
self.keys={} self.keys={}
local helptext={{text="Help"},NEWLINE,NEWLINE} local helptext={{text="Help"},NEWLINE,NEWLINE}
for k,v in pairs(keybindings) do for k,v in pairs(keybindings) do
table.insert(helptext,{text=v.desc,key=v.key,key_sep=':'}) table.insert(helptext,{text=v.desc,key=v.key,key_sep=': '})
table.insert(helptext,NEWLINE) table.insert(helptext,NEWLINE)
end end
table.insert(helptext,NEWLINE) table.insert(helptext,NEWLINE)
@ -81,13 +81,13 @@ function GmEditorUi:init(args)
local helpPage=widgets.Panel{ local helpPage=widgets.Panel{
subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}}
local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"), local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"),
on_submit2=self:callback("editSelectedEnum"), on_submit2=self:callback("editSelectedRaw"),
text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}} text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}
local mainPage=widgets.Panel{ local mainPage=widgets.Panel{
subviews={ subviews={
mainList, mainList,
widgets.Label{text={{text="<no item>",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, widgets.Label{text={{text="<no item>",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}},
widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=":"}},frame={l=1,t=2}}, widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2}},
widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"}, widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"},
--widgets.Label{text="BLAH2"} --widgets.Label{text="BLAH2"}
} }
@ -170,19 +170,28 @@ end
function GmEditorUi:currentTarget() function GmEditorUi:currentTarget()
return self.stack[#self.stack] return self.stack[#self.stack]
end end
function GmEditorUi:editSelectedEnum(index,choice) function GmEditorUi:getSelectedEnumType()
local trg=self:currentTarget() local trg=self:currentTarget()
local trg_key=trg.keys[index] local trg_key=trg.keys[self.subviews.list_main:getSelected()]
if trg.target._field==nil then qerror("not an enum") end if trg.target._field==nil then return nil end
local enum=trg.target:_field(trg_key)._type local enum=trg.target:_field(trg_key)._type
if enum._kind=="enum-type" then if enum._kind=="enum-type" then
return enum
else
return nil
end
end
function GmEditorUi:editSelectedEnum(index,choice)
local enum=self:getSelectedEnumType()
if enum then
local trg=self:currentTarget()
local trg_key=self:getSelectedKey()
local list={} local list={}
for i=enum._first_item, enum._last_item do for i=enum._first_item, enum._last_item do
table.insert(list,{text=tostring(enum[i]),value=i}) table.insert(list,{text=('%s (%i)'):format(tostring(enum[i]), i),value=i})
end end
guiScript.start(function() guiScript.start(function()
local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list) local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list,nil,true)
if ret then if ret then
trg.target[trg_key]=choice.value trg.target[trg_key]=choice.value
self:updateTarget(true) self:updateTarget(true)
@ -210,7 +219,11 @@ function GmEditorUi:openOffseted(index,choice)
self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice))) self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice)))
end) end)
end end
function GmEditorUi:editSelected(index,choice) function GmEditorUi:editSelectedRaw(index,choice)
self:editSelected(index, choice, {raw=true})
end
function GmEditorUi:editSelected(index,choice,opts)
opts = opts or {}
local trg=self:currentTarget() local trg=self:currentTarget()
local trg_key=trg.keys[index] local trg_key=trg.keys[index]
if trg.target and trg.target._kind and trg.target._kind=="bitfield" then if trg.target and trg.target._kind and trg.target._kind=="bitfield" then
@ -219,7 +232,9 @@ function GmEditorUi:editSelected(index,choice)
else else
--print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "")
local trg_type=type(trg.target[trg_key]) local trg_type=type(trg.target[trg_key])
if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected if self:getSelectedEnumType() and not opts.raw then
self:editSelectedEnum()
elseif trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected
dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE, dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE,
tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key)) tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key))
@ -228,6 +243,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)
@ -310,7 +327,7 @@ function getStringValue(trg,field)
if obj._field ~= nil then if obj._field ~= nil then
local enum=obj:_field(field)._type local enum=obj:_field(field)._type
if enum._kind=="enum-type" then if enum._kind=="enum-type" then
text=text.."("..tostring(enum[obj[field]])..")" text=text.." ("..tostring(enum[obj[field]])..")"
end end
end end
end) end)
@ -379,21 +396,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())

@ -0,0 +1,3 @@
-- by Meph
-- http://www.bay12forums.com/smf/index.php?topic=135506.msg4925005#msg4925005
df.global.world.worldgen.worldgen_parms.embark_points=tonumber(...)

@ -1,3 +1,6 @@
# un-suspend construction jobs, one time only
# same as "resume all"
joblist = df.world.job_list.next joblist = df.world.job_list.next
count = 0 count = 0

@ -85,7 +85,7 @@ class TabLinter(Linter):
def fix_line(self, line): def fix_line(self, line):
return line.replace('\t', ' ') return line.replace('\t', ' ')
linters = [NewlineLinter(), TrailingWhitespaceLinter(), TabLinter()] linters = [cls() for cls in Linter.__subclasses__()]
def main(): def main():
root_path = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else '.') root_path = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else '.')