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

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

3
.gitmodules vendored

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

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

@ -4,28 +4,43 @@ Building DFHACK
.. contents::
=====
Linux
=====
On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD.
===================
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.
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
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
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
============
@ -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.
Some additional dependencies for other distros are listed on the `wiki <https://github.com/DFHack/dfhack/wiki/Linux-dependencies>`_.
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
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 Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which
is usually way older, and incompatible with your dfhack. This manifests with
the error message::
comes from GCC 4.5 and is incompatible with code compiled with newer GCC versions.
This manifests itself with the error message::
./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version
`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/
rm libs/libstdc++.so.6
Alternatively, this issue can be avoided by compiling DFHack with GCC 4.5.
========
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
2. Install Xcode from Mac App Store
3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools.
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/>`_
* 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.
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
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::
git clone git://github.com/DFHack/dfhack.git
git clone --recursive https://github.com/DFHack/dfhack.git
cd dfhack
git submodule init
git submodule update
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::
export CC=/opt/local/bin/gcc-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::
mkdir build-osx
@ -172,27 +194,6 @@ Windows
=======
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
============
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.
* 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.
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.
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.

@ -79,6 +79,7 @@ James Logsdon jlogsdon
melkor217 melkor217
acwatkins acwatkins
Wes Malone wesQ3
Michon van Dooren MaienM
======================= ==================== ===========================
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.
--------------------------------------------------------------------------------
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
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
JSON.lua license

@ -785,6 +785,20 @@ can be omitted.
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()``
Returns the DF directory path.

64
NEWS

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

@ -212,7 +212,7 @@ Possible ways to call the command:
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.
@ -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,
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``,
``@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
================
@ -311,6 +312,17 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p
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
------
Makes the game view follow the currently highlighted unit after you exit from
@ -1252,6 +1264,22 @@ This command adds the Guild Representative position to all Human civilizations,
allowing them to make trade agreements (just as they did back in 0.28.181.40d
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
--------
Removes invalid references to mineral inclusions and restores missing ones.
@ -2144,7 +2172,6 @@ Tools:
* ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution.
* ``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.
* ``sticky``: Maintains the selected local area while navigating the world map

@ -4,7 +4,7 @@ add_subdirectory(md5)
add_subdirectory(protobuf)
add_subdirectory(tinyxml)
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.
OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF)
OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON)

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

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

@ -240,14 +240,13 @@ IF(UNIX)
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)
IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF()
ELSE(WIN32)
#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()
ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp)
@ -298,6 +297,9 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE)
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(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
@ -307,7 +309,7 @@ IF(APPLE)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
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 "")
TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)

@ -73,6 +73,7 @@ using namespace DFHack;
#include <stdlib.h>
#include <fstream>
#include "tinythread.h"
#include "md5wrapper.h"
#include "SDL_events.h"
@ -1755,6 +1756,68 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve
void Core::onStateChange(color_ostream &out, state_change_event event)
{
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);
buildings_onStateChange(out, event);
@ -1854,7 +1917,7 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke)
}
// convert A-Z to their a-z counterparts:
if('A' < unicode && unicode < 'Z')
if('A' <= unicode && unicode <= 'Z')
{
unicode += 'a' - 'A';
}
@ -1900,7 +1963,7 @@ int Core::DFH_SDL_Event(SDL::Event* ev)
// Use unicode so Windows gives the correct value for the
// user's Input Language
if((ke->ksym.unicode & 0xff80) == 0)
if(ke->ksym.unicode && ((ke->ksym.unicode & 0xff80) == 0))
{
int key = UnicodeAwareSym(*ke);
SelectHotkey(key, modstate);
@ -2042,6 +2105,23 @@ bool Core::ClearKeyBindings(std::string keyspec)
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;
KeyBinding binding;
if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus))
@ -2093,11 +2173,12 @@ std::vector<std::string> Core::ListKeyBindings(std::string keyspec)
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::map<std::string, void*> known_vptrs;

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

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

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

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

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

@ -81,6 +81,8 @@ VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uint32_t timestamp
void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
{
bool no_vtables = getenv("DFHACK_NO_VTABLES");
bool no_globals = getenv("DFHACK_NO_GLOBALS");
TiXmlElement* pMemEntry;
const char *cstr_name = entry->Attribute("name");
if (!cstr_name)
@ -136,6 +138,8 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
cerr << "Dummy symbol table entry: " << cstr_key << endl;
continue;
}
if ((is_vtable && no_vtables) || (!is_vtable && no_globals))
continue;
uint32_t addr = strtol(cstr_value, 0, 0);
if (is_vtable)
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}"
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)
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
${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h
${dfhack_SOURCE_DIR}/library/include/git-describe.h)

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

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

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

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

@ -59,7 +59,7 @@ ENDMACRO()
MACRO(DFHACK_PLUGIN)
PARSE_ARGUMENTS(PLUGIN
"LINK_LIBRARIES;DEPENDS;PROTOBUFS"
"LINK_LIBRARIES;DEPENDS;PROTOBUFS;COMPILE_FLAGS;COMPILE_FLAGS_GCC;COMPILE_FLAGS_MSVC"
"SOME_OPT"
${ARGN}
)
@ -95,6 +95,13 @@ MACRO(DFHACK_PLUGIN)
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES})
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)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dylib PREFIX "")
ELSEIF(UNIX)

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

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

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

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

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

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

@ -189,7 +189,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
else if (isInNobleRoomQueryMode())
{
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)
{
int selection = last_token - interface_key::STRING_A048;

@ -0,0 +1,504 @@
#include <set>
#include <map>
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "VTableInterpose.h"
#include "uicommon.h"
#include "modules/Gui.h"
#include "df/building_tradedepotst.h"
#include "df/general_ref.h"
#include "df/general_ref_contained_in_itemst.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_tradegoodsst.h"
using namespace DFHack;
using namespace df::enums;
using std::string;
using std::vector;
DFHACK_PLUGIN("confirm");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(ui);
typedef std::set<df::interface_key> ikey_set;
command_result df_confirm (color_ostream &out, vector <string> & parameters);
struct conf_wrapper;
static std::map<std::string, conf_wrapper*> confirmations;
template <typename VT, typename FT>
bool in_vector (std::vector<VT> &vec, FT item)
{
return std::find(vec.begin(), vec.end(), item) != vec.end();
}
#define goods_selected_func(list) \
static bool list##_goods_selected(df::viewscreen_tradegoodsst *screen) \
{ \
for (auto it = screen->list##_selected.begin(); it != screen->list##_selected.end(); ++it) \
if (*it) return true; \
return false; \
}
goods_selected_func(trader);
goods_selected_func(broker);
#undef goods_selected_func
#define goods_all_selected_func(list) \
static bool list##_goods_all_selected(df::viewscreen_tradegoodsst *screen) \
{ \
for (size_t i = 0; i < screen->list##_selected.size(); ++i) \
{ \
if (!screen->list##_selected[i]) \
{ \
std::vector<df::general_ref*> *refs = &screen->list##_items[i]->general_refs; \
bool in_container = false; \
for (auto it = refs->begin(); it != refs->end(); ++it) \
{ \
if (virtual_cast<df::general_ref_contained_in_itemst>(*it)) \
{ \
in_container = true; \
break; \
} \
} \
if (!in_container) \
return false; \
} \
} \
return true; \
}
goods_all_selected_func(trader);
goods_all_selected_func(broker);
#undef goods_all_selected_func
template <class T>
class confirmation {
public:
enum cstate { INACTIVE, ACTIVE, SELECTED };
typedef T screen_type;
screen_type *screen;
bool feed (ikey_set *input) {
if (state == INACTIVE)
{
for (auto it = input->begin(); it != input->end(); ++it)
{
if (intercept_key(*it))
{
last_key = *it;
state = ACTIVE;
return true;
}
}
return false;
}
else if (state == ACTIVE)
{
if (input->count(df::interface_key::LEAVESCREEN))
state = INACTIVE;
else if (input->count(df::interface_key::SELECT))
state = SELECTED;
return true;
}
return false;
}
bool key_conflict (df::interface_key key)
{
if (key == df::interface_key::SELECT || key == df::interface_key::LEAVESCREEN)
return false;
return state == ACTIVE;
}
void render() {
static vector<string> lines;
Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK);
Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK);
Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK);
Screen::Pen corner_dr = Screen::Pen((char)188, COLOR_GREY, COLOR_BLACK);
Screen::Pen border_ud = Screen::Pen((char)205, COLOR_GREY, COLOR_BLACK);
Screen::Pen border_lr = Screen::Pen((char)186, COLOR_GREY, COLOR_BLACK);
if (state == ACTIVE)
{
split_string(&lines, get_message(), "\n");
size_t max_length = 30;
for (auto it = lines.begin(); it != lines.end(); ++it)
max_length = std::max(max_length, it->size());
int width = max_length + 4;
int height = lines.size() + 4;
int x1 = (gps->dimx / 2) - (width / 2);
int x2 = x1 + width - 1;
int y1 = (gps->dimy / 2) - (height / 2);
int y2 = y1 + height - 1;
for (int x = x1; x <= x2; x++)
{
Screen::paintTile(border_ud, x, y1);
Screen::paintTile(border_ud, x, y2);
}
for (int y = y1; y <= y2; y++)
{
Screen::paintTile(border_lr, x1, y);
Screen::paintTile(border_lr, x2, y);
}
Screen::paintTile(corner_ul, x1, y1);
Screen::paintTile(corner_ur, x2, y1);
Screen::paintTile(corner_dl, x1, y2);
Screen::paintTile(corner_dr, x2, y2);
string title = " " + get_title() + " ";
Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK),
x2 - 6, y1, "DFHack");
Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY),
(gps->dimx / 2) - (title.size() / 2), y1, title);
int x = x1 + 2;
OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN));
OutputString(COLOR_WHITE, x, y2, ": Cancel");
x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size();
OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT));
OutputString(COLOR_WHITE, x, y2, ": Ok");
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1);
for (size_t i = 0; i < lines.size(); i++)
{
Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]);
}
}
else if (state == SELECTED)
{
ikey_set tmp;
tmp.insert(last_key);
screen->feed(&tmp);
state = INACTIVE;
}
}
virtual bool intercept_key (df::interface_key key) = 0;
virtual string get_title() { return "Confirm"; }
virtual string get_message() = 0;
virtual UIColor get_color() { return COLOR_YELLOW; }
protected:
cstate state;
df::interface_key last_key;
};
struct conf_wrapper {
bool enabled;
std::set<VMethodInterposeLinkBase*> hooks;
conf_wrapper()
:enabled(false)
{}
void add_hook(VMethodInterposeLinkBase *hook)
{
if (!hooks.count(hook))
hooks.insert(hook);
}
bool apply (bool state) {
if (state == enabled)
return true;
for (auto h = hooks.begin(); h != hooks.end(); ++h)
{
if (!(**h).apply(state))
return false;
}
enabled = state;
return true;
}
};
#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0)
#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \
static cls cls##_instance; \
struct cls##_hooks : cls::screen_type { \
typedef cls::screen_type interpose_base; \
DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \
{ \
cls##_instance.screen = this; \
if (!cls##_instance.feed(input)) \
INTERPOSE_NEXT(feed)(input); \
} \
DEFINE_VMETHOD_INTERPOSE(void, render, ()) \
{ \
cls##_instance.screen = this; \
INTERPOSE_NEXT(render)(); \
cls##_instance.render(); \
} \
DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \
{ \
return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \
} \
}; \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio);
class trade_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
public:
virtual bool intercept_key (df::interface_key key)
{
return key == df::interface_key::TRADE_TRADE;
}
virtual string get_id() { return "trade"; }
virtual string get_title() { return "Confirm trade"; }
virtual string get_message()
{
if (trader_goods_selected(screen) && broker_goods_selected(screen))
return "Are you sure you want to trade the selected goods?";
else if (trader_goods_selected(screen))
return "You are not giving any items. This is likely\n"
"to irritate the merchants.\n"
"Attempt to trade anyway?";
else if (broker_goods_selected(screen))
return "You are not receiving any items. You may want to\n"
"offer these items instead or choose items to receive.\n"
"Attempt to trade anyway?";
else
return "No items are selected. This is likely\n"
"to irritate the merchants.\n"
"Attempt to trade anyway?";
}
};
IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation);
class trade_cancel_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
public:
virtual bool intercept_key (df::interface_key key)
{
return key == df::interface_key::LEAVESCREEN &&
(trader_goods_selected(screen) || broker_goods_selected(screen));
}
virtual string get_id() { return "trade-cancel"; }
virtual string get_title() { return "Cancel trade"; }
virtual string get_message() { return "Are you sure you want leave this screen?\nSelected items will not be saved."; }
};
IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1);
class trade_seize_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
public:
virtual bool intercept_key (df::interface_key key)
{
return trader_goods_selected(screen) && key == df::interface_key::TRADE_SEIZE;
}
virtual string get_id() { return "trade-seize"; }
virtual string get_title() { return "Confirm seize"; }
virtual string get_message() { return "Are you sure you want to sieze these goods?"; }
};
IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation);
class trade_offer_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
public:
virtual bool intercept_key (df::interface_key key)
{
return broker_goods_selected(screen) && key == df::interface_key::TRADE_OFFER;
}
virtual string get_id() { return "trade-offer"; }
virtual string get_title() { return "Confirm offer"; }
virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; }
};
IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation);
class trade_select_all_confirmation : public confirmation<df::viewscreen_tradegoodsst> {
public:
virtual bool intercept_key (df::interface_key key)
{
if (key == df::interface_key::SEC_SELECT)
{
if (screen->in_right_pane && broker_goods_selected(screen) && !broker_goods_all_selected(screen))
return true;
else if (!screen->in_right_pane && trader_goods_selected(screen) && !trader_goods_all_selected(screen))
return true;
}
return false;
}
virtual string get_id() { return "trade-select-all"; }
virtual string get_title() { return "Confirm selection"; }
virtual string get_message()
{
return "Selecting all goods will overwrite your current selection\n"
"and cannot be undone. Continue?";
}
};
IMPLEMENT_CONFIRMATION_HOOKS(trade_select_all_confirmation);
class hauling_route_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
public:
virtual bool intercept_key (df::interface_key key)
{
if (ui->main.mode == ui_sidebar_mode::Hauling && ui->hauling.view_routes.size())
return key == df::interface_key::D_HAULING_REMOVE;
return false;
}
virtual string get_id() { return "haul-delete"; }
virtual string get_title() { return "Confirm deletion"; }
virtual string get_message()
{
std::string type = (ui->hauling.view_stops[ui->hauling.cursor_top]) ? "stop" : "route";
return std::string("Are you sure you want to delete this ") + type + "?";
}
};
IMPLEMENT_CONFIRMATION_HOOKS(hauling_route_delete_confirmation);
class depot_remove_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
public:
virtual bool intercept_key (df::interface_key key)
{
df::building_tradedepotst *depot = virtual_cast<df::building_tradedepotst>(Gui::getAnyBuilding(screen));
if (depot && key == df::interface_key::DESTROYBUILDING)
{
for (auto it = ui->caravans.begin(); it != ui->caravans.end(); ++it)
{
if ((**it).time_remaining)
return true;
}
}
return false;
}
virtual string get_id() { return "depot-remove"; }
virtual string get_title() { return "Confirm depot removal"; }
virtual string get_message()
{
return "Are you sure you want to remove this depot?\n"
"Merchants are present and will lose profits.";
}
};
IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation);
class squad_disband_confirmation : public confirmation<df::viewscreen_layer_militaryst> {
public:
virtual bool intercept_key (df::interface_key key)
{
return screen->num_squads && key == df::interface_key::D_MILITARY_DISBAND_SQUAD;
}
virtual string get_id() { return "squad-disband"; }
virtual string get_title() { return "Disband squad"; }
virtual string get_message() { return "Are you sure you want to disband this squad?"; }
};
IMPLEMENT_CONFIRMATION_HOOKS(squad_disband_confirmation);
class note_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
public:
virtual bool intercept_key (df::interface_key key)
{
return ui->main.mode == ui_sidebar_mode::NotesPoints && key == df::interface_key::D_NOTE_DELETE;
}
virtual string get_id() { return "note-delete"; }
virtual string get_title() { return "Delete note"; }
virtual string get_message() { return "Are you sure you want to delete this note?"; }
};
IMPLEMENT_CONFIRMATION_HOOKS(note_delete_confirmation);
class route_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
public:
virtual bool intercept_key (df::interface_key key)
{
return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE;
}
virtual string get_id() { return "route-delete"; }
virtual string get_title() { return "Delete route"; }
virtual string get_message() { return "Are you sure you want to delete this route?"; }
};
IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation);
#define CHOOK(cls) \
HOOK_ACTION(cls, render) \
HOOK_ACTION(cls, feed) \
HOOK_ACTION(cls, key_conflict)
#define CHOOKS \
CHOOK(trade_confirmation) \
CHOOK(trade_cancel_confirmation) \
CHOOK(trade_seize_confirmation) \
CHOOK(trade_offer_confirmation) \
CHOOK(trade_select_all_confirmation) \
CHOOK(hauling_route_delete_confirmation) \
CHOOK(depot_remove_confirmation) \
CHOOK(squad_disband_confirmation) \
CHOOK(note_delete_confirmation) \
CHOOK(route_delete_confirmation)
DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
{
#define HOOK_ACTION(cls, method) \
if (confirmations.find(cls##_instance.get_id()) == confirmations.end()) \
confirmations[cls##_instance.get_id()] = new conf_wrapper; \
confirmations[cls##_instance.get_id()]->add_hook(&INTERPOSE_HOOK(cls##_hooks, method));
CHOOKS
#undef HOOK_ACTION
commands.push_back(PluginCommand(
"confirm",
"Confirmation dialogs",
df_confirm,
false, //allow non-interactive use
" confirmation enable|disable option|all ...\n"
" confirmation help|status\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
if (is_enabled != enable)
{
for (auto c = confirmations.begin(); c != confirmations.end(); ++c)
{
if (!c->second->apply(enable))
return CR_FAILURE;
}
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
if (plugin_enable(out, false) != CR_OK)
return CR_FAILURE;
return CR_OK;
}
void enable_conf (color_ostream &out, string name, bool state)
{
bool found = false;
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
if (it->first == name)
{
found = true;
it->second->apply(state);
}
}
if (!found)
out.printerr("Unrecognized option: %s\n", name.c_str());
}
command_result df_confirm (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
bool state = true;
if (in_vector(parameters, "help") || in_vector(parameters, "status"))
{
out << "Available options: \n";
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
out << " " << it->first << ": " << (it->second->enabled ? "enabled" : "disabled") << std::endl;
}
return CR_OK;
}
for (auto it = parameters.begin(); it != parameters.end(); ++it)
{
if (*it == "enable")
state = true;
else if (*it == "disable")
state = false;
else if (*it == "all")
{
for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
it->second->apply(state);
}
}
else
enable_conf(out, *it, state);
}
return CR_OK;
}

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

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

@ -1,4 +1,5 @@
#include "uicommon.h"
#include "listcolumn.h"
#include "DataDefs.h"
@ -43,8 +44,6 @@
#include "df/descriptor_shape.h"
#include "df/descriptor_color.h"
#include "jsonxx.h"
using std::deque;
DFHACK_PLUGIN("dwarfmonitor");
@ -156,6 +155,7 @@ static void open_stats_srceen();
namespace dm_lua {
static color_ostream_proxy *out;
static lua_State *state;
typedef int(*initializer)(lua_State*);
int no_args (lua_State *L) { return 0; }
void cleanup()
@ -165,26 +165,26 @@ namespace dm_lua {
delete out;
out = NULL;
}
lua_close(state);
}
bool init_call (lua_State *L, const char *func)
bool init_call (const char *func)
{
if (!out)
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)
{
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!init_call(L, func))
Lua::StackUnwinder top(state);
if (!init_call(func))
return false;
int nargs = init(L);
return safe_call(L, nargs);
int nargs = init(state);
return safe_call(nargs);
}
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"
));
dm_lua::state = Lua::Open(out);
return CR_OK;
}

@ -8,6 +8,7 @@
#include "modules/Screen.h"
#include "modules/Gui.h"
#include <algorithm>
#include <map>
#include <set>
#include <VTableInterpose.h>
@ -61,37 +62,6 @@ void set_embark_pos (df::viewscreen_choose_start_sitest * screen,
int 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 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_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
{
protected:
@ -647,6 +576,8 @@ public:
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_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1);
std::string title = " Embark tools (DFHack) ";
Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (max_x - min_x + title.size()) / 2, min_y, title);
x = min_x + 2;
y = max_y - 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT));
@ -656,7 +587,7 @@ public:
y = min_y + 2;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* t = *iter;
EmbarkTool* t = iter->second;
x = min_x + 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey()));
OutputString(COLOR_WHITE, x, y, ": " + t->getName() + ": ");
@ -677,7 +608,7 @@ public:
df::interface_key key = *iter;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* t = *iter;
EmbarkTool* t = iter->second;
if (t->getToggleKey() == key)
{
t->toggleEnabled();
@ -687,25 +618,20 @@ public:
};
};
void add_tool (EmbarkTool *t)
{
tools[t->getId()] = t;
}
bool tool_exists (std::string tool_name)
{
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return true;
}
return false;
return tools.find(tool_name) != tools.end();
}
bool tool_enabled (std::string tool_name)
{
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return tool->getEnabled();
}
if (tool_exists(tool_name))
return tools[tool_name]->getEnabled();
return false;
}
@ -714,7 +640,7 @@ bool tool_enable (std::string tool_name, bool enable_state)
int n = 0;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
EmbarkTool* tool = iter->second;
if (tool->getId() == tool_name || tool_name == "all")
{
tool->setEnabled(enable_state);
@ -738,8 +664,9 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
std::vector<std::string> parts;
FOR_ITER_TOOLS(it)
{
if ((*it)->getEnabled())
parts.push_back((*it)->getName());
EmbarkTool *t = it->second;
if (t->getEnabled())
parts.push_back(t->getName());
}
if (parts.size())
{
@ -770,7 +697,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
bool cancel = false;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
EmbarkTool* tool = iter->second;
if (tool->getEnabled())
tool->before_feed(this, input, cancel);
}
@ -781,7 +708,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
display_settings();
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
EmbarkTool* tool = iter->second;
if (tool->getEnabled())
tool->after_feed(this, input);
}
@ -790,7 +717,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
{
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
EmbarkTool* tool = iter->second;
if (tool->getEnabled())
tool->before_render(this);
}
@ -798,7 +725,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
display_tool_status();
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
EmbarkTool* tool = iter->second;
if (tool->getEnabled())
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)
{
tools.push_back(new EmbarkAnywhere);
tools.push_back(new MouseControl);
tools.push_back(new NanoEmbark);
tools.push_back(new SandIndicator);
tools.push_back(new StablePosition);
add_tool(new EmbarkAnywhere);
add_tool(new MouseControl);
add_tool(new SandIndicator);
add_tool(new StablePosition);
std::string help = "";
help += "embark-tools (enable/disable) tool [tool...]\n"
"Tools:\n";
FOR_ITER_TOOLS(iter)
{
help += (" " + (*iter)->getId() + ": " + (*iter)->getDesc() + "\n");
help += (" " + iter->second->getId() + ": " + iter->second->getDesc() + "\n");
}
commands.push_back(PluginCommand(
"embark-tools",
@ -855,6 +781,19 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt)
{
if (evt == SC_BEGIN_UNLOAD)
{
if (Gui::getCurFocus() == "dfhack/embark-tools/options")
{
out.printerr("Settings screen active.\n");
return CR_NOT_FOUND;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out)
{
static int8_t mask = 0;
@ -874,8 +813,8 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out)
{
FOR_ITER_TOOLS(iter)
{
if ((*iter)->getEnabled())
(*iter)->after_mouse_event(screen);
if (iter->second->getEnabled())
iter->second->after_mouse_event(screen);
}
}
mask = new_mask;
@ -915,8 +854,8 @@ command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> &
out << "Tool status:" << std::endl;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* t = *iter;
out << t->getName() << " (" << t->getId() << "): "
EmbarkTool* t = iter->second;
out << " " << t->getName() << " (" << t->getId() << "): "
<< (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 "listcolumn.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/ui.h"

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

@ -96,6 +96,48 @@ function tablify(iterableObject)
return t
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()
init()
local path = get_path()
@ -132,16 +174,16 @@ function save_settings(stockpile)
if #suggested == 0 then
suggested = 'Stock1'
end
suggested = sanitize_filename(suggested)
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 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)
else
if not dfhack.filesystem.exists(path) then
dfhack.filesystem.mkdir(path)
end
print("saving...", path..'/'..filename)
stockpiles_save(path..'/'..filename)
end
end

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

@ -22,6 +22,8 @@
#include "DataFuncs.h"
DFHACK_PLUGIN("mousequery");
REQUIRE_GLOBAL(enabler);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui);
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)
{
if (!gps)
return CR_FAILURE;
if (is_enabled != enable)
{
last_clicked_x = last_clicked_y = last_clicked_z = -1;

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

@ -5,6 +5,8 @@
#include <VTableInterpose.h>
#include "uicommon.h"
#include "df/ui_look_list.h"
#include "df/viewscreen_announcelistst.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)
{
for (int x = x1; x <= x2; x++)
@ -225,11 +221,7 @@ public:
{
// Query typing mode
if (input->empty())
{
return false;
}
df::interface_key last_token = *input->rbegin();
df::interface_key last_token = get_string_key(input);
int charcode = Screen::keyToChar(last_token);
if (charcode >= 32 && charcode <= 126)
{

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

@ -40,6 +40,7 @@ using namespace google::protobuf;
using namespace dfstockpiles;
DFHACK_PLUGIN ( "stockpiles" );
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui);
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 ( !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_OK;
@ -509,11 +510,9 @@ static int stockpiles_list_settings ( lua_State *L )
static void stockpiles_load ( color_ostream &out, std::string filename )
{
out << "stockpiles_load " << filename << " ";
std::vector<std::string> params;
params.push_back ( filename );
command_result r = loadstock ( out, params );
out << " result = "<< r << endl;
if ( r != CR_OK )
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 )
{
out << "stockpiles_save " << filename << " ";
std::vector<std::string> params;
params.push_back ( filename );
command_result r = savestock ( out, params );
out << " result = "<< r << endl;
if ( r != CR_OK )
show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true );
}

@ -1,4 +1,5 @@
#include "uicommon.h"
#include "listcolumn.h"
#include <functional>
@ -598,8 +599,115 @@ class StockListColumn : public ListColumn<T>
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
{
@ -661,6 +769,10 @@ public:
Screen::dismiss(this);
return;
}
else if (input->count(interface_key::HELP))
{
Screen::show(new search_help);
}
bool key_processed = false;
switch (selected_column)
@ -698,7 +810,7 @@ public:
hide_flags.bits.dump = !hide_flags.bits.dump;
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;
populateItems();
@ -801,6 +913,12 @@ public:
return;
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.
// But that's different for built containers vs bags/pots in stockpiles.
send_key(interface_key::D_LOOK);
@ -897,49 +1015,49 @@ public:
y = 2;
x = left_margin;
OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, "Press Ctrl-Hotkey to toggle", true, left_margin);
OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE);
OutputString(COLOR_BROWN, x, y, "Filters ", false, left_margin);
OutputString(COLOR_LIGHTRED, x, y, "(Ctrl+Key toggles)", true, left_margin);
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, "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, "Dump", "D", !hide_flags.bits.dump, true, left_margin, COLOR_LIGHTMAGENTA);
OutputFilterString(x, y, "On Fire", "R", !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, "Dump ", "D", !hide_flags.bits.dump, false, left_margin, COLOR_LIGHTMAGENTA);
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, 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, "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, "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, "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;
OutputHotkeyString(x, y, "Min Qual: ", "-+");
OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin);
OutputHotkeyString(x, y, "Max Qual: ", "/*");
OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin);
++y;
OutputHotkeyString(x, y, "Min Wear: ", "Shift-W");
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_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize()));
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");
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;
OutputString(COLOR_LIGHTRED, x, y, "Flag names can also", true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, "be searched for", true, left_margin);
y = gps->dimy - 4;
OutputHotkeyString(x, y, "Search help", interface_key::HELP, true, left_margin);
}
std::string getFocusString() { return "stocks_view"; }
@ -1307,7 +1425,6 @@ struct stocks_hook : public df::viewscreen_storesst
if (input->count(interface_key::CUSTOM_E))
{
Screen::dismiss(this);
Screen::dismiss(Gui::getCurViewscreen(true));
Screen::show(new ViewscreenStocks());
return;
}
@ -1320,7 +1437,7 @@ struct stocks_hook : public df::viewscreen_storesst
auto dim = Screen::getWindowSize();
int x = 40;
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:
ViewscreenStocks::reset();
break;
case SC_BEGIN_UNLOAD:
if (Gui::getCurFocus().find("dfhack/stocks") == 0)
return CR_FAILURE;
break;
default:
break;
}

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

@ -23,26 +23,22 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
{
// Adapted from autofarm
using namespace df::enums::plant_raw_flags;
// Discovered?
if (ui->tasks.discovered_plants[crop_id])
// Possible to plant?
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?
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))
// Right depth?
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)
{
// Right depth?
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 true;
}
}
return false;
@ -53,9 +49,9 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
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();
for (int season = 0; season < 4; season++)
@ -80,7 +76,10 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
DEFINE_VMETHOD_INTERPOSE(void, 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;
auto dims = Gui::getDwarfmodeViewDims();
int x = dims.menu_x1 + 1,

@ -37,10 +37,6 @@ using std::set;
using namespace DFHack;
using namespace df::enums;
using df::global::enabler;
using df::global::gps;
#ifndef HAVE_NULLPTR
#define nullptr 0L
#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);
}
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,
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);
}
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)
{
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
@ -370,7 +392,7 @@ protected:
y2 = sp->room.y + sp->room.height;
}
private:
protected:
int x1, x2, y1, y2, z;
};
@ -414,475 +436,7 @@ public:
DFHack::World::DeletePersistentData(config);
}
private:
protected:
PersistentDataItem config;
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 "PluginManager.h"
#include "MiscUtils.h"
#include "uicommon.h"
#include "LuaTools.h"
#include "DataFuncs.h"
@ -3705,12 +3706,6 @@ DFHACK_PLUGIN_LUA_COMMANDS {
//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
{
public:
@ -3856,7 +3851,7 @@ public:
return false;
}
df::interface_key last_token = *input->rbegin();
df::interface_key last_token = get_string_key(input);
int charcode = Screen::keyToChar(last_token);
if (charcode >= 32 && charcode <= 126)
{

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

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

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