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

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

6
.gitmodules vendored

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

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

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

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

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

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

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

@ -45,7 +45,7 @@ keybinding add Ctrl-Shift-P command-prompt
keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs"
keybinding add 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)
@ -374,6 +376,7 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
DESTINATION ${DFHACK_DATA_DESTINATION}
FILES_MATCHING PATTERN "*.lua"
PATTERN "*.rb"
PATTERN "3rdparty" EXCLUDE
)
install(DIRECTORY ${dfhack_SOURCE_DIR}/patches

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

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

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

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

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

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

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

@ -259,18 +259,24 @@ uint32_t Process::getTickCount()
string Process::getPath()
{
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)

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

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

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

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

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

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

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

@ -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)
@ -133,6 +135,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(mode mode.cpp)
#DFHACK_PLUGIN(misery misery.cpp)
@ -143,7 +146,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(prospector prospector.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(remotefortressreader remotefortressreader.cpp PROTOBUFS RemoteFortressReader)
DFHACK_PLUGIN(RemoteFortressReader remotefortressreader.cpp PROTOBUFS RemoteFortressReader)
DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename)
add_subdirectory(rendermax)
DFHACK_PLUGIN(resume resume.cpp)
@ -159,6 +162,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()

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

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

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

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

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

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

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

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

@ -96,6 +96,48 @@ function tablify(iterableObject)
return t
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

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

@ -33,6 +33,7 @@
#include "df/entity_raw.h"
#include "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;

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

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

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

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

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

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

@ -23,26 +23,22 @@ struct farm_select_hook : df::viewscreen_dwarfmodest {
{
// Adapted from autofarm
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)
{

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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