develop
Anuradha Dissanayake 2013-03-16 12:51:31 +13:00
commit b961cb6a09
301 changed files with 27227 additions and 9930 deletions

3
.gitignore vendored

@ -57,3 +57,6 @@ dfhack/python/dist
build/CPack*Config.cmake build/CPack*Config.cmake
/cmakeall.bat /cmakeall.bat
# vim swap files
*.swp

@ -58,14 +58,10 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif() endif()
# set up versioning. # set up versioning.
set(DF_VERSION_MAJOR "0") set(DF_VERSION "0.34.11")
set(DF_VERSION_MINOR "34") SET(DFHACK_RELEASE "r3" CACHE STRING "Current release revision.")
set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
## where to install things (after the build is done, classic 'make install' or package structure) ## where to install things (after the build is done, classic 'make install' or package structure)
@ -122,7 +118,7 @@ ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS)
ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL) ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL)
if(APPLE) if(APPLE)
add_definitions(-D_DARWIN) add_definitions(-D_DARWIN)
elseif(UNIX) elseif(UNIX)
add_definitions(-D_LINUX) add_definitions(-D_LINUX)
elseif(WIN32) elseif(WIN32)
@ -146,7 +142,7 @@ include_directories(depends/clsocket/src)
add_subdirectory(depends) add_subdirectory(depends)
find_package(Docutils) #find_package(Docutils)
#set (RST_FILES #set (RST_FILES
#"Readme" #"Readme"
@ -173,6 +169,7 @@ IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
## install the default documentation files ## install the default documentation files
install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
#build the plugins #build the plugins
@ -182,11 +179,11 @@ endif()
# Packaging with CPack! # Packaging with CPack!
IF(UNIX) IF(UNIX)
if(APPLE) if(APPLE)
SET(CPACK_GENERATOR "ZIP") SET(CPACK_GENERATOR "ZIP")
else() else()
SET(CPACK_GENERATOR "TGZ") SET(CPACK_GENERATOR "TGZ")
endif() endif()
ELSEIF(WIN32) ELSEIF(WIN32)
SET(CPACK_GENERATOR "ZIP") SET(CPACK_GENERATOR "ZIP")
ENDIF() ENDIF()

@ -334,20 +334,21 @@ ul.auto-toc {
<li><a class="reference internal" href="#build" id="id7">Build</a></li> <li><a class="reference internal" href="#build" id="id7">Build</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#windows" id="id8">Windows</a><ul> <li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a></li>
<li><a class="reference internal" href="#id1" id="id9">How to get the code</a></li> <li><a class="reference internal" href="#windows" id="id9">Windows</a><ul>
<li><a class="reference internal" href="#id2" id="id10">Dependencies</a></li> <li><a class="reference internal" href="#id1" id="id10">How to get the code</a></li>
<li><a class="reference internal" href="#id3" id="id11">Build</a></li> <li><a class="reference internal" href="#id2" id="id11">Dependencies</a></li>
<li><a class="reference internal" href="#id3" id="id12">Build</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#build-types" id="id12">Build types</a></li> <li><a class="reference internal" href="#build-types" id="id13">Build types</a></li>
<li><a class="reference internal" href="#using-the-library-as-a-developer" id="id13">Using the library as a developer</a><ul> <li><a class="reference internal" href="#using-the-library-as-a-developer" id="id14">Using the library as a developer</a><ul>
<li><a class="reference internal" href="#df-data-structure-definitions" id="id14">DF data structure definitions</a></li> <li><a class="reference internal" href="#df-data-structure-definitions" id="id15">DF data structure definitions</a></li>
<li><a class="reference internal" href="#remote-access-interface" id="id15">Remote access interface</a></li> <li><a class="reference internal" href="#remote-access-interface" id="id16">Remote access interface</a></li>
<li><a class="reference internal" href="#contributing-to-dfhack" id="id16">Contributing to DFHack</a><ul> <li><a class="reference internal" href="#contributing-to-dfhack" id="id17">Contributing to DFHack</a><ul>
<li><a class="reference internal" href="#coding-style" id="id17">Coding style</a></li> <li><a class="reference internal" href="#coding-style" id="id18">Coding style</a></li>
<li><a class="reference internal" href="#how-to-get-new-code-into-dfhack" id="id18">How to get new code into DFHack</a></li> <li><a class="reference internal" href="#how-to-get-new-code-into-dfhack" id="id19">How to get new code into DFHack</a></li>
<li><a class="reference internal" href="#memory-research" id="id19">Memory research</a></li> <li><a class="reference internal" href="#memory-research" id="id20">Memory research</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -403,11 +404,66 @@ extra options.</p>
program.</p> program.</p>
</div> </div>
</div> </div>
<div class="section" id="mac-os-x">
<h1><a class="toc-backref" href="#id8">Mac OS X</a></h1>
<ol class="arabic">
<li><p class="first">Download and unpack a copy of the latest DF</p>
</li>
<li><p class="first">Install Xcode from Mac App Store</p>
</li>
<li><p class="first">Open Xcode, go to Preferences &gt; Downloads, and install the Command Line Tools.</p>
</li>
<li><p class="first">Install MacPorts.</p>
</li>
<li><p class="first">Install dependencies from MacPorts:</p>
<ul>
<li><p class="first"><tt class="docutils literal">sudo port install gcc45 +universal cmake +universal <span class="pre">git-core</span> +universal</tt></p>
<p>This will take some time—maybe hours, depending on your machine.</p>
</li>
<li><p class="first">At some point during this process, it may ask you to install a Java environment; let it do so.</p>
</li>
</ul>
</li>
<li><p class="first">Install perl dependencies</p>
<blockquote>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">sudo cpan</tt></p>
<p>If this is the first time you've run cpan, you will need to go through the setup
process. Just stick with the defaults for everything and you'll be fine.</p>
</li>
<li><p class="first"><tt class="docutils literal">install <span class="pre">XML::LibXML</span></tt></p>
</li>
<li><p class="first"><tt class="docutils literal">install <span class="pre">XML::LibXSLT</span></tt></p>
</li>
</ol>
</blockquote>
</li>
<li><p class="first">Get the dfhack source:</p>
<pre class="literal-block">
git clone https://github.com/danaris/dfhack.git
cd dfhack
git submodule init
git submodule update
</pre>
</li>
<li><p class="first">Build dfhack:</p>
<pre class="literal-block">
mkdir build-osx
cd build-osx
export CC=/opt/local/bin/gcc-mp-4.5
export CXX=/opt/local/bin/g++-mp-4.5
cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory
make
make install
</pre>
</li>
</ol>
</div>
<div class="section" id="windows"> <div class="section" id="windows">
<h1><a class="toc-backref" href="#id8">Windows</a></h1> <h1><a class="toc-backref" href="#id9">Windows</a></h1>
<p>On Windows, DFHack replaces the SDL library distributed with DF.</p> <p>On Windows, DFHack replaces the SDL library distributed with DF.</p>
<div class="section" id="id1"> <div class="section" id="id1">
<h2><a class="toc-backref" href="#id9">How to get the code</a></h2> <h2><a class="toc-backref" href="#id10">How to get the code</a></h2>
<p>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. <p>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:</p> You will need some sort of Windows port of git, or a GUI. Some examples:</p>
<blockquote> <blockquote>
@ -428,7 +484,7 @@ git submodule update
<p>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).</p> <p>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).</p>
</div> </div>
<div class="section" id="id2"> <div class="section" id="id2">
<h2><a class="toc-backref" href="#id10">Dependencies</a></h2> <h2><a class="toc-backref" href="#id11">Dependencies</a></h2>
<p>First, you need <tt class="docutils literal">cmake</tt>. Get the win32 installer version from the official <p>First, you need <tt class="docutils literal">cmake</tt>. Get the win32 installer version from the official
site: <a class="reference external" href="http://www.cmake.org/cmake/resources/software.html">http://www.cmake.org/cmake/resources/software.html</a></p> site: <a class="reference external" href="http://www.cmake.org/cmake/resources/software.html">http://www.cmake.org/cmake/resources/software.html</a></p>
<p>It has the usual installer wizard. Make sure you let it add its binary folder <p>It has the usual installer wizard. Make sure you let it add its binary folder
@ -445,7 +501,7 @@ Grab it from Microsoft's site.</p>
<p>If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.</p> <p>If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.</p>
</div> </div>
<div class="section" id="id3"> <div class="section" id="id3">
<h2><a class="toc-backref" href="#id11">Build</a></h2> <h2><a class="toc-backref" href="#id12">Build</a></h2>
<p>There are several different batch files in the <tt class="docutils literal">build</tt> folder along with a script that's used for picking the DF path.</p> <p>There are several different batch files in the <tt class="docutils literal">build</tt> folder along with a script that's used for picking the DF path.</p>
<p>First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development. <p>First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development.
Next, run one of the scripts with <tt class="docutils literal">generate</tt> prefix. These create the MSVC solution file(s):</p> Next, run one of the scripts with <tt class="docutils literal">generate</tt> prefix. These create the MSVC solution file(s):</p>
@ -467,7 +523,7 @@ So pick either Release or RelWithDebInfo build and build the INSTALL target.</p>
</div> </div>
</div> </div>
<div class="section" id="build-types"> <div class="section" id="build-types">
<h1><a class="toc-backref" href="#id12">Build types</a></h1> <h1><a class="toc-backref" href="#id13">Build types</a></h1>
<p><tt class="docutils literal">cmake</tt> allows you to pick a build type by changing this <p><tt class="docutils literal">cmake</tt> allows you to pick a build type by changing this
variable: <tt class="docutils literal">CMAKE_BUILD_TYPE</tt></p> variable: <tt class="docutils literal">CMAKE_BUILD_TYPE</tt></p>
<pre class="literal-block"> <pre class="literal-block">
@ -479,7 +535,7 @@ cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
'RelWithDebInfo'. 'Debug' is not available on Windows.</p> 'RelWithDebInfo'. 'Debug' is not available on Windows.</p>
</div> </div>
<div class="section" id="using-the-library-as-a-developer"> <div class="section" id="using-the-library-as-a-developer">
<h1><a class="toc-backref" href="#id13">Using the library as a developer</a></h1> <h1><a class="toc-backref" href="#id14">Using the library as a developer</a></h1>
<p>Currently, the most direct way to use the library is to write a plugin that can be loaded by it. <p>Currently, the most direct way to use the library is to write a plugin that can be loaded by it.
All the plugins can be found in the 'plugins' folder. There's no in-depth documentation All the plugins can be found in the 'plugins' folder. There's no in-depth documentation
on how to write one yet, but it should be easy enough to copy one and just follow the pattern.</p> on how to write one yet, but it should be easy enough to copy one and just follow the pattern.</p>
@ -497,29 +553,29 @@ The main license is zlib/libpng, some bits are MIT licensed, and some are BSD li
<p>Feel free to add your own extensions and plugins. Contributing back to <p>Feel free to add your own extensions and plugins. Contributing back to
the dfhack repository is welcome and the right thing to do :)</p> the dfhack repository is welcome and the right thing to do :)</p>
<div class="section" id="df-data-structure-definitions"> <div class="section" id="df-data-structure-definitions">
<h2><a class="toc-backref" href="#id14">DF data structure definitions</a></h2> <h2><a class="toc-backref" href="#id15">DF data structure definitions</a></h2>
<p>DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.</p> <p>DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.</p>
<p>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.</p> <p>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.</p>
<p>Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.</p> <p>Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.</p>
</div> </div>
<div class="section" id="remote-access-interface"> <div class="section" id="remote-access-interface">
<h2><a class="toc-backref" href="#id15">Remote access interface</a></h2> <h2><a class="toc-backref" href="#id16">Remote access interface</a></h2>
<p>DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The <tt class="docutils literal"><span class="pre">dfhack-run</span></tt> command uses this interface to invoke ordinary console commands.</p> <p>DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The <tt class="docutils literal"><span class="pre">dfhack-run</span></tt> command uses this interface to invoke ordinary console commands.</p>
<p>Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.</p> <p>Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.</p>
<p>Protocol client implementations exist for Java and C#.</p> <p>Protocol client implementations exist for Java and C#.</p>
</div> </div>
<div class="section" id="contributing-to-dfhack"> <div class="section" id="contributing-to-dfhack">
<h2><a class="toc-backref" href="#id16">Contributing to DFHack</a></h2> <h2><a class="toc-backref" href="#id17">Contributing to DFHack</a></h2>
<p>Several things should be kept in mind when contributing to DFHack.</p> <p>Several things should be kept in mind when contributing to DFHack.</p>
<div class="section" id="coding-style"> <div class="section" id="coding-style">
<h3><a class="toc-backref" href="#id17">Coding style</a></h3> <h3><a class="toc-backref" href="#id18">Coding style</a></h3>
<p>DFhack uses ANSI formatting and four spaces as indentation. Line <p>DFhack uses ANSI formatting and four spaces as indentation. Line
endings are UNIX. The files use UTF-8 encoding. Code not following this endings are UNIX. The files use UTF-8 encoding. Code not following this
won't make me happy, because I'll have to fix it. There's a good chance won't make me happy, because I'll have to fix it. There's a good chance
I'll make <em>you</em> fix it ;)</p> I'll make <em>you</em> fix it ;)</p>
</div> </div>
<div class="section" id="how-to-get-new-code-into-dfhack"> <div class="section" id="how-to-get-new-code-into-dfhack">
<h3><a class="toc-backref" href="#id18">How to get new code into DFHack</a></h3> <h3><a class="toc-backref" href="#id19">How to get new code into DFHack</a></h3>
<p>You can send patches or make a clone of the github repo and ask me on <p>You can send patches or make a clone of the github repo and ask me on
the IRC channel to pull your code in. I'll review it and see if there the IRC channel to pull your code in. I'll review it and see if there
are any problems. I'll fix them if they are minor.</p> are any problems. I'll fix them if they are minor.</p>
@ -529,7 +585,7 @@ this is also a good place to dump new ideas and/or bugs that need
fixing.</p> fixing.</p>
</div> </div>
<div class="section" id="memory-research"> <div class="section" id="memory-research">
<h3><a class="toc-backref" href="#id19">Memory research</a></h3> <h3><a class="toc-backref" href="#id20">Memory research</a></h3>
<p>If you want to do memory research, you'll need some tools and some knowledge. <p>If you want to do memory research, you'll need some tools and some knowledge.
In general, you'll need a good memory viewer and optionally something In general, you'll need a good memory viewer and optionally something
to look at machine code without getting crazy :)</p> to look at machine code without getting crazy :)</p>

@ -63,6 +63,63 @@ extra options.
You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui
program. program.
========
Mac OS X
========
If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST.
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 MacPorts.
5. Install dependencies from MacPorts:
* ``sudo port install gcc45 +universal cmake +universal git-core +universal``
This will take some time—maybe hours, depending on your machine.
* At some point during this process, it may ask you to install a Java environment; let it do so.
6. Install perl dependencies
1. ``sudo cpan``
If this is the first time you've run cpan, you will need to go through the setup
process. Just stick with the defaults for everything and you'll be fine.
2. ``install XML::LibXML``
3. ``install XML::LibXSLT``
7. Get the dfhack source::
git clone https://github.com/danaris/dfhack.git
cd dfhack
git submodule init
git submodule update
8. Build dfhack::
mkdir build-osx
cd build-osx
export CC=/opt/local/bin/gcc-mp-4.5
export CXX=/opt/local/bin/g++-mp-4.5
cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory
make
make install
Snow Leopard Changes
====================
1. Add a step 6.2a (before Install XML::LibXSLT)::
In a separate Terminal window or tab, run:
``sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml``
2. Add a step 7a (before building)::
In <dfhack directory>/library/LuaTypes.cpp, change line 467 to
``int len = strlen((char*)ptr);``
======= =======
Windows Windows
======= =======

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

79
NEWS

@ -1,6 +1,83 @@
DFHack future DFHack future
Nothing yet! Is not yet known.
DFHack v0.34.11-r3
Internals:
- support for displaying active keybindings properly.
- support for reusable widgets in lua screen library.
- Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins
don't have to monitor the same things redundantly.
Notable bugfixes:
- autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires.
Misc improvements:
- fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches.
- removebadthoughts: add --dry-run option
- superdwarf: work in adventure mode too
- tweak stable-cursor: carries cursor location from/to Build menu.
- deathcause: allow selection from the unitlist screen
- slayrace: allow targetting undeads
New tweaks:
- tweak military-training: speed up melee squad training up to 10x (normally 3-5x).
New scripts:
- binpatch: the same as the stand-alone binpatch.exe, but works at runtime.
- region-pops: displays animal populations of the region and allows tweaking them.
- lua: lua interpreter front-end converted to a script from a native command.
- dfusion: misc scripts with a text based menu.
- embark: lets you embark anywhere.
- lever: list and pull fort levers from the dfhack console.
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
- soundsense-season: writes the correct season to gamelog.txt on world load.
- create-items: spawn items
- fix/cloth-stockpile: fixes bug 5739; needs to be run after savegame load every time.
New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
- gui/workflow: a front-end for the workflow plugin (part inspired by falconne).
- gui/assign-rack: works together with a binary patch to fix weapon racks.
- gui/gm-editor: an universal editor for lots of dfhack things.
- gui/companion-order: a adventure mode command interface for your companions.
- gui/advfort: a way to do jobs with your adventurer (e.g. build fort).
New binary patches (for use with binpatch):
- armorstand-capacity: doubles the capacity of armor stands.
- custom-reagent-size: lets custom reactions use small amounts of inputs.
- deconstruct-heapfall: stops some items still falling on head when deconstructing.
- deconstruct-teleport: stops items from 16x16 block teleporting when deconstructing.
- hospital-overstocking: stops hospital overstocking with supplies.
- training-ammo: lets dwarves with quiver full of combat-only ammo train.
- weaponrack-unassign: fixes bug that negates work done by gui/assign-rack.
Workflow plugin:
- properly considers minecarts assigned to routes busy.
- code for deducing job outputs rewritten in lua for flexibility.
- logic fix: collecting webs produces silk, and ungathered webs are not thread.
- items assigned to squads are considered busy, even if not in inventory.
- shearing and milking jobs are supported, but only with generic MILK or YARN outputs.
- workflow announces when the stock level gets very low once a season.
New Fix Armory plugin:
Together with a couple of binary patches and the gui/assign-rack script,
this plugin makes weapon racks, armor stands, chests and cabinets in
properly designated barracks be used again for storage of squad equipment.
New Search plugin by falconne:
Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens.
New AutoMaterial plugin by falconne:
Makes building constructions (walls, floors, fortifications, etc) a little bit easier by
saving you from having to trawl through long lists of materials each time you place one.
Dfusion plugin:
Reworked to make use of lua modules, now all the scripts can be used from other scripts.
New Eventful plugin:
A collection of lua events, that will allow new ways to interact with df world.
Auto syndrome plugin:
A way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws.
Infinite sky plugin:
Create new z-levels automatically or on request.
True transformation plugin:
A better way of doing permanent transformations that allows later transformations.
Work now plugin:
Makes the game assign jobs every time you pause.
DFHack v0.34.11-r2 DFHack v0.34.11-r2

File diff suppressed because it is too large Load Diff

@ -58,9 +58,35 @@ The stonesense plugin might require some additional libraries on Linux.
If any of the plugins or dfhack itself refuses to load, check the stderr.log If any of the plugins or dfhack itself refuses to load, check the stderr.log
file created in your DF folder. file created in your DF folder.
Getting started
===============
If DFHack is installed correctly, it will automatically pop up a console
window once DF is started as usual on windows. Linux and Mac OS X require
running the dfhack script from the terminal, and will use that terminal for
the console.
**NOTE**: The dfhack-run executable is there for calling DFHack commands in
an already running DF+DFHack instance from external OS scripts and programs,
and is *not* the way how you use DFHack normally.
DFHack has a lot of features, which can be accessed by typing commands in the
console, or by mapping them to keyboard shortcuts. Most of the newer and more
user-friendly tools are designed to be at least partially used via the latter
way.
In order to set keybindings, you have to create a text configuration file
called ``dfhack.init``; the installation comes with an example version called
``dfhack.init-example``, which is fully functional, covers all of the recent
features and can be simply renamed to ``dfhack.init``. You are encouraged to look
through it to learn which features it makes available under which key combinations.
For more information, refer to the rest of this document.
============ ============
Using DFHack Using DFHack
============ ============
DFHack basically extends what DF can do with something similar to the drop-down DFHack basically extends what DF can do with something similar to the drop-down
console found in Quake engine games. On Windows, this is a separate command line console found in Quake engine games. On Windows, this is a separate command line
window. On linux, the terminal used to launch the dfhack script is taken over window. On linux, the terminal used to launch the dfhack script is taken over
@ -88,6 +114,46 @@ Interactive commands like 'liquids' cannot be used as hotkeys.
Most of the commands come from plugins. Those reside in 'hack/plugins/'. Most of the commands come from plugins. Those reside in 'hack/plugins/'.
Patched binaries
================
On linux and OSX, users of patched binaries may have to find the relevant
section in symbols.xml, and add a new line with the checksum of their
executable::
<md5-hash value='????????????????????????????????'/>
In order to find the correct value of the hash, look into stderr.log;
DFHack prints an error there if it does not recognize the hash.
DFHack includes a small stand-alone utility for applying and removing
binary patches from the game executable. Use it from the regular operating
system console:
* ``binpatch check "Dwarf Fortress.exe" patch.dif``
Checks and prints if the patch is currently applied.
* ``binpatch apply "Dwarf Fortress.exe" patch.dif``
Applies the patch, unless it is already applied or in conflict.
* ``binpatch remove "Dwarf Fortress.exe" patch.dif``
Removes the patch, unless it is already removed.
The patches are expected to be encoded in text format used by IDA.
Live patching
-------------
As an alternative, you can use the ``binpatch`` dfhack command to apply/remove
patches live in memory during a DF session.
In this case, updating symbols.xml is not necessary.
============================= =============================
Something doesn't work, help! Something doesn't work, help!
============================= =============================
@ -201,6 +267,8 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p
* 'fastdwarf 1 1' enables both * 'fastdwarf 1 1' enables both
* 'fastdwarf 0' disables both * 'fastdwarf 0' disables both
* 'fastdwarf 1' enables speedydwarf and disables teledwarf * 'fastdwarf 1' enables speedydwarf and disables teledwarf
* 'fastdwarf 2 ...' sets a native debug flag in the game memory
that implements an even more aggressive version of speedydwarf.
Game interface Game interface
============== ==============
@ -381,6 +449,26 @@ Options:
:bees: turn colonies into honey bee colonies :bees: turn colonies into honey bee colonies
createitem
----------
Allows creating new items of arbitrary types and made of arbitrary materials.
Any items created are spawned at the feet of the selected unit.
Specify the item and material information as you would indicate them in custom reaction raws, with the following differences:
* Separate the item and material with a space rather than a colon
* If the item has no subtype, omit the :NONE
* If the item is REMAINS, FISH, FISH_RAW, VERMIN, PET, or EGG, specify a CREATURE:CASTE pair instead of a material token.
Corpses, body parts, and prepared meals cannot be created using this tool.
Examples:
``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2``
Create 2 pairs of steel gauntlets.
``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD``
Create tower-cap logs.
``createitem FISH FISH_SHAD:MALE 5``
Create a stack of 5 cleaned shad, ready to eat.
deramp (by zilpin) deramp (by zilpin)
------------------ ------------------
Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation.
@ -1019,6 +1107,9 @@ Subcommands that persist until disabled or DF quit:
:patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts.
Does NOT fix the problem when soldiers go off-duty (i.e. civilian). Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
:readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu. :readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu.
.. image:: images/tweak-plate.png
:stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates. :stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates.
In very item-heavy forts with big stockpiles this can improve FPS by 50-100% In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
:fast-heat: Further improves temperature update performance by ensuring that 1 degree :fast-heat: Further improves temperature update performance by ensuring that 1 degree
@ -1037,9 +1128,78 @@ Subcommands that persist until disabled or DF quit:
:military-stable-assign: Preserve list order and cursor position when assigning to squad, :military-stable-assign: Preserve list order and cursor position when assigning to squad,
i.e. stop the rightmost list of the Positions page of the military i.e. stop the rightmost list of the Positions page of the military
screen from constantly resetting to the top. screen from constantly resetting to the top.
:military-color-assigned: Color squad candidates already assigned to other squads in brown/green :military-color-assigned: Color squad candidates already assigned to other squads in yellow/green
to make them stand out more in the list. to make them stand out more in the list.
.. image:: images/tweak-mil-color.png
:military-training: Speeds up melee squad training by removing an almost certainly
unintended inverse dependency of training speed on unit count
(i.e. the more units you have, the slower it becomes), and making
the units spar more.
fix-armory
----------
Enables a fix for storage of squad equipment in barracks.
Specifically, it prevents your haulers from moving squad equipment
to stockpiles, and instead queues jobs to store it on weapon racks,
armor stands, and in containers.
.. note::
In order to actually be used, weapon racks have to be patched and
manually assigned to a squad. See documentation for ``gui/assign-rack``
below.
Also, the default capacity of armor stands is way too low, so you
may want to also apply the ``armorstand-capacity`` patch. Check out
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
for more information about the bugs.
Note that the buildings in the armory are used as follows:
* Weapon racks (when patched) are used to store any assigned weapons.
Each rack belongs to a specific squad, and can store up to 5 weapons.
* Armor stands belong to specific squad members and are used for
armor and shields. By default one stand can store one item of each
type (hence one boot or gauntlet); if patched, the limit is raised to 2,
which should be sufficient.
* Cabinets are used to store assigned clothing for a specific squad member.
They are **never** used to store owned clothing.
* Chests (boxes, etc) are used for a flask, backpack or quiver assigned
to the squad member. Due to a probable bug, food is dropped out of the
backpack when it is stored.
.. warning::
Although armor stands, cabinets and chests properly belong only to one
squad member, the owner of the building used to create the barracks will
randomly use any containers inside the room. Thus, it is recommended to
always create the armory from a weapon rack.
Contrary to the common misconception, all these uses are controlled by the
*Individual Equipment* usage flag. The *Squad Equipment* flag is actually
intended for ammo, but the game does even less in that area than for armor
and weapons. This plugin implements the following rules almost from scratch:
* Combat ammo is stored in chests inside rooms with Squad Equipment enabled.
* If a chest is assigned to a squad member due to Individual Equipment also
being set, it is only used for that squad's ammo; otherwise, any squads
with Squad Equipment on the room will use all of the chests at random.
* Training ammo is stored in chests inside archery ranges designated from
archery targets, and controlled by the same Train flag as archery training
itself. This is inspired by some defunct code for weapon racks.
There are some minor traces in the game code to suggest that the first of
these rules is intended by Toady; the rest are invented by this plugin.
Mode switch and reclaim Mode switch and reclaim
======================= =======================
@ -1165,10 +1325,18 @@ Usage:
List workflow-controlled jobs (if in a workshop, filtered by it). List workflow-controlled jobs (if in a workshop, filtered by it).
``workflow list`` ``workflow list``
List active constraints, and their job counts. List active constraints, and their job counts.
``workflow count <constraint-spec> <cnt-limit> [cnt-gap], workflow amount <constraint-spec> <cnt-limit> [cnt-gap]`` ``workflow list-commands``
Set a constraint. The first form counts each stack as only 1 item. List active constraints as workflow commands that re-create them;
this list can be copied to a file, and then reloaded using the
``script`` built-in command.
``workflow count <constraint-spec> <cnt-limit> [cnt-gap]``
Set a constraint, counting every stack as 1 item.
``workflow amount <constraint-spec> <cnt-limit> [cnt-gap]``
Set a constraint, counting all items within stacks.
``workflow unlimit <constraint-spec>`` ``workflow unlimit <constraint-spec>``
Delete a constraint. Delete a constraint.
``workflow unlimit-all``
Delete all constraints.
Function Function
........ ........
@ -1183,6 +1351,37 @@ amount goes above or below the limit. The gap specifies how much below the limit
the amount has to drop before jobs are resumed; this is intended to reduce the amount has to drop before jobs are resumed; this is intended to reduce
the frequency of jobs being toggled. the frequency of jobs being toggled.
Check out the ``gui/workflow`` script below for a simple front-end integrated
in the game UI.
Constraint format
.................
The contstraint spec consists of 4 parts, separated with '/' characters::
ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
The first part is mandatory and specifies the item type and subtype,
using the raw tokens for items, in the same syntax you would e.g. use
for a custom reaction input. See this list for more info: http://dwarffortresswiki.org/index.php/Item_token
The subsequent parts are optional:
- A generic material spec constrains the item material to one of
the hard-coded generic classes, which currently include::
PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN
METAL STONE SAND GLASS CLAY MILK
- A specific material spec chooses the material exactly, using the
raw syntax for reaction input materials, e.g. INORGANIC:IRON,
although for convenience it also allows just IRON, or ACACIA:WOOD etc.
See this page for more details on the unabbreviated raw syntax:
http://dwarffortresswiki.org/index.php/Material_token
- A comma-separated list of miscellaneous flags, which currently can
be used to ignore imported items or items below a certain quality.
Constraint examples Constraint examples
................... ...................
@ -1212,6 +1411,11 @@ Make sure there are always 15-20 coal and 25-30 copper bars.
workflow count BAR//COAL 20 workflow count BAR//COAL 20
workflow count BAR//COPPER 30 workflow count BAR//COPPER 30
Produce 15-20 gold crafts.
::
workflow count CRAFTS//GOLD 20
Collect 15-20 sand bags and clay boulders. Collect 15-20 sand bags and clay boulders.
:: ::
@ -1223,9 +1427,16 @@ Make sure there are always 80-100 units of dimple dye.
workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
.. note::
In order for this to work, you have to set the material of the PLANT input In order for this to work, you have to set the material of the PLANT input
on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material'
command. command. Otherwise the plugin won't be able to deduce the output material.
Maintain 10-100 locally-made crafts of exceptional quality.
::
workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management Fortress activity management
@ -1515,19 +1726,17 @@ twice.
dfusion dfusion
------- -------
This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin. This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:
:Friendship: a binary plugin that allows multi race forts (to use make a script that imports plugins.dfusion.friendship and use Friendship:install{table} table should contain list of race names.)
See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15 :Embark: a binary plugin that allows multi race embark (to use make a script that imports plugins.dfusion.embark and use Embark:install{table} table should contain list of race names or list of pairs (race-name, caste_id)).
Confirmed working DFusion plugins: See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=93317.0
:simple_embark: allows changing the number of dwarves available on embark.
.. note:: .. note::
* Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
* This is currently working only on Windows. * The game will be suspended while you're using dfusion. Don't panic when it doesn't respond.
* The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
misery misery
------ ------
@ -1584,6 +1793,12 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
Diagnoses and fixes issues with nonexistant 'items occupying site', usually Diagnoses and fixes issues with nonexistant 'items occupying site', usually
caused by autodump bugs or other hacking mishaps. caused by autodump bugs or other hacking mishaps.
* fix/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.
gui/* gui/*
===== =====
@ -1591,6 +1806,17 @@ gui/*
Scripts that implement dialogs inserted into the main game window are put in this Scripts that implement dialogs inserted into the main game window are put in this
directory. directory.
binpatch
========
Checks, applies or removes binary patches directly in memory at runtime::
binpatch check/apply/remove <patchname>
If the name of the patch has no extension or directory separators, the
script uses ``hack/patches/<df-version>/<name>.dif``, thus auto-selecting
the version appropriate for the currently loaded executable.
quicksave quicksave
========= =========
@ -1632,29 +1858,34 @@ removebadthoughts
This script remove negative thoughts from your dwarves. Very useful against This script remove negative thoughts from your dwarves. Very useful against
tantrum spirals. tantrum spirals.
With a selected unit in 'v' mode, will clear this unit's mind, otherwise The script can target a single creature, when used with the ``him`` argument,
clear all your fort's units minds. or the whole fort population, with ``all``.
To show every bad thought present without actually removing them, run the
script with the ``-n`` or ``--dry-run`` argument. This can give a quick
hint on what bothers your dwarves the most.
Individual dwarf happiness may not increase right after this command is run, Individual dwarf happiness may not increase right after this command is run,
but in the short term your dwarves will get much more joyful. but in the short term your dwarves will get much more joyful.
The thoughts are set to be very old, and the game will remove them soon when
you unpause.
With the optional ``-v`` parameter, the script will dump the negative thoughts Internals: the thoughts are set to be very old, so that the game remove them
it removed. quickly after you unpause.
slayrace slayrace
======== ========
Kills any unit of a given race. Kills any unit of a given race.
With no argument, lists the available races. With no argument, lists the available races and count eligible targets.
With the special argument 'him', targets only the selected creature. With the special argument ``him``, targets only the selected creature.
With the special argument ``undead``, targets all undeads on the map,
regardless of their race.
Any non-dead non-caged unit of the specified race gets its ``blood_count`` Any non-dead non-caged unit of the specified race gets its ``blood_count``
set to 0, which means immediate death at the next game tick. For creatures set to 0, which means immediate death at the next game tick. For creatures
such as vampires, also set animal.vanish_countdown to 2. such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command, An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the ``magma``. In this case, a column of 7/7 magma is generated on top of the
@ -1737,7 +1968,106 @@ deathcause
========== ==========
Focus a body part ingame, and this script will display the cause of death of Focus a body part ingame, and this script will display the cause of death of
the creature. the creature.
Also works when selecting units from the 'u'nitlist viewscreen.
lua
===
There are the following ways to invoke this command:
1. ``lua`` (without any parameters)
This starts an interactive lua interpreter.
2. ``lua -f "filename"`` or ``lua --file "filename"``
This loads and runs the file indicated by filename.
3. ``lua -s ["filename"]`` or ``lua --save ["filename"]``
This loads and runs the file indicated by filename from the save
directory. If the filename is not supplied, it loads "dfhack.lua".
4. ``:lua`` *lua statement...*
Parses and executes the lua statement like the interactive interpreter would.
embark
======
Allows to embark anywhere. Currently windows only.
lever
=====
Allow manipulation of in-game levers from the dfhack console.
Can list levers, including state and links, with::
lever list
To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``.
This is the same as 'q'uerying the building and queue a 'P'ull request.
To magically toggle the lever immediately, use::
lever pull 42 --now
stripcaged
==========
For dumping items inside cages. Will mark selected items for dumping, then
a dwarf may come and actually dump it. See also ``autodump``.
With the ``items`` argument, only dumps items laying in the cage, excluding
stuff worn by caged creatures. ``weapons`` will dump worn weapons, ``armor``
will dump everything worn by caged creatures (including armor and clothing),
and ``all`` will dump everything, on a creature or not.
``stripcaged list`` will display on the dfhack console the list of all cages
and their item content.
Without further arguments, all commands work on all cages and animal traps on
the map. With the ``here`` argument, considers only the in-game selected cage
(or the cage under the game cursor). To target only specific cages, you can
alternatively pass cage IDs as arguments::
stripcaged weapons 25321 34228
create-items
============
Spawn arbitrary items under the cursor.
The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).
Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``,
``web``.
Instead of material, using ``list`` makes the script list eligible materials.
The ``web`` item category will create an uncollected cobweb on the floor.
Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Exemples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
create-items log list
create-items web CREATURE:SPIDER_CAVE_GIANT:SILK
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
soundsense-season
=================
It is a well known issue that Soundsense cannot detect the correct
current season when a savegame is loaded and has to play random
season music until a season switch occurs.
This script registers a hook that prints the appropriate string
to gamelog.txt on every map load to fix this. For best results
call the script from ``dfhack.init``.
======================= =======================
In-game interface tools In-game interface tools
@ -1751,6 +2081,9 @@ are mostly implemented by lua scripts.
In order to avoid user confusion, as a matter of policy all these tools In order to avoid user confusion, as a matter of policy all these tools
display the word "DFHack" on the screen somewhere while active. display the word "DFHack" on the screen somewhere while active.
When that is not appropriate because they merely add keybinding hints to
existing DF screens, they deliberately use red instead of green for the key.
As an exception, the tweak plugin described above does not follow this As an exception, the tweak plugin described above does not follow this
guideline because it arguably just fixes small usability bugs in the game UI. guideline because it arguably just fixes small usability bugs in the game UI.
@ -1761,12 +2094,18 @@ Dwarf Manipulator
Implemented by the manipulator plugin. To activate, open the unit screen and Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'. press 'l'.
.. image:: images/manipulator.png
This tool implements a Dwarf Therapist-like interface within the game UI. The This tool implements a Dwarf Therapist-like interface within the game UI. The
far left column displays the unit's Happiness (color-coded based on its far left column displays the unit's Happiness (color-coded based on its
value), and the right half of the screen displays each dwarf's labor settings value), and the right half of the screen displays each dwarf's labor settings
and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand
Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds Master, and U-Z for Legendary thru Legendary+5).
denote skills not controlled by labors.
Cells with teal backgrounds denote skills not controlled by labors, e.g.
military and social skills.
.. image:: images/manipulator2.png
Use the arrow keys or number pad to move the cursor around, holding Shift to Use the arrow keys or number pad to move the cursor around, holding Shift to
move 10 tiles at a time. move 10 tiles at a time.
@ -1800,19 +2139,116 @@ Pressing ESC normally returns to the unit screen, but Shift-ESC would exit
directly to the main dwarf mode screen. directly to the main dwarf mode screen.
Search
======
The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows
(unit list), Rooms, Announcements, Job List and Unit List screens.
.. image:: images/search.png
Searching works the same way as the search option in "Move to Depot" does.
You will see the Search option displayed on screen with a hotkey (usually 's').
Pressing it lets you start typing a query and the relevant list will start
filtering automatically.
Pressing ENTER, ESC or the arrow keys will return you to browsing the now
filtered list, which still functions as normal. You can clear the filter
by either going back into search mode and backspacing to delete it, or
pressing the "shifted" version of the search hotkey while browsing the
list (e.g. if the hotkey is 's', then hitting 'shift-s' will clear any
filter).
Leaving any screen automatically clears the filter.
In the Trade screen, the actual trade will always only act on items that
are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, the 't' key is
blocked while search is active, so you have to reset the filters first.
Pressing Alt-C will clear both search strings.
In the stockpile screen the option only appears if the cursor is in the
rightmost list:
.. image:: images/search-stockpile.png
Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only
on items actually shown in the rightmost list, so it is possible to select
only fat or tallow by forbidding fats, then searching for fat/tallow, and
using Permit Fats again while the list is filtered.
AutoMaterial
============
The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.
Firstly, it moves the last used material for a given construction type to the top of
the list, if there are any left. So if you build a wall with chalk blocks, the next
time you place a wall the chalk blocks will be at the top of the list, regardless of
distance (it only does this in "grouped" mode, as individual item lists could be huge).
This should mean you can place most constructions without having to search for your
preferred material type.
.. image:: images/automaterial-mat.png
Pressing 'a' while highlighting any material will enable that material for "auto select"
for this construction type. You can enable multiple materials as autoselect. Now the next
time you place this type of construction, the plugin will automatically choose materials
for you from the kinds you enabled. If there is enough to satisfy the whole placement,
you won't be prompted with the material screen - the construction will be placed and you
will be back in the construction menu as if you did it manually.
When choosing the construction placement, you will see a couple of options:
.. image:: images/automaterial-pos.png
Use 'a' here to temporarily disable the material autoselection, e.g. if you need
to go to the material selection screen so you can toggle some materials on or off.
The other option (auto type selection, off by default) can be toggled on with 't'. If you
toggle this option on, instead of returning you to the main construction menu after selecting
materials, it returns you back to this screen. If you use this along with several autoselect
enabled materials, you should be able to place complex constructions more conveniently.
gui/liquids gui/liquids
=========== ===========
To use, bind to a key and activate in the 'k' mode. To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.
.. image:: images/liquids.png
This script is a gui front-end to the liquids plugin and works similar to it,
allowing you to add or remove water & magma, and create obsidian walls & floors.
Note that there is **no undo support**, and that bugs in this plugin have been
known to create pathfinding problems and heat traps.
The ``b`` key changes how the affected area is selected. The default *Rectangle*
mode works by selecting two corners like any ordinary designation. The ``p``
key chooses between adding water, magma, obsidian walls & floors, or just
tweaking flags.
When painting liquids, it is possible to select the desired level with ``+-``,
and choose between setting it exactly, only increasing or only decreasing
with ``s``.
In addition, ``f`` allows disabling or enabling the flowing water computations
for an area, and ``r`` operates on the "permanent flow" property that makes
rivers power water wheels even when full and technically not flowing.
While active, use the suggested keys to switch the usual liquids parameters, and Enter After setting up the desired operations using the described keys, use ``Enter`` to apply them.
to select the target area and apply changes.
gui/mechanisms gui/mechanisms
============== ==============
To use, bind to a key and activate in the 'q' mode. To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.
.. image:: images/mechanisms.png
Lists mechanisms connected to the building, and their links. Navigating the list centers Lists mechanisms connected to the building, and their links. Navigating the list centers
the view on the relevant linked buildings. the view on the relevant linked buildings.
@ -1830,21 +2266,35 @@ via a simple dialog in the game ui.
* ``gui/rename [building]`` in 'q' mode changes the name of a building. * ``gui/rename [building]`` in 'q' mode changes the name of a building.
.. image:: images/rename-bld.png
The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. The selected building must be one of stockpile, workshop, furnace, trap, or siege engine.
It is also possible to rename zones from the 'i' menu. It is also possible to rename zones from the 'i' menu.
* ``gui/rename [unit]`` with a unit selected changes the nickname. * ``gui/rename [unit]`` with a unit selected changes the nickname.
Unlike the built-in interface, this works even on enemies and animals.
* ``gui/rename unit-profession`` changes the selected unit's custom profession name. * ``gui/rename unit-profession`` changes the selected unit's custom profession name.
.. image:: images/rename-prof.png
Likewise, this can be applied to any unit, and when used on animals it overrides
their species string.
The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state.
The example config binds building/unit rename to Ctrl-Shift-N, and
unit profession change to Ctrl-Shift-T.
gui/room-list gui/room-list
============= =============
To use, bind to a key and activate in the 'q' mode, either immediately or after opening To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
the assign owner page. either immediately or after opening the assign owner page.
.. image:: images/room-list.png
The script lists other rooms owned by the same owner, or by the unit selected in the assign The script lists other rooms owned by the same owner, or by the unit selected in the assign
list, and allows unassigning them. list, and allows unassigning them.
@ -1853,7 +2303,8 @@ list, and allows unassigning them.
gui/choose-weapons gui/choose-weapons
================== ==================
Bind to a key, and activate in the Equip->View/Customize page of the military screen. Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize
page of the military screen.
Depending on the cursor location, it rewrites all 'individual choice weapon' entries Depending on the cursor location, it rewrites all 'individual choice weapon' entries
in the selected squad or position to use a specific weapon type matching the assigned in the selected squad or position to use a specific weapon type matching the assigned
@ -1864,6 +2315,208 @@ Rationale: individual choice seems to be unreliable when there is a weapon short
and may lead to inappropriate weapons being selected. and may lead to inappropriate weapons being selected.
gui/guide-path
==============
Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with
the cursor over a Guide order.
.. image:: images/guide-path.png
The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time.
gui/workshop-job
================
Bind to a key (the example config uses Alt-A), and activate with a job selected in
a workshop in the 'q' mode.
.. image:: images/workshop-job.png
The script shows a list of the input reagents of the selected job, and allows changing
them like the ``job item-type`` and ``job item-material`` commands.
Specifically, pressing the 'i' key pops up a dialog that lets you select an item
type from a list.
.. image:: images/workshop-job-item.png
Pressing 'm', unless the item type does not allow a material,
lets you choose a material.
.. image:: images/workshop-job-material.png
Since there are a lot more materials than item types, this dialog is more complex
and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked
with an arrow on the left.
.. warning::
Due to the way input reagent matching works in DF, you must select an item type
if you select a material, or the material will be matched incorrectly in some cases.
If you press 'm' without choosing an item type, the script will auto-choose it
if there is only one valid choice, or pop up an error message box instead of the
material selection dialog.
Note that both materials and item types presented in the dialogs are filtered
by the job input flags, and even the selected item type for material selection,
or material for item type selection. Many jobs would let you select only one
input item type.
For example, if you choose a *plant* input item type for your prepare meal job,
it will only let you select cookable materials.
If you choose a *barrel* item instead (meaning things stored in barrels, like
drink or milk), it will let you select any material, since in this case the
material is matched against the barrel itself. Then, if you select, say, iron,
and then try to change the input item type, now it won't let you select *plant*;
you have to unset the material first.
gui/workflow
============
Bind to a key (the example config uses Alt-W), and activate with a job selected
in a workshop in the 'q' mode.
.. image:: images/workflow.png
This script provides a simple interface to constraints managed by the workflow
plugin. When active, it displays a list of all constraints applicable to the
current job, and their current status.
A constraint specifies a certain range to be compared against either individual
*item* or whole *stack* count, an item type and optionally a material. When the
current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it.
Pressing 'I' switches the current constraint between counting stacks or items.
Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting
items and expanding the range each gives a 2x bonus).
Pressing 'A' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by choosing one as template. If you
don't see the choice you want in the list, it likely means you have to adjust
the job material first using ``job item-material`` or ``gui/workshop-job``,
as described in ``workflow`` documentation above. In this manner, this feature
can be used for troubleshooting jobs that don't match the right constraints.
.. image:: images/workflow-new1.png
If you select one of the outputs with Enter, the matching constraint is simply
added to the list. If you use Shift-Enter, the interface proceeds to the
next dialog, which allows you to edit the suggested constraint parameters to
suit your need, and set the item count range.
.. image:: images/workflow-new2.png
Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen)
opens the overall status screen, which was copied from the C++ implementation
by falconne for better integration with the rest of the lua script:
.. image:: images/workflow-status.png
This screen shows all currently existing workflow constraints, and allows
monitoring and/or changing them from one screen. The constraint list can
be filtered by typing text in the field below.
The color of the stock level number indicates how "healthy" the stock level
is, based on current count and trend. Bright green is very good, green is good,
red is bad, bright red is very bad.
The limit number is also color-coded. Red means that there are currently no
workshops producing that item (i.e. no jobs). If it's yellow, that means the
production has been delayed, possibly due to lack of input materials.
The chart on the right is a plot of the last 14 days (28 half day plots) worth
of stock history for the selected item, with the rightmost point representing
the current stock value. The bright green dashed line is the target
limit (maximum) and the dark green line is that minus the gap (minimum).
gui/assign-rack
===============
Bind to a key (the example config uses P), and activate when viewing a weapon
rack in the 'q' mode.
.. image:: images/assign-rack.png
This script is part of a group of related fixes to make the armory storage
work again. The existing issues are:
* Weapon racks have to each be assigned to a specific squad, like with
beds/boxes/armor stands and individual squad members, but nothing in
the game does this. This issue is what this script addresses.
* Even if assigned by the script, **the game will unassign the racks again without a binary patch**.
This patch is called ``weaponrack-unassign``, and can be applied via
the binpatch program, or the matching script. See this for more info
about the bug:
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
* Haulers still take equpment stored in the armory away to the stockpiles,
unless the ``fix-armory`` plugin above is used.
The script interface simply lets you designate one of the squads that
are assigned to the barracks/armory containing the selected stand as
the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.
gui/advfort
=============
This script allows to perform jobs in adventure mode. For more complete help
press '?' while script is running. It's most confortable to use this as a
keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:
* -a or --nodfassign - uses different method to assign items.
* -i or --inventory - checks inventory for possible items to use in the job.
* -c or --cheat - relaxes item requirements for buildings (e.g. walls from bones).
implies -a
* job - selects that job (e.g. Dig or FellTree)
gui/companion-order
=======================
A script to issue orders for companions. Select companions with lower case chars, issue orders with upper
case. Must be in look or talk mode to issue command on tile.
* move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you.
* equip - try to equip items on the ground.
* pick-up - try to take items into hand (also wield)
* unequip - remove and drop equipment
* unwield - drop held items
* wait - temporarely remove from party
* follow - rejoin the party after "wait"
* leave - remove from party (can be rejoined by talking)
gui/gm-editor
=============
There are three ways to open this editor:
* using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)
* using gui/gm-editor <lua command> - executes lua command and opens editor on
it's results (e.g. gui/gm-editor "df.global.world.items.all" shows all items)
* using gui/gm-editor dialog - shows an in game dialog to input lua command. Works
the same as version above.
This editor allows to change and modify almost anything in df. Press '?' for an
in-game help.
============= =============
Behavior Mods Behavior Mods
============= =============
@ -1899,7 +2552,10 @@ Configuration UI
---------------- ----------------
The configuration front-end to the plugin is implemented by the gui/siege-engine The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key and activate after selecting a siege engine in 'q' mode. script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.
.. image:: images/siege-engine.png
The main mode displays the current target, selected ammo item type, linked stockpiles and The main mode displays the current target, selected ammo item type, linked stockpiles and
the allowed operator skill range. The map tile color is changed to signify if it can be the allowed operator skill range. The map tile color is changed to signify if it can be
@ -1929,7 +2585,10 @@ The power-meter plugin implements a modified pressure plate that detects power b
supplied to gear boxes built in the four adjacent N/S/W/E tiles. supplied to gear boxes built in the four adjacent N/S/W/E tiles.
The configuration front-end is implemented by the gui/power-meter script. Bind it to a The configuration front-end is implemented by the gui/power-meter script. Bind it to a
key and activate after selecting Pressure Plate in the build menu. key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate
in the build menu.
.. image:: images/power-meter.png
The script follows the general look and feel of the regular pressure plate build The script follows the general look and feel of the regular pressure plate build
configuration page, but configures parameters relevant to the modded power meter building. configuration page, but configures parameters relevant to the modded power meter building.
@ -2051,3 +2710,4 @@ be bought from caravans. :)
To be really useful this needs patches from bug 808, ``tweak fix-dimensions`` To be really useful this needs patches from bug 808, ``tweak fix-dimensions``
and ``tweak advmode-contained``. and ``tweak advmode-contained``.

@ -36,16 +36,14 @@
* internal hash function, calling * internal hash function, calling
* the basic methods from md5.h * the basic methods from md5.h
*/ */
std::string md5wrapper::hashit(std::string text) std::string md5wrapper::hashit(unsigned char *data, size_t length)
{ {
MD5Context ctx; MD5Context ctx;
//init md5 //init md5
MD5Init(&ctx); MD5Init(&ctx);
//update with our string //update with our string
MD5Update(&ctx, MD5Update(&ctx, data, length);
(unsigned char*)text.c_str(),
text.length());
//create the hash //create the hash
unsigned char buff[16] = ""; unsigned char buff[16] = "";
@ -95,10 +93,9 @@ md5wrapper::~md5wrapper()
*/ */
std::string md5wrapper::getHashFromString(std::string text) std::string md5wrapper::getHashFromString(std::string text)
{ {
return this->hashit(text); return this->hashit((unsigned char*)text.data(), text.length());
} }
/* /*
* creates a MD5 hash from * creates a MD5 hash from
* a file specified in "filename" and * a file specified in "filename" and

@ -31,7 +31,7 @@ class md5wrapper
* internal hash function, calling * internal hash function, calling
* the basic methods from md5.h * the basic methods from md5.h
*/ */
std::string hashit(std::string text); std::string hashit(unsigned char *data, size_t length);
/* /*
* converts the numeric giets to * converts the numeric giets to
@ -52,6 +52,10 @@ class md5wrapper
*/ */
std::string getHashFromString(std::string text); std::string getHashFromString(std::string text);
std::string getHashFromBytes(const unsigned char *data, size_t size) {
return hashit(const_cast<unsigned char*>(data),size);
}
/* /*
* creates a MD5 hash from * creates a MD5 hash from
* a file specified in "filename" and * a file specified in "filename" and

@ -2,23 +2,34 @@
# Generic dwarfmode bindings # # Generic dwarfmode bindings #
############################## ##############################
# toggle the display of water level as 1-7 tiles
keybinding add Ctrl-W twaterlvl keybinding add Ctrl-W twaterlvl
# with cursor: # with cursor:
# designate the whole vein for digging
keybinding add Ctrl-V digv keybinding add Ctrl-V digv
keybinding add Ctrl-Shift-V "digv x" keybinding add Ctrl-Shift-V "digv x"
# clean the selected tile of blood etc
keybinding add Ctrl-C spotclean keybinding add Ctrl-C spotclean
# destroy items designated for dump in the selected tile
keybinding add Ctrl-Shift-K autodump-destroy-here keybinding add Ctrl-Shift-K autodump-destroy-here
# any item: # with an item selected:
# destroy the selected item
keybinding add Ctrl-K autodump-destroy-item keybinding add Ctrl-K autodump-destroy-item
# scripts:
# quicksave, only in main dwarfmode screen and menu page # quicksave, only in main dwarfmode screen and menu page
keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
# gui/rename script # gui/rename script - rename units and buildings
keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-N gui/rename
keybinding add Alt-Shift-P "gui/rename unit-profession" keybinding add Ctrl-Shift-T "gui/rename unit-profession"
############################## ##############################
# Generic adv mode bindings # # Generic adv mode bindings #
@ -31,10 +42,10 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force"
# Context-specific bindings # # Context-specific bindings #
############################# #############################
# q->stockpile; p # q->stockpile; p - copy & paste stockpiles
keybinding add Alt-P copystock keybinding add Alt-P copystock
# q->workshop # q->workshop - duplicate the selected job
keybinding add Ctrl-D job-duplicate keybinding add Ctrl-D job-duplicate
# materials: q->workshop; b->select items # materials: q->workshop; b->select items
@ -48,7 +59,7 @@ keybinding add Shift-O "job-material OBSIDIAN"
keybinding add Shift-T "job-material ORTHOCLASE" keybinding add Shift-T "job-material ORTHOCLASE"
keybinding add Shift-G "job-material GLASS_GREEN" keybinding add Shift-G "job-material GLASS_GREEN"
# sort units and items # sort units and items in the on-screen list
keybinding add Alt-Shift-N "sort-units name" "sort-items description" keybinding add Alt-Shift-N "sort-units name" "sort-items description"
keybinding add Alt-Shift-R "sort-units arrival" keybinding add Alt-Shift-R "sort-units arrival"
keybinding add Alt-Shift-T "sort-units profession" "sort-items type material" keybinding add Alt-Shift-T "sort-units profession" "sort-items type material"
@ -60,7 +71,7 @@ keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
# browse rooms of same owner # browse rooms of same owner
keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
# interface for the liquids plugin # interface for the liquids plugin - spawn water/magma/obsidian
keybinding add Alt-L@dwarfmode/LookAround gui/liquids keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction # machine power sensitive pressure plate construction
@ -72,6 +83,19 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
# military weapon auto-select # military weapon auto-select
keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons
# minecart Guide path
keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path
# workshop job details
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
# workflow front-end
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status"
# assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
############################ ############################
# UI and game logic tweaks # # UI and game logic tweaks #
############################ ############################
@ -109,3 +133,39 @@ tweak fast-trade
tweak military-stable-assign tweak military-stable-assign
# in same list, color units already assigned to squads in brown & green # in same list, color units already assigned to squads in brown & green
tweak military-color-assigned tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
###########
# Scripts #
###########
# write the correct season to gamelog on world load
soundsense-season
# patch the material objects in memory to fix cloth stockpiles
fix/cloth-stockpile enable
#######################################################
# Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
#######################################################
# Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items
#binpatch apply hospital-overstocking
# Bug 808 - custom reactions completely using up all of their reagents
#binpatch apply custom-reagent-size
# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo
#binpatch apply training-ammo
# Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

@ -111,6 +111,7 @@ include/modules/Burrows.h
include/modules/Constructions.h include/modules/Constructions.h
include/modules/Units.h include/modules/Units.h
include/modules/Engravings.h include/modules/Engravings.h
include/modules/EventManager.h
include/modules/Gui.h include/modules/Gui.h
include/modules/Items.h include/modules/Items.h
include/modules/Job.h include/modules/Job.h
@ -121,7 +122,6 @@ include/modules/Materials.h
include/modules/Notes.h include/modules/Notes.h
include/modules/Screen.h include/modules/Screen.h
include/modules/Translation.h include/modules/Translation.h
include/modules/Vegetation.h
include/modules/Vermin.h include/modules/Vermin.h
include/modules/World.h include/modules/World.h
include/modules/Graphic.h include/modules/Graphic.h
@ -133,6 +133,7 @@ modules/Burrows.cpp
modules/Constructions.cpp modules/Constructions.cpp
modules/Units.cpp modules/Units.cpp
modules/Engravings.cpp modules/Engravings.cpp
modules/EventManager.cpp
modules/Gui.cpp modules/Gui.cpp
modules/Items.cpp modules/Items.cpp
modules/Job.cpp modules/Job.cpp
@ -142,7 +143,6 @@ modules/Materials.cpp
modules/Notes.cpp modules/Notes.cpp
modules/Screen.cpp modules/Screen.cpp
modules/Translation.cpp modules/Translation.cpp
modules/Vegetation.cpp
modules/Vermin.cpp modules/Vermin.cpp
modules/World.cpp modules/World.cpp
modules/Graphic.cpp modules/Graphic.cpp
@ -167,7 +167,7 @@ IF(UNIX)
IF(BUILD_EGGY) IF(BUILD_EGGY)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY})
ELSEIF(APPLE) ELSEIF(APPLE)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN})
ELSE() ELSE()
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX})
ENDIF() ENDIF()
@ -235,7 +235,7 @@ ENDIF()
IF(UNIX) IF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE) IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF() ENDIF()
ELSE(WIN32) ELSE(WIN32)
#FIXME: do we really need psapi? #FIXME: do we really need psapi?
@ -250,6 +250,9 @@ ADD_DEPENDENCIES(dfhack-client dfhack)
ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp)
ADD_EXECUTABLE(binpatch binpatch.cpp)
TARGET_LINK_LIBRARIES(binpatch dfhack-md5)
IF(BUILD_EGGY) IF(BUILD_EGGY)
SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" )
else() else()
@ -270,14 +273,14 @@ ENDIF()
SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE) IF(APPLE)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib)
SET(ZIP_LIBRARY /usr/lib/libz.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY})
SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF() ENDIF()
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS}) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS})
@ -287,20 +290,20 @@ TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
if(APPLE) if(APPLE)
add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...") add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...")
endif() endif()
IF(UNIX) IF(UNIX)
if (APPLE) if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .) DESTINATION .)
else() else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD # On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
DESTINATION .) DESTINATION .)
endif() endif()
ELSE() ELSE()
@ -329,7 +332,7 @@ install(FILES xml/symbols.xml
install(FILES ../dfhack.init-example install(FILES ../dfhack.init-example
DESTINATION ${DFHACK_BINARY_DESTINATION}) DESTINATION ${DFHACK_BINARY_DESTINATION})
install(TARGETS dfhack-run dfhack-client install(TARGETS dfhack-run dfhack-client binpatch
LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION}
RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION})
@ -343,6 +346,10 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
PATTERN "*.rb" PATTERN "*.rb"
) )
install(DIRECTORY ${dfhack_SOURCE_DIR}/patches
DESTINATION ${DFHACK_DATA_DESTINATION}
FILES_MATCHING PATTERN "*.dif")
# Unused for so long that it's not even relevant now... # Unused for so long that it's not even relevant now...
if(BUILD_DEVEL) if(BUILD_DEVEL)
if(WIN32) if(WIN32)

@ -44,6 +44,7 @@ using namespace std;
#include "VersionInfo.h" #include "VersionInfo.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "modules/EventManager.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Graphic.h" #include "modules/Graphic.h"
@ -316,7 +317,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr,
rbcmd += "'" + args[i] + "', "; rbcmd += "'" + args[i] + "', ";
rbcmd += "]\n"; rbcmd += "]\n";
rbcmd += "load './hack/scripts/" + name + ".rb'"; rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }";
return plug_mgr->eval_ruby(out, rbcmd.c_str()); return plug_mgr->eval_ruby(out, rbcmd.c_str());
} }
@ -343,6 +344,52 @@ command_result Core::runCommand(color_ostream &out, const std::string &command)
return CR_NOT_IMPLEMENTED; return CR_NOT_IMPLEMENTED;
} }
static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed)
{
std::vector<std::string> possible;
auto plug_mgr = Core::getInstance().getPluginManager();
for(size_t i = 0; i < plug_mgr->size(); i++)
{
const Plugin * plug = (plug_mgr->operator[](i));
for (size_t j = 0; j < plug->size(); j++)
{
const PluginCommand &pcmd = plug->operator[](j);
if (pcmd.isHotkeyCommand())
continue;
if (pcmd.name.substr(0, first.size()) == first)
possible.push_back(pcmd.name);
}
}
bool all = (first.find('/') != std::string::npos);
std::map<string, string> scripts;
listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all);
for (auto iter = scripts.begin(); iter != scripts.end(); ++iter)
if (iter->first.substr(0, first.size()) == first)
possible.push_back(iter->first);
if (possible.size() == 1)
{
completed = possible[0];
//fprintf(stderr, "Autocompleted %s to %s\n", , );
con.printerr("%s is not recognized. Did you mean %s?\n", first.c_str(), completed.c_str());
return true;
}
if (possible.size() > 1 && possible.size() < 8)
{
std::string out;
for (size_t i = 0; i < possible.size(); i++)
out += " " + possible[i];
con.printerr("%s is not recognized. Possible completions:%s\n", first.c_str(), out.c_str());
return true;
}
return false;
}
command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts) command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts)
{ {
if (!first.empty()) if (!first.empty())
@ -374,6 +421,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
" unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n"
); );
con.print("\nDFHack version " DFHACK_VERSION ".\n");
} }
else if (parts.size() == 1) else if (parts.size() == 1)
{ {
@ -627,8 +676,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
} }
else if(first == "fpause") else if(first == "fpause")
{ {
World * w = getWorld(); World::SetPauseState(true);
w->SetPauseState(true);
con.print("The game was forced to pause!\n"); con.print("The game was forced to pause!\n");
} }
else if(first == "cls") else if(first == "cls")
@ -664,10 +712,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
if(res == CR_NOT_IMPLEMENTED) if(res == CR_NOT_IMPLEMENTED)
{ {
auto filename = getHackPath() + "scripts/" + first; auto filename = getHackPath() + "scripts/" + first;
std::string completed;
if (fileExists(filename + ".lua")) if (fileExists(filename + ".lua"))
res = runLuaScript(con, first, parts); res = runLuaScript(con, first, parts);
else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) else if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
res = runRubyScript(con, plug_mgr, first, parts); res = runRubyScript(con, plug_mgr, first, parts);
else if (try_autocomplete(con, first, completed))
return CR_NOT_IMPLEMENTED;// runCommand(con, completed, parts);
else else
con.printerr("%s is not a recognized command.\n", first.c_str()); con.printerr("%s is not a recognized command.\n", first.c_str());
} }
@ -732,7 +784,6 @@ void fIOthread(void * iodata)
{ {
string command = ""; string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history); int ret = con.lineedit("[DFHack]# ",command, main_history);
fprintf(stderr,"Command: [%s]\n",command.c_str());
if(ret == -2) if(ret == -2)
{ {
cerr << "Console is shutting down properly." << endl; cerr << "Console is shutting down properly." << endl;
@ -746,14 +797,10 @@ void fIOthread(void * iodata)
else if(ret) else if(ret)
{ {
// a proper, non-empty command was entered // a proper, non-empty command was entered
fprintf(stderr,"Adding command to history\n");
main_history.add(command); main_history.add(command);
fprintf(stderr,"Saving history\n");
main_history.save("dfhack.history"); main_history.save("dfhack.history");
} }
fprintf(stderr,"Running command\n");
auto rv = core->runCommand(con, command); auto rv = core->runCommand(con, command);
if (rv == CR_NOT_IMPLEMENTED) if (rv == CR_NOT_IMPLEMENTED)
@ -821,6 +868,8 @@ std::string Core::getHackPath()
#endif #endif
} }
void init_screen_module(Core *);
bool Core::Init() bool Core::Init()
{ {
if(started) if(started)
@ -891,6 +940,7 @@ bool Core::Init()
*/ */
// initialize data defs // initialize data defs
virtual_identity::Init(this); virtual_identity::Init(this);
init_screen_module(this);
// initialize common lua context // initialize common lua context
Lua::Core::Init(con); Lua::Core::Init(con);
@ -900,6 +950,7 @@ bool Core::Init()
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(this);
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
@ -1122,7 +1173,7 @@ void Core::doUpdate(color_ostream &out, bool first_update)
last_world_data_ptr = new_wdata; last_world_data_ptr = new_wdata;
last_local_map_ptr = new_mapdata; last_local_map_ptr = new_mapdata;
getWorld()->ClearPersistentCache(); World::ClearPersistentCache();
// and if the world is going away, we report the map change first // and if the world is going away, we report the map change first
if(had_map) if(had_map)
@ -1140,7 +1191,7 @@ void Core::doUpdate(color_ostream &out, bool first_update)
if (isMapLoaded() != had_map) if (isMapLoaded() != had_map)
{ {
getWorld()->ClearPersistentCache(); World::ClearPersistentCache();
onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
} }
} }
@ -1234,6 +1285,8 @@ static int buildings_timer = 0;
void Core::onUpdate(color_ostream &out) void Core::onUpdate(color_ostream &out)
{ {
EventManager::manageEvents(out);
// convert building reagents // convert building reagents
if (buildings_do_onupdate && (++buildings_timer & 1)) if (buildings_do_onupdate && (++buildings_timer & 1))
buildings_onUpdate(out); buildings_onUpdate(out);
@ -1247,6 +1300,8 @@ void Core::onUpdate(color_ostream &out)
void Core::onStateChange(color_ostream &out, state_change_event event) void Core::onStateChange(color_ostream &out, state_change_event event)
{ {
EventManager::onStateChange(out, event);
buildings_onStateChange(out, event); buildings_onStateChange(out, event);
plug_mgr->OnStateChange(out, event); plug_mgr->OnStateChange(out, event);
@ -1611,15 +1666,27 @@ void ClassNameCheck::getKnownClassNames(std::vector<std::string> &names)
names.push_back(*it); names.push_back(*it);
} }
bool Process::patchMemory(void *target, const void* src, size_t count) MemoryPatcher::MemoryPatcher(Process *p_) : p(p_)
{
if (!p)
p = Core::getInstance().p;
}
MemoryPatcher::~MemoryPatcher()
{
close();
}
bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write)
{ {
uint8_t *sptr = (uint8_t*)target; uint8_t *sptr = (uint8_t*)target;
uint8_t *eptr = sptr + count; uint8_t *eptr = sptr + count;
// Find the valid memory ranges // Find the valid memory ranges
std::vector<t_memrange> ranges; if (ranges.empty())
getMemRanges(ranges); p->getMemRanges(ranges);
// Find the ranges that this area spans
unsigned start = 0; unsigned start = 0;
while (start < ranges.size() && ranges[start].end <= sptr) while (start < ranges.size() && ranges[start].end <= sptr)
start++; start++;
@ -1642,23 +1709,45 @@ bool Process::patchMemory(void *target, const void* src, size_t count)
return false; return false;
// Apply writable permissions & update // Apply writable permissions & update
bool ok = true; for (unsigned i = start; i < end; i++)
for (unsigned i = start; i < end && ok; i++)
{ {
t_memrange perms = ranges[i]; auto &perms = ranges[i];
if ((perms.write || !write) && perms.read)
continue;
save.push_back(perms);
perms.write = perms.read = true; perms.write = perms.read = true;
if (!setPermisions(perms, perms)) if (!p->setPermisions(perms, perms))
ok = false; return false;
} }
if (ok) return true;
memmove(target, src, count); }
bool MemoryPatcher::write(void *target, const void *src, size_t size)
{
if (!makeWritable(target, size))
return false;
for (unsigned i = start; i < end && ok; i++) memmove(target, src, size);
setPermisions(ranges[i], ranges[i]); return true;
}
void MemoryPatcher::close()
{
for (size_t i = 0; i < save.size(); i++)
p->setPermisions(save[i], save[i]);
save.clear();
ranges.clear();
};
bool Process::patchMemory(void *target, const void* src, size_t count)
{
MemoryPatcher patcher(this);
return ok; return patcher.write(target, src, count);
} }
/******************************************************************************* /*******************************************************************************
@ -1678,7 +1767,6 @@ TYPE * Core::get##TYPE() \
return s_mods.p##TYPE;\ return s_mods.p##TYPE;\
} }
MODULE_GETTER(World);
MODULE_GETTER(Materials); MODULE_GETTER(Materials);
MODULE_GETTER(Notes); MODULE_GETTER(Notes);
MODULE_GETTER(Graphic); MODULE_GETTER(Graphic);

@ -271,39 +271,39 @@ DFhackCExport vPtr SDL_SetVideoMode(int width, int height, int bpp, uint32_t fla
static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0;
DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect)
{ {
if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 )
{ {
DFHack::Core & c = DFHack::Core::getInstance(); DFHack::Core & c = DFHack::Core::getInstance();
DFHack::Graphic* g = c.getGraphic(); DFHack::Graphic* g = c.getGraphic();
DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h);
if ( ov != NULL ) if ( ov != NULL )
{ {
if ( ov->paintOver ) if ( ov->paintOver )
{ {
_SDL_UpperBlit(src, srcrect, dst, dstrect); _SDL_UpperBlit(src, srcrect, dst, dstrect);
} }
DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect; DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect;
dstrect2->x = dstrect->x; dstrect2->x = dstrect->x;
dstrect2->y = dstrect->y; dstrect2->y = dstrect->y;
dstrect2->w = dstrect->w; dstrect2->w = dstrect->w;
dstrect2->h = dstrect->h; dstrect2->h = dstrect->h;
if ( ov->dstResize != NULL ) if ( ov->dstResize != NULL )
{ {
DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize; DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize;
dstrect2->x += r->x; dstrect2->x += r->x;
dstrect2->y += r->y; dstrect2->y += r->y;
dstrect2->w += r->w; dstrect2->w += r->w;
dstrect2->h += r->h; dstrect2->h += r->h;
} }
int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2); int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2);
delete dstrect2; delete dstrect2;
return result; return result;
} }
} }
return _SDL_UpperBlit(src, srcrect, dst, dstrect); return _SDL_UpperBlit(src, srcrect, dst, dstrect);
} }

@ -30,6 +30,7 @@ distribution.
#include "MemAccess.h" #include "MemAccess.h"
#include "Core.h" #include "Core.h"
#include "Error.h"
#include "VersionInfo.h" #include "VersionInfo.h"
#include "tinythread.h" #include "tinythread.h"
// must be last due to MS stupidity // must be last due to MS stupidity
@ -81,6 +82,7 @@ distribution.
#include "df/flow_info.h" #include "df/flow_info.h"
#include "df/unit_misc_trait.h" #include "df/unit_misc_trait.h"
#include "df/proj_itemst.h" #include "df/proj_itemst.h"
#include "df/itemdef.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -258,7 +260,7 @@ static PersistentDataItem persistent_by_struct(lua_State *state, int idx)
int id = lua_tointeger(state, -1); int id = lua_tointeger(state, -1);
lua_pop(state, 1); lua_pop(state, 1);
PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); PersistentDataItem ref = World::GetPersistentData(id);
if (ref.isValid()) if (ref.isValid())
{ {
@ -311,19 +313,19 @@ static PersistentDataItem get_persistent(lua_State *state)
if (lua_istable(state, 1)) if (lua_istable(state, 1))
{ {
Lua::StackUnwinder frame(state);
if (!lua_getmetatable(state, 1) || if (!lua_getmetatable(state, 1) ||
!lua_rawequal(state, -1, lua_upvalueindex(1))) !lua_rawequal(state, -1, lua_upvalueindex(1)))
luaL_argerror(state, 1, "invalid table type"); luaL_argerror(state, 1, "invalid table type");
lua_settop(state, 1);
return persistent_by_struct(state, 1); return persistent_by_struct(state, 1);
} }
else else
{ {
const char *str = luaL_checkstring(state, 1); const char *str = luaL_checkstring(state, 1);
return Core::getInstance().getWorld()->GetPersistentData(str); return World::GetPersistentData(str);
} }
} }
@ -342,7 +344,7 @@ static int dfhack_persistent_delete(lua_State *state)
auto ref = get_persistent(state); auto ref = get_persistent(state);
bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); bool ok = World::DeletePersistentData(ref);
lua_pushboolean(state, ok); lua_pushboolean(state, ok);
return 1; return 1;
@ -356,7 +358,7 @@ static int dfhack_persistent_get_all(lua_State *state)
bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false);
std::vector<PersistentDataItem> data; std::vector<PersistentDataItem> data;
Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); World::GetPersistentData(&data, str, prefix);
if (data.empty()) if (data.empty())
{ {
@ -396,7 +398,7 @@ static int dfhack_persistent_save(lua_State *state)
if (add) if (add)
{ {
ref = Core::getInstance().getWorld()->AddPersistentData(str); ref = World::AddPersistentData(str);
added = true; added = true;
} }
else if (lua_getmetatable(state, 1)) else if (lua_getmetatable(state, 1))
@ -409,13 +411,13 @@ static int dfhack_persistent_save(lua_State *state)
} }
else else
{ {
ref = Core::getInstance().getWorld()->GetPersistentData(str); ref = World::GetPersistentData(str);
} }
// Auto-add if not found // Auto-add if not found
if (!ref.isValid()) if (!ref.isValid())
{ {
ref = Core::getInstance().getWorld()->AddPersistentData(str); ref = World::AddPersistentData(str);
if (!ref.isValid()) if (!ref.isValid())
luaL_error(state, "cannot create persistent entry"); luaL_error(state, "cannot create persistent entry");
added = true; added = true;
@ -446,11 +448,38 @@ static int dfhack_persistent_save(lua_State *state)
return 2; return 2;
} }
static int dfhack_persistent_getTilemask(lua_State *state)
{
CoreSuspender suspend;
lua_settop(state, 3);
auto ref = get_persistent(state);
auto block = Lua::CheckDFObject<df::map_block>(state, 2);
bool create = lua_toboolean(state, 3);
Lua::PushDFObject(state, World::getPersistentTilemask(ref, block, create));
return 1;
}
static int dfhack_persistent_deleteTilemask(lua_State *state)
{
CoreSuspender suspend;
lua_settop(state, 2);
auto ref = get_persistent(state);
auto block = Lua::CheckDFObject<df::map_block>(state, 2);
lua_pushboolean(state, World::deletePersistentTilemask(ref, block));
return 1;
}
static const luaL_Reg dfhack_persistent_funcs[] = { static const luaL_Reg dfhack_persistent_funcs[] = {
{ "get", dfhack_persistent_get }, { "get", dfhack_persistent_get },
{ "delete", dfhack_persistent_delete }, { "delete", dfhack_persistent_delete },
{ "get_all", dfhack_persistent_get_all }, { "get_all", dfhack_persistent_get_all },
{ "save", dfhack_persistent_save }, { "save", dfhack_persistent_save },
{ "getTilemask", dfhack_persistent_getTilemask },
{ "deleteTilemask", dfhack_persistent_deleteTilemask },
{ NULL, NULL } { NULL, NULL }
}; };
@ -471,7 +500,9 @@ static void OpenPersistent(lua_State *state)
* Material info lookup * * Material info lookup *
************************/ ************************/
static void push_matinfo(lua_State *state, MaterialInfo &info) static int DFHACK_MATINFO_TOKEN = 0;
void Lua::Push(lua_State *state, MaterialInfo &info)
{ {
if (!info.isValid()) if (!info.isValid())
{ {
@ -480,7 +511,7 @@ static void push_matinfo(lua_State *state, MaterialInfo &info)
} }
lua_newtable(state); lua_newtable(state);
lua_pushvalue(state, lua_upvalueindex(1)); lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN);
lua_setmetatable(state, -2); lua_setmetatable(state, -2);
lua_pushinteger(state, info.type); lua_pushinteger(state, info.type);
@ -535,7 +566,7 @@ static int dfhack_matinfo_find(lua_State *state)
info.find(tokens); info.find(tokens);
} }
push_matinfo(state, info); Lua::Push(state, info);
return 1; return 1;
} }
@ -603,7 +634,7 @@ static int dfhack_matinfo_decode(lua_State *state)
{ {
MaterialInfo info; MaterialInfo info;
decode_matinfo(state, &info, true); decode_matinfo(state, &info, true);
push_matinfo(state, info); Lua::Push(state, info);
return 1; return 1;
} }
@ -682,6 +713,9 @@ static void OpenMatinfo(lua_State *state)
{ {
luaL_getsubtable(state, lua_gettop(state), "matinfo"); luaL_getsubtable(state, lua_gettop(state), "matinfo");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN);
lua_dup(state); lua_dup(state);
luaL_setfuncs(state, dfhack_matinfo_funcs, 1); luaL_setfuncs(state, dfhack_matinfo_funcs, 1);
@ -691,6 +725,316 @@ static void OpenMatinfo(lua_State *state)
lua_pop(state, 1); lua_pop(state, 1);
} }
/**************
* Pen object *
**************/
static int DFHACK_PEN_TOKEN = 0;
void Lua::Push(lua_State *L, const Screen::Pen &info)
{
if (!info.valid())
{
lua_pushnil(L);
return;
}
void *pdata = lua_newuserdata(L, sizeof(Pen));
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
lua_setmetatable(L, -2);
new (pdata) Pen(info);
}
static Pen *check_pen_native(lua_State *L, int index)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2))
luaL_argerror(L, index, "not a pen object");
lua_pop(L, 2);
return (Pen*)lua_touserdata(L, index);
}
void Lua::CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil, bool allow_color)
{
index = lua_absindex(L, index);
luaL_checkany(L, index);
if (lua_isnil(L, index))
{
if (!allow_nil)
luaL_argerror(L, index, "nil pen not allowed");
*pen = Pen(0,0,0,-1);
}
else if (lua_isuserdata(L, index))
{
*pen = *check_pen_native(L, index);
}
else if (allow_color && lua_isnumber(L, index))
{
*pen = Pen(0, lua_tointeger(L, index)&15, 0);
}
else
{
luaL_checktype(L, index, LUA_TTABLE);
decode_pen(L, *pen, index);
}
}
static int adjust_pen(lua_State *L, bool no_copy)
{
lua_settop(L, 4);
Pen pen;
int iidx = 1;
Lua::CheckPen(L, &pen, 1, true, true);
if (!lua_isnil(L, 2) || !lua_isnil(L, 3) || !lua_isnil(L, 4))
{
if (lua_isnumber(L, 2) || lua_isnil(L, 2))
{
if (!pen.valid())
pen = Pen();
iidx = -1;
pen.fg = luaL_optint(L, 2, pen.fg) & 15;
pen.bg = luaL_optint(L, 3, pen.bg);
if (!lua_isnil(L, 4))
pen.bold = lua_toboolean(L, 4);
else if (!lua_isnil(L, 2))
{
pen.bold = !!(pen.fg & 8);
pen.fg &= 7;
}
}
else
{
iidx = 2;
Lua::CheckPen(L, &pen, 2, false, false);
}
}
if (no_copy && iidx > 0 && lua_isuserdata(L, iidx))
lua_pushvalue(L, iidx);
else
Lua::Push(L, pen);
return 1;
}
static int dfhack_pen_parse(lua_State *L)
{
return adjust_pen(L, true);
}
static int dfhack_pen_make(lua_State *L)
{
return adjust_pen(L, false);
}
static void make_pen_table(lua_State *L, Pen &pen)
{
if (!pen.valid())
luaL_error(L, "invalid pen state");
else
{
lua_newtable(L);
lua_pushinteger(L, (unsigned char)pen.ch); lua_setfield(L, -2, "ch");
lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
if (pen.tile)
{
lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
}
switch (pen.tile_mode) {
case Pen::CharColor:
lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
break;
case Pen::TileColor:
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
break;
default:
lua_pushboolean(L, false); lua_setfield(L, -2, "tile_color");
break;
}
}
}
static void get_pen_mirror(lua_State *L, int idx)
{
lua_getuservalue(L, idx);
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
Pen pen;
Lua::CheckPen(L, &pen, idx, false, false);
make_pen_table(L, pen);
lua_dup(L);
lua_setuservalue(L, idx);
}
}
static int dfhack_pen_index(lua_State *L)
{
lua_settop(L, 2);
luaL_checktype(L, 1, LUA_TUSERDATA);
// check metatable
if (!lua_getmetatable(L, 1))
luaL_argerror(L, 1, "must be a pen");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (!lua_isnil(L, -1))
return 1;
// otherwise read from the mirror table, creating it if necessary
lua_settop(L, 2);
get_pen_mirror(L, 1);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
return 1;
}
static int pen_pnext(lua_State *L)
{
lua_settop(L, 2); /* create a 2nd argument if there isn't one */
if (lua_next(L, lua_upvalueindex(1)))
return 2;
lua_pushnil(L);
return 1;
}
static int dfhack_pen_pairs(lua_State *L)
{
luaL_checktype(L, 1, LUA_TUSERDATA);
get_pen_mirror(L, 1);
lua_pushcclosure(L, pen_pnext, 1);
lua_pushnil(L);
lua_pushnil(L);
return 3;
}
const char *const pen_fields[] = {
"ch", "fg", "bold", "bg", "tile", "tile_color", "tile_fg", "tile_bg", NULL
};
static int dfhack_pen_newindex(lua_State *L)
{
lua_settop(L, 3);
luaL_checktype(L, 1, LUA_TUSERDATA);
int id = luaL_checkoption(L, 2, NULL, pen_fields);
int arg = 0;
Pen &pen = *check_pen_native(L, 1);
bool wipe_tile = false, wipe_tc = false;
switch (id) {
case 0:
if (lua_type(L, 3) != LUA_TNUMBER)
arg = (unsigned char)*luaL_checkstring(L, 3);
else
arg = luaL_checkint(L, 3);
pen.ch = arg;
lua_pushinteger(L, (unsigned char)pen.ch);
break;
case 1:
pen.fg = luaL_checkint(L, 3) & 15;
lua_pushinteger(L, pen.fg);
break;
case 2:
pen.bold = lua_toboolean(L, 3);
lua_pushboolean(L, pen.bold);
break;
case 3:
pen.bg = luaL_checkint(L, 3) & 15;
lua_pushinteger(L, pen.bg);
break;
case 4:
arg = lua_isnil(L, 3) ? 0 : luaL_checkint(L, 3);
if (arg < 0)
luaL_argerror(L, 3, "invalid tile index");
pen.tile = arg;
if (pen.tile)
lua_pushinteger(L, pen.tile);
else
lua_pushnil(L);
break;
case 5:
wipe_tile = (pen.tile_mode == Pen::TileColor);
pen.tile_mode = lua_toboolean(L, 3) ? Pen::CharColor : Pen::AsIs;
lua_pushboolean(L, pen.tile_mode == Pen::CharColor);
break;
case 6:
if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_bg = 0; }
pen.tile_fg = luaL_checkint(L, 3) & 15;
pen.tile_mode = Pen::TileColor;
lua_pushinteger(L, pen.tile_fg);
break;
case 7:
if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_fg = 7; }
pen.tile_bg = luaL_checkint(L, 3) & 15;
pen.tile_mode = Pen::TileColor;
lua_pushinteger(L, pen.tile_bg);
break;
}
lua_getuservalue(L, 1);
if (!lua_isnil(L, -1))
{
lua_remove(L, 3);
lua_insert(L, 2);
lua_rawset(L, 2);
if (wipe_tc) {
lua_pushnil(L); lua_setfield(L, 2, "tile_color");
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, 2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, 2, "tile_bg");
}
if (wipe_tile) {
lua_pushnil(L); lua_setfield(L, 2, "tile_fg");
lua_pushnil(L); lua_setfield(L, 2, "tile_bg");
}
}
return 0;
}
static const luaL_Reg dfhack_pen_funcs[] = {
{ "parse", dfhack_pen_parse },
{ "make", dfhack_pen_make },
{ "__index", dfhack_pen_index },
{ "__pairs", dfhack_pen_pairs },
{ "__newindex", dfhack_pen_newindex },
{ NULL, NULL }
};
static void OpenPen(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "pen");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
luaL_setfuncs(state, dfhack_pen_funcs, 0);
lua_pop(state, 1);
}
/************************ /************************
* Wrappers for C++ API * * Wrappers for C++ API *
************************/ ************************/
@ -777,10 +1121,14 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,cloneJobStruct), WRAPM(Job,cloneJobStruct),
WRAPM(Job,printItemDetails), WRAPM(Job,printItemDetails),
WRAPM(Job,printJobDetails), WRAPM(Job,printJobDetails),
WRAPM(Job,getGeneralRef),
WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder), WRAPM(Job,getHolder),
WRAPM(Job,getWorker), WRAPM(Job,getWorker),
WRAPM(Job,checkBuildingsNow), WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow), WRAPM(Job,checkDesignationsNow),
WRAPM(Job,isSuitableItem),
WRAPM(Job,isSuitableMaterial),
WRAPN(is_equal, jobEqual), WRAPN(is_equal, jobEqual),
WRAPN(is_item_equal, jobItemEqual), WRAPN(is_item_equal, jobItemEqual),
{ NULL, NULL } { NULL, NULL }
@ -811,6 +1159,8 @@ static const luaL_Reg dfhack_job_funcs[] = {
/***** Units module *****/ /***** Units module *****/
static const LuaWrapper::FunctionReg dfhack_units_module[] = { static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getGeneralRef),
WRAPM(Units, getSpecificRef),
WRAPM(Units, getContainer), WRAPM(Units, getContainer),
WRAPM(Units, setNickname), WRAPM(Units, setNickname),
WRAPM(Units, getVisibleName), WRAPM(Units, getVisibleName),
@ -830,6 +1180,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getNominalSkill), WRAPM(Units, getNominalSkill),
WRAPM(Units, getEffectiveSkill), WRAPM(Units, getEffectiveSkill),
WRAPM(Units, getExperience),
WRAPM(Units, computeMovementSpeed), WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),
@ -906,7 +1257,12 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getOwner), WRAPM(Items, getOwner),
WRAPM(Items, setOwner), WRAPM(Items, setOwner),
WRAPM(Items, getContainer), WRAPM(Items, getContainer),
WRAPM(Items, getHolderBuilding),
WRAPM(Items, getHolderUnit),
WRAPM(Items, getDescription), WRAPM(Items, getDescription),
WRAPM(Items, isCasteMaterial),
WRAPM(Items, getSubtypeCount),
WRAPM(Items, getSubtypeDef),
WRAPN(moveToGround, items_moveToGround), WRAPN(moveToGround, items_moveToGround),
WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToBuilding, items_moveToBuilding), WRAPN(moveToBuilding, items_moveToBuilding),
@ -937,6 +1293,22 @@ static const luaL_Reg dfhack_items_funcs[] = {
/***** Maps module *****/ /***** Maps module *****/
static bool hasTileAssignment(df::tile_bitmask *bm) {
return bm && bm->has_assignments();
}
static bool getTileAssignment(df::tile_bitmask *bm, int x, int y) {
return bm && bm->getassignment(x,y);
}
static void setTileAssignment(df::tile_bitmask *bm, int x, int y, bool val) {
CHECK_NULL_POINTER(bm);
bm->setassignment(x,y,val);
}
static void resetTileAssignment(df::tile_bitmask *bm, bool val) {
CHECK_NULL_POINTER(bm);
if (val) bm->set_all();
else bm->clear();
}
static const LuaWrapper::FunctionReg dfhack_maps_module[] = { static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock),
WRAPM(Maps, enableBlockUpdates), WRAPM(Maps, enableBlockUpdates),
@ -944,6 +1316,10 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween), WRAPM(Maps, canWalkBetween),
WRAPM(Maps, spawnFlow), WRAPM(Maps, spawnFlow),
WRAPN(hasTileAssignment, hasTileAssignment),
WRAPN(getTileAssignment, getTileAssignment),
WRAPN(setTileAssignment, setTileAssignment),
WRAPN(resetTileAssignment, resetTileAssignment),
{ NULL, NULL } { NULL, NULL }
}; };
@ -968,6 +1344,25 @@ static int maps_ensureTileBlock(lua_State *L)
return 1; return 1;
} }
static int maps_getTileType(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
auto ptype = Maps::getTileType(pos);
if (ptype)
lua_pushinteger(L, *ptype);
else
lua_pushnil(L);
return 1;
}
static int maps_getTileFlags(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::getTileDesignation(pos));
Lua::PushDFObject(L, Maps::getTileOccupancy(pos));
return 2;
}
static int maps_getRegionBiome(lua_State *L) static int maps_getRegionBiome(lua_State *L)
{ {
auto pos = CheckCoordXY(L, 1, true); auto pos = CheckCoordXY(L, 1, true);
@ -985,6 +1380,8 @@ static const luaL_Reg dfhack_maps_funcs[] = {
{ "isValidTilePos", maps_isValidTilePos }, { "isValidTilePos", maps_isValidTilePos },
{ "getTileBlock", maps_getTileBlock }, { "getTileBlock", maps_getTileBlock },
{ "ensureTileBlock", maps_ensureTileBlock }, { "ensureTileBlock", maps_ensureTileBlock },
{ "getTileType", maps_getTileType },
{ "getTileFlags", maps_getTileFlags },
{ "getRegionBiome", maps_getRegionBiome }, { "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn }, { "getTileBiomeRgn", maps_getTileBiomeRgn },
{ NULL, NULL } { NULL, NULL }
@ -1035,6 +1432,8 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) {
} }
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, getGeneralRef),
WRAPM(Buildings, getSpecificRef),
WRAPM(Buildings, setOwner), WRAPM(Buildings, setOwner),
WRAPM(Buildings, allocInstance), WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles), WRAPM(Buildings, checkFreeTiles),
@ -1146,6 +1545,7 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = {
WRAPM(Screen, inGraphicsMode), WRAPM(Screen, inGraphicsMode),
WRAPM(Screen, clear), WRAPM(Screen, clear),
WRAPM(Screen, invalidate), WRAPM(Screen, invalidate),
WRAPM(Screen, getKeyDisplay),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1168,7 +1568,7 @@ static int screen_getWindowSize(lua_State *L)
static int screen_paintTile(lua_State *L) static int screen_paintTile(lua_State *L)
{ {
Pen pen; Pen pen;
decode_pen(L, pen, 1); Lua::CheckPen(L, &pen, 1);
int x = luaL_checkint(L, 2); int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3); int y = luaL_checkint(L, 3);
if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) if (lua_gettop(L) >= 4 && !lua_isnil(L, 4))
@ -1189,44 +1589,14 @@ static int screen_readTile(lua_State *L)
int x = luaL_checkint(L, 1); int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2); int y = luaL_checkint(L, 2);
Pen pen = Screen::readTile(x, y); Pen pen = Screen::readTile(x, y);
Lua::Push(L, pen);
if (!pen.valid())
{
lua_pushnil(L);
}
else
{
lua_newtable(L);
lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch");
lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
if (pen.tile)
{
lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
switch (pen.tile_mode) {
case Pen::CharColor:
lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
break;
case Pen::TileColor:
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
break;
default:
break;
}
}
}
return 1; return 1;
} }
static int screen_paintString(lua_State *L) static int screen_paintString(lua_State *L)
{ {
Pen pen; Pen pen;
decode_pen(L, pen, 1); Lua::CheckPen(L, &pen, 1);
int x = luaL_checkint(L, 2); int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3); int y = luaL_checkint(L, 3);
const char *text = luaL_checkstring(L, 4); const char *text = luaL_checkstring(L, 4);
@ -1237,7 +1607,7 @@ static int screen_paintString(lua_State *L)
static int screen_fillRect(lua_State *L) static int screen_fillRect(lua_State *L)
{ {
Pen pen; Pen pen;
decode_pen(L, pen, 1); Lua::CheckPen(L, &pen, 1);
int x1 = luaL_checkint(L, 2); int x1 = luaL_checkint(L, 2);
int y1 = luaL_checkint(L, 3); int y1 = luaL_checkint(L, 3);
int x2 = luaL_checkint(L, 4); int x2 = luaL_checkint(L, 4);
@ -1355,9 +1725,11 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
return rv; return rv;
} }
static uint32_t getImageBase() { return Core::getInstance().p->getBase(); }
static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = { static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getImageBase),
WRAP(getRebaseDelta), WRAP(getRebaseDelta),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1411,6 +1783,18 @@ static int internal_getVTable(lua_State *L)
return 1; return 1;
} }
static int internal_adjustOffset(lua_State *L)
{
lua_settop(L, 2);
int off = luaL_checkint(L, 1);
int rv = Core::getInstance().p->adjustOffset(off, lua_toboolean(L, 2));
if (rv >= 0)
lua_pushinteger(L, rv);
else
lua_pushnil(L);
return 1;
}
static int internal_getMemRanges(lua_State *L) static int internal_getMemRanges(lua_State *L)
{ {
std::vector<DFHack::t_memrange> ranges; std::vector<DFHack::t_memrange> ranges;
@ -1454,6 +1838,81 @@ static int internal_patchMemory(lua_State *L)
return 1; return 1;
} }
static int internal_patchBytes(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 2);
MemoryPatcher patcher;
if (!lua_isnil(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L);
while (lua_next(L, 2))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in verify table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, false))
{
lua_pushnil(L);
lua_pushstring(L, "invalid verify address");
lua_pushvalue(L, -3);
return 3;
}
if (*addr != value)
{
lua_pushnil(L);
lua_pushstring(L, "wrong verify value");
lua_pushvalue(L, -3);
return 3;
}
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in write table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, true))
{
lua_pushnil(L);
lua_pushstring(L, "invalid write address");
lua_pushvalue(L, -3);
return 3;
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
uint8_t value = (uint8_t)lua_tounsigned(L, -1);
lua_pop(L, 1);
*addr = value;
}
lua_pushboolean(L, true);
return 1;
}
static int internal_memmove(lua_State *L) static int internal_memmove(lua_State *L)
{ {
void *dest = checkaddr(L, 1); void *dest = checkaddr(L, 1);
@ -1543,8 +2002,10 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress }, { "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress }, { "setAddress", internal_setAddress },
{ "getVTable", internal_getVTable }, { "getVTable", internal_getVTable },
{ "adjustOffset", internal_adjustOffset },
{ "getMemRanges", internal_getMemRanges }, { "getMemRanges", internal_getMemRanges },
{ "patchMemory", internal_patchMemory }, { "patchMemory", internal_patchMemory },
{ "patchBytes", internal_patchBytes },
{ "memmove", internal_memmove }, { "memmove", internal_memmove },
{ "memcmp", internal_memcmp }, { "memcmp", internal_memcmp },
{ "memscan", internal_memscan }, { "memscan", internal_memscan },
@ -1561,6 +2022,7 @@ void OpenDFHackApi(lua_State *state)
{ {
OpenPersistent(state); OpenPersistent(state);
OpenMatinfo(state); OpenMatinfo(state);
OpenPen(state);
LuaWrapper::SetFunctionWrappers(state, dfhack_module); LuaWrapper::SetFunctionWrappers(state, dfhack_module);
OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "gui", dfhack_gui_module);

@ -107,7 +107,8 @@ static void signal_typeid_error(color_ostream *out, lua_State *state,
type_identity *type, const char *msg, type_identity *type, const char *msg,
int val_index, bool perr, bool signal) int val_index, bool perr, bool signal)
{ {
std::string error = stl_sprintf(msg, type->getFullName().c_str()); std::string typestr = type ? type->getFullName() : "any pointer";
std::string error = stl_sprintf(msg, typestr.c_str());
if (signal) if (signal)
{ {
@ -134,6 +135,8 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_
if (lua_isnil(state, val_index)) if (lua_isnil(state, val_index))
return NULL; return NULL;
if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false); void *rv = get_object_internal(state, type, val_index, exact_type, false);
@ -1548,6 +1551,10 @@ void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
void OpenDFHackApi(lua_State *state); void OpenDFHackApi(lua_State *state);
namespace DFHack { namespace Lua { namespace Core {
static void InitCoreContext();
}}}
lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
{ {
if (!state) if (!state)
@ -1651,6 +1658,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_dup(state); lua_dup(state);
lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
// Init core-context specific stuff before loading dfhack.lua
if (IsCoreContext(state))
Lua::Core::InitCoreContext();
// load dfhack.lua // load dfhack.lua
Require(out, state, "dfhack"); Require(out, state, "dfhack");
@ -1826,8 +1837,12 @@ void DFHack::Lua::Core::Init(color_ostream &out)
State = luaL_newstate(); State = luaL_newstate();
// Calls InitCoreContext after checking IsCoreContext
Lua::Open(out, State); Lua::Open(out, State);
}
static void Lua::Core::InitCoreContext()
{
lua_newtable(State); lua_newtable(State);
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);

@ -1265,6 +1265,51 @@ static void BuildTypeMetatable(lua_State *state, type_identity *type)
* Recursive walk of scopes to construct the df... tree. * Recursive walk of scopes to construct the df... tree.
*/ */
static int wtype_pnext(lua_State *L)
{
lua_settop(L, 2); /* create a 2nd argument if there isn't one */
if (lua_next(L, lua_upvalueindex(1)))
return 2;
lua_pushnil(L);
return 1;
}
static int wtype_pairs(lua_State *state)
{
lua_pushvalue(state, lua_upvalueindex(1));
lua_pushcclosure(state, wtype_pnext, 1);
lua_pushnil(state);
lua_pushnil(state);
return 3;
}
static int wtype_inext(lua_State *L)
{
int i = luaL_checkint(L, 2);
i++; /* next value */
if (i <= lua_tointeger(L, lua_upvalueindex(2)))
{
lua_pushinteger(L, i);
lua_rawgeti(L, lua_upvalueindex(1), i);
return 2;
}
else
{
lua_pushnil(L);
return 1;
}
}
static int wtype_ipairs(lua_State *state)
{
lua_pushvalue(state, lua_upvalueindex(1));
lua_pushvalue(state, lua_upvalueindex(3));
lua_pushcclosure(state, wtype_inext, 2);
lua_pushnil(state);
lua_pushvalue(state, lua_upvalueindex(2));
return 3;
}
static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children); static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children);
void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name) void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name)
@ -1278,7 +1323,7 @@ void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *n
lua_rawset(state, table); lua_rawset(state, table);
} }
static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identity *eid)
{ {
const char *const *keys = eid->getKeys(); const char *const *keys = eid->getKeys();
@ -1296,11 +1341,17 @@ static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid)
if (eid->getFirstItem() <= eid->getLastItem()) if (eid->getFirstItem() <= eid->getLastItem())
{ {
lua_pushvalue(state, base+1);
lua_pushinteger(state, eid->getFirstItem()-1);
lua_pushinteger(state, eid->getLastItem());
lua_pushcclosure(state, wtype_ipairs, 3);
lua_setfield(state, ix_meta, "__ipairs");
lua_pushinteger(state, eid->getFirstItem()); lua_pushinteger(state, eid->getFirstItem());
lua_setfield(state, base+1, "_first_item"); lua_setfield(state, ftable, "_first_item");
lua_pushinteger(state, eid->getLastItem()); lua_pushinteger(state, eid->getLastItem());
lua_setfield(state, base+1, "_last_item"); lua_setfield(state, ftable, "_last_item");
} }
SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN);
@ -1321,7 +1372,7 @@ static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid)
lua_setmetatable(state, ftable); lua_setmetatable(state, ftable);
} }
static void FillBitfieldKeys(lua_State *state, int ftable, bitfield_identity *eid) static void FillBitfieldKeys(lua_State *state, int ix_meta, int ftable, bitfield_identity *eid)
{ {
// Create a new table attached to ftable as __index // Create a new table attached to ftable as __index
lua_newtable(state); lua_newtable(state);
@ -1338,11 +1389,17 @@ static void FillBitfieldKeys(lua_State *state, int ftable, bitfield_identity *ei
i += bits[i].size-1; i += bits[i].size-1;
} }
lua_pushvalue(state, base+1);
lua_pushinteger(state, -1);
lua_pushinteger(state, eid->getNumBits()-1);
lua_pushcclosure(state, wtype_ipairs, 3);
lua_setfield(state, ix_meta, "__ipairs");
lua_pushinteger(state, 0); lua_pushinteger(state, 0);
lua_setfield(state, base+1, "_first_item"); lua_setfield(state, ftable, "_first_item");
lua_pushinteger(state, eid->getNumBits()-1); lua_pushinteger(state, eid->getNumBits()-1);
lua_setfield(state, base+1, "_last_item"); lua_setfield(state, ftable, "_last_item");
SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN);
@ -1355,7 +1412,12 @@ static void RenderType(lua_State *state, compound_identity *node)
assert(node->getName()); assert(node->getName());
std::string name = node->getFullName(); std::string name = node->getFullName();
int base = lua_gettop(state); // Frame:
// base+1 - outer table
// base+2 - metatable of outer table
// base+3 - inner table
// base+4 - pairs table
Lua::StackUnwinder base(state);
lua_newtable(state); lua_newtable(state);
if (!lua_checkstack(state, 20)) if (!lua_checkstack(state, 20))
@ -1365,51 +1427,59 @@ static void RenderType(lua_State *state, compound_identity *node)
// metatable // metatable
lua_newtable(state); lua_newtable(state);
int ix_meta = base+2;
lua_dup(state); lua_dup(state);
lua_setmetatable(state, base+1); lua_setmetatable(state, base+1);
lua_pushstring(state, name.c_str()); lua_pushstring(state, name.c_str());
lua_setfield(state, base+2, "__metatable"); lua_setfield(state, ix_meta, "__metatable");
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME);
lua_setfield(state, base+2, "__tostring"); lua_setfield(state, ix_meta, "__tostring");
lua_pushlightuserdata(state, node); lua_pushlightuserdata(state, node);
lua_rawsetp(state, base+2, &DFHACK_IDENTITY_FIELD_TOKEN); lua_rawsetp(state, ix_meta, &DFHACK_IDENTITY_FIELD_TOKEN);
// inner table // inner table
lua_newtable(state); lua_newtable(state);
int ftable = base+3;
lua_dup(state); lua_dup(state);
lua_setfield(state, base+2, "__index"); lua_setfield(state, ix_meta, "__index");
int ftable = base+3; // pairs table
lua_newtable(state);
int ptable = base+4;
lua_pushvalue(state, ptable);
lua_pushcclosure(state, wtype_pairs, 1);
lua_setfield(state, ix_meta, "__pairs");
switch (node->type()) switch (node->type())
{ {
case IDTYPE_STRUCT: case IDTYPE_STRUCT:
lua_pushstring(state, "struct-type"); lua_pushstring(state, "struct-type");
lua_setfield(state, ftable, "_kind"); lua_setfield(state, ftable, "_kind");
IndexStatics(state, base+2, base+3, (struct_identity*)node); IndexStatics(state, ix_meta, ftable, (struct_identity*)node);
break; break;
case IDTYPE_CLASS: case IDTYPE_CLASS:
lua_pushstring(state, "class-type"); lua_pushstring(state, "class-type");
lua_setfield(state, ftable, "_kind"); lua_setfield(state, ftable, "_kind");
IndexStatics(state, base+2, base+3, (struct_identity*)node); IndexStatics(state, ix_meta, ftable, (struct_identity*)node);
break; break;
case IDTYPE_ENUM: case IDTYPE_ENUM:
lua_pushstring(state, "enum-type"); lua_pushstring(state, "enum-type");
lua_setfield(state, ftable, "_kind"); lua_setfield(state, ftable, "_kind");
FillEnumKeys(state, ftable, (enum_identity*)node); FillEnumKeys(state, ix_meta, ftable, (enum_identity*)node);
break; break;
case IDTYPE_BITFIELD: case IDTYPE_BITFIELD:
lua_pushstring(state, "bitfield-type"); lua_pushstring(state, "bitfield-type");
lua_setfield(state, ftable, "_kind"); lua_setfield(state, ftable, "_kind");
FillBitfieldKeys(state, ftable, (bitfield_identity*)node); FillBitfieldKeys(state, ix_meta, ftable, (bitfield_identity*)node);
break; break;
case IDTYPE_GLOBAL: case IDTYPE_GLOBAL:
@ -1425,14 +1495,14 @@ static void RenderType(lua_State *state, compound_identity *node)
BuildTypeMetatable(state, node); BuildTypeMetatable(state, node);
lua_dup(state); lua_dup(state);
lua_setmetatable(state, base+3); lua_setmetatable(state, ftable);
lua_getfield(state, -1, "__newindex"); lua_getfield(state, -1, "__newindex");
lua_setfield(state, base+2, "__newindex"); lua_setfield(state, ix_meta, "__newindex");
lua_getfield(state, -1, "__pairs"); lua_getfield(state, -1, "__pairs");
lua_setfield(state, base+2, "__pairs"); lua_setfield(state, ix_meta, "__pairs");
lua_pop(state, 3); base += 1;
return; return;
} }
@ -1454,17 +1524,25 @@ static void RenderType(lua_State *state, compound_identity *node)
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_IS_INSTANCE_NAME); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_IS_INSTANCE_NAME);
lua_setfield(state, ftable, "is_instance"); lua_setfield(state, ftable, "is_instance");
lua_pop(state, 2); base += 1;
} }
static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children) static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children)
{ {
// fieldtable pairstable |
int base = lua_gettop(state);
for (size_t i = 0; i < children.size(); i++) for (size_t i = 0; i < children.size(); i++)
{ {
RenderType(state, children[i]); RenderType(state, children[i]);
lua_pushstring(state, children[i]->getName()); lua_pushstring(state, children[i]->getName());
lua_swap(state); lua_swap(state);
lua_rawset(state, -3);
// save in both tables
lua_pushvalue(state, -2);
lua_pushvalue(state, -2);
lua_rawset(state, base);
lua_rawset(state, base-1);
} }
} }
@ -1524,10 +1602,13 @@ static int DoAttach(lua_State *state)
{ {
// Assign df a metatable with read-only contents // Assign df a metatable with read-only contents
lua_newtable(state); lua_newtable(state);
lua_newtable(state);
// Render the type structure // Render the type structure
RenderTypeChildren(state, compound_identity::getTopScope()); RenderTypeChildren(state, compound_identity::getTopScope());
lua_swap(state); // -> pairstable fieldtable
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME);
lua_setfield(state, -2, "sizeof"); lua_setfield(state, -2, "sizeof");
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME);
@ -1558,7 +1639,15 @@ static int DoAttach(lua_State *state)
lua_pushcfunction(state, meta_isnull); lua_pushcfunction(state, meta_isnull);
lua_setfield(state, -2, "isnull"); lua_setfield(state, -2, "isnull");
freeze_table(state, false, "df"); freeze_table(state, true, "df");
// pairstable dftable dfmeta
lua_pushvalue(state, -3);
lua_pushcclosure(state, wtype_pairs, 1);
lua_setfield(state, -2, "__pairs");
lua_pop(state, 1);
lua_remove(state, -2);
} }
return 1; return 1;

@ -9,14 +9,14 @@
NSAutoreleasePool *thePool; NSAutoreleasePool *thePool;
int create_pool() { int create_pool() {
fprintf(stderr,"Creating autorelease pool\n"); fprintf(stderr,"Creating autorelease pool\n");
thePool = [[NSAutoreleasePool alloc] init]; thePool = [[NSAutoreleasePool alloc] init];
return 1; return 1;
} }
int destroy_pool() { int destroy_pool() {
fprintf(stderr,"Draining and releasing autorelease pool\n"); fprintf(stderr,"Draining and releasing autorelease pool\n");
[thePool drain]; [thePool drain];
[thePool release]; [thePool release];
return 0; return 0;
} }

@ -148,6 +148,11 @@ bool prefix_matches(const std::string &prefix, const std::string &key, std::stri
return false; return false;
} }
int random_int(int max)
{
return int(int64_t(rand())*max/(int64_t(RAND_MAX)+1));
}
#ifdef LINUX_BUILD // Linux #ifdef LINUX_BUILD // Linux
uint64_t GetTimeMs64() uint64_t GetTimeMs64()
{ {

@ -22,6 +22,7 @@ must not be misrepresented as being the original software.
distribution. distribution.
*/ */
#include "modules/EventManager.h"
#include "Internal.h" #include "Internal.h"
#include "Core.h" #include "Core.h"
#include "MemAccess.h" #include "MemAccess.h"
@ -211,9 +212,10 @@ bool Plugin::load(color_ostream &con)
} }
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_name =(const char ** ) LookupPlugin(plug, "name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); const char ** plug_version =(const char ** ) LookupPlugin(plug, "version");
if(!plug_name || !plug_version) Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
if(!plug_name || !plug_version || !plug_self)
{ {
con.printerr("Plugin %s has no name or version.\n", filename.c_str()); con.printerr("Plugin %s has no name, version or self pointer.\n", filename.c_str());
ClosePlugin(plug); ClosePlugin(plug);
RefAutolock lock(access); RefAutolock lock(access);
state = PS_BROKEN; state = PS_BROKEN;
@ -228,6 +230,7 @@ bool Plugin::load(color_ostream &con)
state = PS_BROKEN; state = PS_BROKEN;
return false; return false;
} }
*plug_self = this;
RefAutolock lock(access); RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init"); plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init) if(!plugin_init)
@ -270,6 +273,7 @@ bool Plugin::unload(color_ostream &con)
// if we are actually loaded // if we are actually loaded
if(state == PS_LOADED) if(state == PS_LOADED)
{ {
EventManager::unregisterAll(this);
// notify the plugin about an attempt to shutdown // notify the plugin about an attempt to shutdown
if (plugin_onstatechange && if (plugin_onstatechange &&
plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND)
@ -598,6 +602,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core)
{
cmdlist_mutex = new mutex();
eval_ruby = NULL;
}
PluginManager::~PluginManager()
{
for(size_t i = 0; i < all_plugins.size();i++)
{
delete all_plugins[i];
}
all_plugins.clear();
delete cmdlist_mutex;
}
void PluginManager::init(Core * core)
{ {
#ifdef LINUX_BUILD #ifdef LINUX_BUILD
string path = core->getHackPath() + "plugins/"; string path = core->getHackPath() + "plugins/";
@ -606,8 +626,6 @@ PluginManager::PluginManager(Core * core)
string path = core->getHackPath() + "plugins\\"; string path = core->getHackPath() + "plugins\\";
const string searchstr = ".plug.dll"; const string searchstr = ".plug.dll";
#endif #endif
cmdlist_mutex = new mutex();
eval_ruby = NULL;
vector <string> filez; vector <string> filez;
getdir(path, filez); getdir(path, filez);
for(size_t i = 0; i < filez.size();i++) for(size_t i = 0; i < filez.size();i++)
@ -622,16 +640,6 @@ PluginManager::PluginManager(Core * core)
} }
} }
PluginManager::~PluginManager()
{
for(size_t i = 0; i < all_plugins.size();i++)
{
delete all_plugins[i];
}
all_plugins.clear();
delete cmdlist_mutex;
}
Plugin *PluginManager::getPluginByName (const std::string & name) Plugin *PluginManager::getPluginByName (const std::string & name)
{ {
for(size_t i = 0; i < all_plugins.size(); i++) for(size_t i = 0; i < all_plugins.size(); i++)

@ -53,10 +53,10 @@ Process::Process(VersionInfoFactory * known_versions)
char path[1024]; char path[1024];
char *real_path; char *real_path;
uint32_t size = sizeof(path); uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) { if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
identified = false; identified = false;
my_descriptor = 0; my_descriptor = 0;
@ -166,29 +166,29 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
(vm_region_info_t)&info, &info_count, &object); (vm_region_info_t)&info, &info_count, &object);
if (kr == KERN_SUCCESS) { if (kr == KERN_SUCCESS) {
if (info.reserved==1) { if (info.reserved==1) {
address += vmsize; address += vmsize;
continue; continue;
} }
Dl_info dlinfo; Dl_info dlinfo;
int dlcheck; int dlcheck;
dlcheck = dladdr((const void*)address, &dlinfo); dlcheck = dladdr((const void*)address, &dlinfo);
if (dlcheck==0) { if (dlcheck==0) {
dlinfo.dli_fname = ""; dlinfo.dli_fname = "";
} }
t_memrange temp; t_memrange temp;
strncpy( temp.name, dlinfo.dli_fname, 1023 ); strncpy( temp.name, dlinfo.dli_fname, 1023 );
temp.name[1023] = 0; temp.name[1023] = 0;
temp.start = (void *) address; temp.start = (void *) address;
temp.end = (void *) (address+vmsize); temp.end = (void *) (address+vmsize);
temp.read = (info.protection & VM_PROT_READ); temp.read = (info.protection & VM_PROT_READ);
temp.write = (info.protection & VM_PROT_WRITE); temp.write = (info.protection & VM_PROT_WRITE);
temp.execute = (info.protection & VM_PROT_EXECUTE); temp.execute = (info.protection & VM_PROT_EXECUTE);
temp.shared = info.shared; temp.shared = info.shared;
temp.valid = true; temp.valid = true;
ranges.push_back(temp); ranges.push_back(temp);
fprintf(stderr, fprintf(stderr,
"%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", "%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n",
address, (address + vmsize), (vmsize >> 10), address, (address + vmsize), (vmsize >> 10),
(info.protection & VM_PROT_READ) ? 'r' : '-', (info.protection & VM_PROT_READ) ? 'r' : '-',
@ -220,9 +220,14 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
}*/ }*/
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
return 0; return 0x1000000;
}
int Process::adjustOffset(int offset, bool /*to_file*/)
{
return offset;
} }
static int getdir (string dir, vector<string> &files) static int getdir (string dir, vector<string> &files)
@ -272,15 +277,15 @@ uint32_t Process::getTickCount()
string Process::getPath() string Process::getPath()
{ {
char path[1024]; char path[1024];
char *real_path; char *real_path;
uint32_t size = sizeof(path); uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) { if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
std::string path_string(real_path); std::string path_string(real_path);
int last_slash = path_string.find_last_of("/"); int last_slash = path_string.find_last_of("/");
std::string directory = path_string.substr(0,last_slash); std::string directory = path_string.substr(0,last_slash);
return directory; return directory;
} }
@ -300,3 +305,28 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
}
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -155,9 +155,14 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
fclose(mapFile); fclose(mapFile);
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
return 0; return 0x8048000;
}
int Process::adjustOffset(int offset, bool /*to_file*/)
{
return offset;
} }
static int getdir (string dir, vector<string> &files) static int getdir (string dir, vector<string> &files)
@ -231,3 +236,28 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
}
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -160,7 +160,7 @@ Process::Process(VersionInfoFactory * factory)
identified = true; identified = true;
// give the process a data model and memory layout fixed for the base of first module // give the process a data model and memory layout fixed for the base of first module
my_descriptor = new VersionInfo(*vinfo); my_descriptor = new VersionInfo(*vinfo);
my_descriptor->rebaseTo((uint32_t)d->base); my_descriptor->rebaseTo(getBase());
for(size_t i = 0; i < threads_ids.size();i++) for(size_t i = 0; i < threads_ids.size();i++)
{ {
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]); HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]);
@ -394,13 +394,46 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
} }
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
if(d) if(d)
return (uint32_t) d->base; return (uintptr_t) d->base;
return 0x400000; return 0x400000;
} }
int Process::adjustOffset(int offset, bool to_file)
{
if (!d)
return -1;
for(int i = 0; i < d->pe_header.FileHeader.NumberOfSections; i++)
{
auto &section = d->sections[i];
if (to_file)
{
unsigned delta = offset - section.VirtualAddress;
if (delta >= section.Misc.VirtualSize)
continue;
if (!section.PointerToRawData || delta >= section.SizeOfRawData)
return -1;
return (int)(section.PointerToRawData + delta);
}
else
{
unsigned delta = offset - section.PointerToRawData;
if (!section.PointerToRawData || delta >= section.SizeOfRawData)
continue;
if (delta >= section.Misc.VirtualSize)
return -1;
return (int)(section.VirtualAddress + delta);
}
}
return -1;
}
string Process::doReadClassName (void * vptr) string Process::doReadClassName (void * vptr)
{ {
char * rtti = readPtr((char *)vptr - 0x4); char * rtti = readPtr((char *)vptr - 0x4);
@ -440,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result; return result;
} }
void* Process::memAlloc(const int length)
{
void *ret;
// returns 0 on error
ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if (!ret)
ret = (void*)-1;
return ret;
}
int Process::memDealloc(void *ptr, const int length)
{
// can only free the whole region at once
// vfree returns 0 on error
return !VirtualFree(ptr, 0, MEM_RELEASE);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
DWORD old_prot = 0;
// only support a few constant combinations
if (prot == 0)
prot_native = PAGE_NOACCESS;
else if (prot == Process::MemProt::READ)
prot_native = PAGE_READONLY;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE))
prot_native = PAGE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READ;
else
return -1;
return !VirtualProtect(ptr, length, prot_native, &old_prot);
}

@ -292,9 +292,9 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit,
if (!unit->custom_profession.empty()) if (!unit->custom_profession.empty())
info->set_custom_profession(unit->custom_profession); info->set_custom_profession(unit->custom_profession);
if (unit->military.squad_index >= 0) if (unit->military.squad_id >= 0)
{ {
info->set_squad_id(unit->military.squad_index); info->set_squad_id(unit->military.squad_id);
info->set_squad_position(unit->military.squad_position); info->set_squad_position(unit->military.squad_position);
} }
} }

@ -100,6 +100,24 @@ bool DFHack::removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type
return false; return false;
} }
df::item *DFHack::findItemRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getItem() : NULL;
}
df::building *DFHack::findBuildingRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getBuilding() : NULL;
}
df::unit *DFHack::findUnitRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getUnit() : NULL;
}
df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type) df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type)
{ {
for (int i = vec.size()-1; i >= 0; i--) for (int i = vec.size()-1; i >= 0; i--)

@ -39,15 +39,54 @@ using namespace DFHack;
/* /*
* Code for accessing method pointers directly. Very compiler-specific. * Code for accessing method pointers directly. Very compiler-specific.
*
* Pointers to methods in C++ are conceptually similar to pointers to
* functions, but with some complications. Specifically, the target of
* such pointer can be either:
*
* - An ordinary non-virtual method, in which case the pointer behaves
* not much differently from a simple function pointer.
* - A virtual method, in which case calling the pointer must emulate
* an ordinary call to that method, i.e. fetch the real code address
* from the vtable at the appropriate index.
*
* This means that pointers to virtual methods actually have to encode
* the relevant vtable index value in some way. Also, since these two
* types of pointers cannot be distinguished by data type and differ
* only in value, any sane compiler would ensure that any non-virtual
* method that can potentially be called via a pointer uses the same
* parameter passing rules as an equivalent virtual method, so that
* the same parameter passing code would work with both types of pointer.
*
* This means that with a few small low-level compiler-specific wrappers
* to access the data inside such pointers it is possible to:
*
* - Convert a non-virtual method pointer into a code address that
* can be directly put into a vtable.
* - Convert a pointer taken out of a vtable into a fake non-virtual
* method pointer that can be used to easily call the original
* vmethod body.
* - Extract a vtable index out of a virtual method pointer.
*
* Taken together, these features allow delegating all the difficult
* and fragile tasks like passing parameters and calculating the
* vtable index to the C++ compiler.
*/ */
#if defined(_MSC_VER) #if defined(_MSC_VER)
// MSVC may use up to 3 different representations
// based on context, but adding the /vmg /vmm options
// forces it to stick to this one. It can accomodate
// multiple, but not virtual inheritance.
struct MSVC_MPTR { struct MSVC_MPTR {
void *method; void *method;
intptr_t this_shift; intptr_t this_shift;
}; };
// Debug builds sometimes use additional thunks that
// just jump to the real one, presumably to attach some
// additional debug info.
static uint32_t *follow_jmp(void *ptr) static uint32_t *follow_jmp(void *ptr)
{ {
uint8_t *p = (uint8_t*)ptr; uint8_t *p = (uint8_t*)ptr;
@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr)
{ {
switch (*p) switch (*p)
{ {
case 0xE9: case 0xE9: // jmp near rel32
p += 5 + *(int32_t*)(p+1); p += 5 + *(int32_t*)(p+1);
break; break;
case 0xEB: case 0xEB: // jmp short rel8
p += 2 + *(int8_t*)(p+1); p += 2 + *(int8_t*)(p+1);
break; break;
default: default:
@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
#elif defined(__GXX_ABI_VERSION) #elif defined(__GXX_ABI_VERSION)
// GCC seems to always use this structure - possibly unless
// virtual inheritance is involved, but that's irrelevant.
struct GCC_MPTR { struct GCC_MPTR {
intptr_t method; intptr_t method; // Code pointer or tagged vtable offset
intptr_t this_shift; intptr_t this_shift;
}; };
@ -166,12 +207,12 @@ void *virtual_identity::get_vmethod_ptr(int idx)
return vtable[idx]; return vtable[idx];
} }
bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr)
{ {
assert(idx >= 0); assert(idx >= 0);
void **vtable = (void**)vtable_ptr; void **vtable = (void**)vtable_ptr;
if (!vtable) return NULL; if (!vtable) return NULL;
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); return patcher.write(&vtable[idx], &ptr, sizeof(void*));
} }
/* /*
@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v
{ {
if (vmethod_idx < 0 || interpose_method == NULL) if (vmethod_idx < 0 || interpose_method == NULL)
{ {
/*
* A failure here almost certainly means a problem in one
* of the pointer-to-method access wrappers above:
*
* - vmethod_idx comes from vmethod_pointer_to_idx_
* - interpose_method comes from method_pointer_to_addr_
*/
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n", fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
vmethod_idx, unsigned(interpose_method)); vmethod_idx, unsigned(interpose_method));
fflush(stderr); fflush(stderr);
@ -281,9 +330,10 @@ VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_
return item; return item;
} }
void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) bool VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr)
{ {
auto &children = cur->getChildren(); auto &children = cur->getChildren();
bool found = false;
for (size_t i = 0; i < children.size(); i++) for (size_t i = 0; i < children.size(); i++)
{ {
@ -298,17 +348,32 @@ void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmp
continue; continue;
child_next.insert(base); child_next.insert(base);
found = true;
} }
else else if (child->vtable_ptr)
{ {
void *cptr = child->get_vmethod_ptr(vmethod_idx); void *cptr = child->get_vmethod_ptr(vmethod_idx);
if (cptr != vmptr) if (cptr != vmptr)
continue; continue;
child_hosts.insert(child); child_hosts.insert(child);
found = true;
find_child_hosts(child, vmptr); find_child_hosts(child, vmptr);
} }
else
{
// If this vtable is not known, but any of the children
// have the same vmethod, this one definitely does too
if (find_child_hosts(child, vmptr))
{
child_hosts.insert(child);
found = true;
}
}
} }
return found;
} }
void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
@ -328,7 +393,9 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
auto last = this; auto last = this;
while (last->prev) last = last->prev; while (last->prev) last = last->prev;
from->set_vmethod_ptr(vmethod_idx, last->saved_chain); MemoryPatcher patcher;
from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain);
// Unlink the chains // Unlink the chains
child_hosts.erase(from); child_hosts.erase(from);
@ -363,13 +430,15 @@ bool VMethodInterposeLinkBase::apply(bool enable)
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr // Apply the new method ptr
MemoryPatcher patcher;
set_chain(old_ptr); set_chain(old_ptr);
if (next_link) if (next_link)
{ {
next_link->set_chain(interpose_method); next_link->set_chain(interpose_method);
} }
else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method))
{ {
set_chain(NULL); set_chain(NULL);
return false; return false;
@ -443,7 +512,7 @@ bool VMethodInterposeLinkBase::apply(bool enable)
{ {
auto nhost = *it; auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == old_link); assert(nhost->interpose_list[vmethod_idx] == old_link);
nhost->set_vmethod_ptr(vmethod_idx, interpose_method); nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method);
nhost->interpose_list[vmethod_idx] = this; nhost->interpose_list[vmethod_idx] = this;
} }
@ -480,9 +549,11 @@ void VMethodInterposeLinkBase::remove()
} }
else else
{ {
MemoryPatcher patcher;
// Remove from the list in the identity and vtable // Remove from the list in the identity and vtable
host->interpose_list[vmethod_idx] = prev; host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain); host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
for (auto it = child_next.begin(); it != child_next.end(); ++it) for (auto it = child_next.begin(); it != child_next.end(); ++it)
{ {
@ -499,7 +570,7 @@ void VMethodInterposeLinkBase::remove()
auto nhost = *it; auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == this); assert(nhost->interpose_list[vmethod_idx] == this);
nhost->interpose_list[vmethod_idx] = prev; nhost->interpose_list[vmethod_idx] = prev;
nhost->set_vmethod_ptr(vmethod_idx, saved_chain); nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
if (prev) if (prev)
prev->child_hosts.insert(nhost); prev->child_hosts.insert(nhost);
} }

@ -103,13 +103,13 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
{ {
mem->setOS(OS_LINUX); mem->setOS(OS_LINUX);
// this is wrong... I'm not going to do base image relocation on linux though. // this is wrong... I'm not going to do base image relocation on linux though.
mem->setBase(0x0); mem->setBase(0x8048000);
} }
else if(os == "darwin") else if(os == "darwin")
{ {
mem->setOS(OS_APPLE); mem->setOS(OS_APPLE);
// this is wrong... I'm not going to do base image relocation on linux though. // this is wrong... I'm not going to do base image relocation on linux though.
mem->setBase(0x0); mem->setBase(0x1000000);
} }
else else
{ {

@ -0,0 +1,315 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
A thread-safe logging console with a line editor for windows.
Based on linenoise win32 port,
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
All rights reserved.
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
The original linenoise can be found at: http://github.com/antirez/linenoise
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Redis nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <iostream>
#include <fstream>
#include <istream>
#include <string>
#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <vector>
#include <memory>
#include <md5wrapper.h>
using std::cout;
using std::cerr;
using std::endl;
typedef unsigned char patch_byte;
struct BinaryPatch {
struct Byte {
unsigned offset;
patch_byte old_val, new_val;
};
enum State {
Conflict = 0,
Unapplied = 1,
Applied = 2,
Partial = 3
};
std::vector<Byte> entries;
bool loadDIF(std::string name);
State checkState(const patch_byte *ptr, size_t len);
void apply(patch_byte *ptr, size_t len, bool newv);
};
inline bool is_hex(char c)
{
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
bool BinaryPatch::loadDIF(std::string name)
{
entries.clear();
std::ifstream infile(name);
if(infile.bad())
{
cerr << "Cannot open file: " << name << endl;
return false;
}
std::string s;
while(std::getline(infile, s))
{
// Parse lines that begin with "[0-9a-f]+:"
size_t idx = s.find(':');
if (idx == std::string::npos || idx == 0 || idx > 8)
continue;
bool ok = true;
for (size_t i = 0; i < idx; i++)
if (!is_hex(s[i]))
ok = false;
if (!ok)
continue;
unsigned off, oval, nval;
int nchar = 0;
int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar);
if (cnt < 3)
{
cerr << "Could not parse: " << s << endl;
return false;
}
for (size_t i = nchar; i < s.size(); i++)
{
if (!isspace(s[i]))
{
cerr << "Garbage at end of line: " << s << endl;
return false;
}
}
if (oval >= 256 || nval >= 256)
{
cerr << "Invalid byte values: " << s << endl;
return false;
}
Byte bv = { off, patch_byte(oval), patch_byte(nval) };
entries.push_back(bv);
}
if (entries.empty())
{
cerr << "No lines recognized." << endl;
return false;
}
return true;
}
BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len)
{
int state = 0;
for (size_t i = 0; i < entries.size(); i++)
{
Byte &bv = entries[i];
if (bv.offset >= len)
{
cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl;
return Conflict;
}
patch_byte cv = ptr[bv.offset];
if (bv.old_val == cv)
state |= Unapplied;
else if (bv.new_val == cv)
state |= Applied;
else
{
cerr << std::hex << bv.offset << ": "
<< unsigned(bv.old_val) << " " << unsigned(bv.new_val)
<< ", but currently " << unsigned(cv) << std::dec << endl;
return Conflict;
}
}
return State(state);
}
void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv)
{
for (size_t i = 0; i < entries.size(); i++)
{
Byte &bv = entries[i];
assert (bv.offset < len);
ptr[bv.offset] = (newv ? bv.new_val : bv.old_val);
}
}
bool load_file(std::vector<patch_byte> *pvec, std::string fname)
{
FILE *f = fopen(fname.c_str(), "rb");
if (!f)
{
cerr << "Cannot open file: " << fname << endl;
return false;
}
fseek(f, 0, SEEK_END);
pvec->resize(ftell(f));
fseek(f, 0, SEEK_SET);
size_t cnt = fread(pvec->data(), 1, pvec->size(), f);
fclose(f);
return cnt == pvec->size();
}
bool save_file(const std::vector<patch_byte> &pvec, std::string fname)
{
FILE *f = fopen(fname.c_str(), "wb");
if (!f)
{
cerr << "Cannot open file: " << fname << endl;
return false;
}
size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f);
fclose(f);
return cnt == pvec.size();
}
std::string compute_hash(const std::vector<patch_byte> &pvec)
{
md5wrapper md5;
return md5.getHashFromBytes(pvec.data(), pvec.size());
}
int main (int argc, char *argv[])
{
if (argc <= 3)
{
cerr << "Usage: binpatch check|apply|remove <exe> <patch>" << endl;
return 2;
}
std::string cmd = argv[1];
if (cmd != "check" && cmd != "apply" && cmd != "remove")
{
cerr << "Invalid command: " << cmd << endl;
return 2;
}
std::string exe_file = argv[2];
std::vector<patch_byte> bindata;
if (!load_file(&bindata, exe_file))
return 2;
BinaryPatch patch;
if (!patch.loadDIF(argv[3]))
return 2;
BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size());
if (state == BinaryPatch::Conflict)
return 1;
if (cmd == "check")
{
switch (state)
{
case BinaryPatch::Unapplied:
cout << "Currently not applied." << endl;
break;
case BinaryPatch::Applied:
cout << "Currently applied." << endl;
break;
case BinaryPatch::Partial:
cout << "Currently partially applied." << endl;
break;
default:
break;
}
return 0;
}
else if (cmd == "apply")
{
if (state == BinaryPatch::Applied)
{
cout << "Already applied." << endl;
return 0;
}
patch.apply(bindata.data(), bindata.size(), true);
}
else if (cmd == "remove")
{
if (state == BinaryPatch::Unapplied)
{
cout << "Already removed." << endl;
return 0;
}
patch.apply(bindata.data(), bindata.size(), false);
}
if (!save_file(bindata, exe_file + ".bak"))
{
cerr << "Could not create backup." << endl;
return 1;
}
if (!save_file(bindata, exe_file))
return 1;
cout << "Patched " << patch.entries.size()
<< " bytes, new hash: " << compute_hash(bindata) << endl;
return 0;
}

@ -65,20 +65,20 @@ namespace DFHack
bool save (const char * filename) bool save (const char * filename)
{ {
std::ofstream outfile (filename); std::ofstream outfile (filename);
//fprintf(stderr,"Save: Initialized stream\n"); //fprintf(stderr,"Save: Initialized stream\n");
if(outfile.bad()) if(outfile.bad())
return false; return false;
//fprintf(stderr,"Save: Iterating...\n"); //fprintf(stderr,"Save: Iterating...\n");
for(auto iter = history.begin();iter < history.end(); iter++) for(auto iter = history.begin();iter < history.end(); iter++)
{ {
//fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str()); //fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str());
outfile << *iter << std::endl; outfile << *iter << std::endl;
//fprintf(stderr,"Save: Flushing\n"); //fprintf(stderr,"Save: Flushing\n");
outfile.flush(); outfile.flush();
} }
//fprintf(stderr,"Save: Closing\n"); //fprintf(stderr,"Save: Closing\n");
outfile.close(); outfile.close();
//fprintf(stderr,"Save: Done\n"); //fprintf(stderr,"Save: Done\n");
return true; return true;
} }
/// add a command to the history /// add a command to the history

@ -54,7 +54,6 @@ namespace DFHack
{ {
class Process; class Process;
class Module; class Module;
class World;
class Materials; class Materials;
class Notes; class Notes;
struct VersionInfo; struct VersionInfo;
@ -120,8 +119,6 @@ namespace DFHack
/// Is everything OK? /// Is everything OK?
bool isValid(void) { return !errorstate; } bool isValid(void) { return !errorstate; }
/// get the world module
World * getWorld();
/// get the materials module /// get the materials module
Materials * getMaterials(); Materials * getMaterials();
/// get the notes module /// get the notes module
@ -205,7 +202,6 @@ namespace DFHack
// Module storage // Module storage
struct struct
{ {
World * pWorld;
Materials * pMaterials; Materials * pMaterials;
Notes * pNotes; Notes * pNotes;
Graphic * pGraphic; Graphic * pGraphic;

@ -61,7 +61,6 @@ distribution.
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Vegetation.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Gui.h" #include "modules/Gui.h"

@ -35,7 +35,7 @@ distribution.
// Stop some MS stupidity // Stop some MS stupidity
#ifdef interface #ifdef interface
#undef interface #undef interface
#endif #endif
typedef struct lua_State lua_State; typedef struct lua_State lua_State;
@ -294,6 +294,7 @@ namespace DFHack
#endif #endif
class DFHACK_EXPORT VMethodInterposeLinkBase; class DFHACK_EXPORT VMethodInterposeLinkBase;
class MemoryPatcher;
class DFHACK_EXPORT virtual_identity : public struct_identity { class DFHACK_EXPORT virtual_identity : public struct_identity {
static std::map<void*, virtual_identity*> known; static std::map<void*, virtual_identity*> known;
@ -313,7 +314,7 @@ namespace DFHack
bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); }
void *get_vmethod_ptr(int index); void *get_vmethod_ptr(int index);
bool set_vmethod_ptr(int index, void *ptr); bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr);
public: public:
virtual_identity(size_t size, TAllocateFn alloc, virtual_identity(size_t size, TAllocateFn alloc,

@ -36,10 +36,14 @@ distribution.
namespace DFHack { namespace DFHack {
class function_identity_base; class function_identity_base;
struct MaterialInfo;
namespace Units { namespace Units {
struct NoblePosition; struct NoblePosition;
} }
namespace Screen {
struct Pen;
};
} }
namespace DFHack {namespace Lua { namespace DFHack {namespace Lua {
@ -283,6 +287,8 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT void Push(lua_State *state, df::coord obj); DFHACK_EXPORT void Push(lua_State *state, df::coord obj);
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj);
void Push(lua_State *state, const Units::NoblePosition &pos); void Push(lua_State *state, const Units::NoblePosition &pos);
DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info);
DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info);
template<class T> inline void Push(lua_State *state, T *ptr) { template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr); PushDFObject(state, ptr);
} }
@ -313,6 +319,8 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos);
DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true);
DFHACK_EXPORT bool IsCoreContext(lua_State *state); DFHACK_EXPORT bool IsCoreContext(lua_State *state);
namespace Event { namespace Event {
@ -472,3 +480,18 @@ namespace DFHack {namespace Lua {
name##_event.invoke(out, 5); \ name##_event.invoke(out, 5); \
} \ } \
} }
#define DEFINE_LUA_EVENT_6(name, handler, arg_type1, arg_type2, arg_type3, arg_type4, arg_type5,arg_type6) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4,arg_type5 arg5, arg_type6 arg6) { \
handler(out, arg1, arg2, arg3, arg4, arg5, arg6); \
if (auto state = name##_event.state_if_count()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
DFHack::Lua::Push(state, arg4); \
DFHack::Lua::Push(state, arg5); \
DFHack::Lua::Push(state, arg6); \
name##_event.invoke(out, 6); \
} \
}

@ -275,11 +275,13 @@ namespace DFHack
{ {
return my_descriptor; return my_descriptor;
}; };
uint32_t getBase(); uintptr_t getBase();
/// get the DF Process ID /// get the DF Process ID
int getPID(); int getPID();
/// get the DF Process FilePath /// get the DF Process FilePath
std::string getPath(); std::string getPath();
/// Adjust between in-memory and in-file image offset
int adjustOffset(int offset, bool to_file = false);
/// millisecond tick count, exactly as DF uses /// millisecond tick count, exactly as DF uses
uint32_t getTickCount(); uint32_t getTickCount();
@ -289,6 +291,27 @@ namespace DFHack
/// write a possibly read-only memory area /// write a possibly read-only memory area
bool patchMemory(void *target, const void* src, size_t count); bool patchMemory(void *target, const void* src, size_t count);
/// allocate new memory pages for code or stuff
/// returns -1 on error (0 is a valid address)
void* memAlloc(const int length);
/// free memory pages from memAlloc
/// should have length = alloced length for portability
/// returns 0 on success
int memDealloc(void *ptr, const int length);
/// change memory page permissions
/// prot is a bitwise OR of the MemProt enum
/// returns 0 on success
int memProtect(void *ptr, const int length, const int prot);
enum MemProt {
READ = 1,
WRITE = 2,
EXEC = 4
};
private: private:
VersionInfo * my_descriptor; VersionInfo * my_descriptor;
PlatformSpecific *d; PlatformSpecific *d;
@ -315,5 +338,22 @@ namespace DFHack
// Get list of names given to ClassNameCheck constructors. // Get list of names given to ClassNameCheck constructors.
static void getKnownClassNames(std::vector<std::string> &names); static void getKnownClassNames(std::vector<std::string> &names);
}; };
class DFHACK_EXPORT MemoryPatcher
{
Process *p;
std::vector<t_memrange> ranges, save;
public:
MemoryPatcher(Process *p = NULL);
~MemoryPatcher();
bool verifyAccess(void *target, size_t size, bool write = false);
bool makeWritable(void *target, size_t size) {
return verifyAccess(target, size, true);
}
bool write(void *target, const void *src, size_t size);
void close();
};
} }
#endif #endif

@ -331,6 +331,8 @@ inline T clip_range(T a, T1 minv, T2 maxv) {
return a; return a;
} }
DFHACK_EXPORT int random_int(int max);
/** /**
* Returns the amount of milliseconds elapsed since the UNIX epoch. * Returns the amount of milliseconds elapsed since the UNIX epoch.
* Works on both windows and linux. * Works on both windows and linux.

@ -33,7 +33,6 @@ namespace DFHack
Module* createGui(); Module* createGui();
Module* createWorld(); Module* createWorld();
Module* createMaterials(); Module* createMaterials();
Module* createVegetation();
Module* createNotes(); Module* createNotes();
Module* createGraphic(); Module* createGraphic();
} }

@ -205,6 +205,7 @@ namespace DFHack
friend class Plugin; friend class Plugin;
PluginManager(Core * core); PluginManager(Core * core);
~PluginManager(); ~PluginManager();
void init(Core* core);
void OnUpdate(color_ostream &out); void OnUpdate(color_ostream &out);
void OnStateChange(color_ostream &out, state_change_event event); void OnStateChange(color_ostream &out, state_change_event event);
void registerCommands( Plugin * p ); void registerCommands( Plugin * p );
@ -246,7 +247,8 @@ namespace DFHack
/// You have to have this in every plugin you write - just once. Ideally on top of the main file. /// You have to have this in every plugin you write - just once. Ideally on top of the main file.
#define DFHACK_PLUGIN(plugin_name) \ #define DFHACK_PLUGIN(plugin_name) \
DFhackDataExport const char * version = DFHACK_VERSION;\ DFhackDataExport const char * version = DFHACK_VERSION;\
DFhackDataExport const char * name = plugin_name; DFhackDataExport const char * name = plugin_name;\
DFhackDataExport Plugin *plugin_self = NULL;
#define DFHACK_PLUGIN_LUA_COMMANDS \ #define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =

@ -41,7 +41,7 @@ namespace DFHack
struct struct
{ {
//Maybe should add 'up' and 'down' for Z-levels? //Maybe should add 'up' and 'down' for Z-levels?
unsigned char north,south,west,east; unsigned char north,south,west,east;
}; };
inline TileDirection() inline TileDirection()
@ -203,6 +203,12 @@ namespace DFHack
return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype));
} }
inline
bool FlowPassableDown(df::tiletype tiletype)
{
return ENUM_ATTR(tiletype_shape, passable_flow_down, tileShape(tiletype));
}
inline inline
bool isWalkable(df::tiletype tiletype) bool isWalkable(df::tiletype tiletype)
{ {

@ -74,12 +74,38 @@ namespace DFHack
uint32_t xpNxtLvl; uint32_t xpNxtLvl;
}; };
typedef std::pair<df::coord2d, df::coord2d> rect2d;
inline rect2d intersect(rect2d a, rect2d b) {
df::coord2d g1 = a.first, g2 = a.second;
df::coord2d c1 = b.first, c2 = b.second;
df::coord2d rc1 = df::coord2d(std::max(g1.x, c1.x), std::max(g1.y, c1.y));
df::coord2d rc2 = df::coord2d(std::min(g2.x, c2.x), std::min(g2.y, c2.y));
return rect2d(rc1, rc2);
}
inline rect2d mkrect_xy(int x1, int y1, int x2, int y2) {
return rect2d(df::coord2d(x1, y1), df::coord2d(x2, y2));
}
inline rect2d mkrect_wh(int x, int y, int w, int h) {
return rect2d(df::coord2d(x, y), df::coord2d(x+w-1, y+h-1));
}
inline df::coord2d rect_size(const rect2d &rect) {
return rect.second - rect.first + df::coord2d(1,1);
}
DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files); DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files);
DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending); DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending);
DFHACK_EXPORT df::general_ref *findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type); DFHACK_EXPORT df::general_ref *findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT bool removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id); DFHACK_EXPORT bool removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id);
DFHACK_EXPORT df::item *findItemRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::building *findBuildingRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::unit *findUnitRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type); DFHACK_EXPORT df::specific_ref *findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type);
DFHACK_EXPORT bool removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr); DFHACK_EXPORT bool removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr);
}// namespace DFHack }// namespace DFHack

@ -28,6 +28,58 @@ distribution.
namespace DFHack namespace DFHack
{ {
/* VMethod interpose API.
This API allows replacing an entry in the original vtable
with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are
automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage:
struct my_hack : df::someclass {
typedef df::someclass interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
...
INTERPOSE_NEXT(foo)(arg) // call the original
...
}
};
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply())
error();
}
void shutdown() {
INTERPOSE_HOOK(my_hack, foo).remove();
}
Important caveat:
This will NOT intercept calls to the superclass vmethod
from overriding vmethod bodies in subclasses, i.e. whenever
DF code contains something like this, the call to "superclass::foo()"
doesn't actually use vtables, and thus will never trigger any hooks:
class superclass { virtual foo() { ... } };
class subclass : superclass { virtual foo() { ... superclass::foo(); ... } };
The only workaround is to implement and apply a second hook for subclass::foo,
and repeat that for any other subclasses and sub-subclasses that override this
vmethod.
*/
template<bool> struct StaticAssert; template<bool> struct StaticAssert;
template<> struct StaticAssert<true> {}; template<> struct StaticAssert<true> {};
@ -81,43 +133,6 @@ namespace DFHack
return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx)); return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx));
} }
/* VMethod interpose API.
This API allows replacing an entry in the original vtable
with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are
automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage:
struct my_hack : df::someclass {
typedef df::someclass interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
...
INTERPOSE_NEXT(foo)(arg) // call the original
...
}
};
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply())
error();
}
void shutdown() {
INTERPOSE_HOOK(my_hack, foo).remove();
}
*/
#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \ #define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
typedef rtype (interpose_base::*interpose_ptr_##name)args; \ typedef rtype (interpose_base::*interpose_ptr_##name)args; \
@ -142,24 +157,27 @@ namespace DFHack
friend class virtual_identity; friend class virtual_identity;
virtual_identity *host; // Class with the vtable virtual_identity *host; // Class with the vtable
int vmethod_idx; int vmethod_idx; // Index of the interposed method in the vtable
void *interpose_method; // Pointer to the code of the interposing method void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below void *chain_mptr; // Pointer to the chain field in the subclass below
int priority; int priority; // Higher priority hooks are called earlier
bool applied; bool applied; // True if this hook is currently applied
void *saved_chain; // Previous pointer to the code void *saved_chain; // Pointer to the code of the original vmethod or next hook
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
// inherited vtable members // Chain of hooks within the same host
VMethodInterposeLinkBase *next, *prev;
// Subclasses that inherit this topmost hook directly
std::set<virtual_identity*> child_hosts; std::set<virtual_identity*> child_hosts;
// Hooks within subclasses that branch off this topmost hook
std::set<VMethodInterposeLinkBase*> child_next; std::set<VMethodInterposeLinkBase*> child_next;
// (See the cpp file for a more detailed description of these links)
void set_chain(void *chain); void set_chain(void *chain);
void on_host_delete(virtual_identity *host); void on_host_delete(virtual_identity *host);
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
void find_child_hosts(virtual_identity *cur, void *vmptr); bool find_child_hosts(virtual_identity *cur, void *vmptr);
public: public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority); VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority);
~VMethodInterposeLinkBase(); ~VMethodInterposeLinkBase();
@ -172,6 +190,9 @@ namespace DFHack
template<class Base, class Ptr> template<class Base, class Ptr>
class VMethodInterposeLink : public VMethodInterposeLinkBase { class VMethodInterposeLink : public VMethodInterposeLinkBase {
public: public:
// Exactly the same as the saved_chain field of superclass,
// but converted to the appropriate pointer-to-method type.
// Kept up to date via the chain_mptr pointer.
Ptr chain; Ptr chain;
operator Ptr () { return chain; } operator Ptr () { return chain; }

@ -27,7 +27,7 @@ distribution.
#include "Pragma.h" #include "Pragma.h"
#include "Export.h" #include "Export.h"
#include "Types.h" /* #include "Types.h" */
#include <map> #include <map>
#include <sys/types.h> #include <sys/types.h>
#include <vector> #include <vector>

@ -1,5 +1,12 @@
bool empty() const { return x.empty(); }
unsigned size() const { return x.size(); } unsigned size() const { return x.size(); }
void clear() {
x.clear();
y.clear();
z.clear();
}
coord operator[] (unsigned idx) const { coord operator[] (unsigned idx) const {
if (idx >= x.size()) if (idx >= x.size())
return coord(); return coord();

@ -16,7 +16,7 @@ inline bool getassignment( const df::coord2d &xy )
} }
inline bool getassignment( int x, int y ) inline bool getassignment( int x, int y )
{ {
return (bits[y] & (1 << x)); return (bits[(y&15)] & (1 << (x&15)));
} }
inline void setassignment( const df::coord2d &xy, bool bit ) inline void setassignment( const df::coord2d &xy, bool bit )
{ {
@ -25,9 +25,9 @@ inline void setassignment( const df::coord2d &xy, bool bit )
inline void setassignment( int x, int y, bool bit ) inline void setassignment( int x, int y, bool bit )
{ {
if(bit) if(bit)
bits[y] |= (1 << x); bits[(y&15)] |= (1 << (x&15));
else else
bits[y] &= ~(1 << x); bits[(y&15)] &= ~(1 << (x&15));
} }
bool has_assignments() bool has_assignments()
{ {

@ -25,7 +25,9 @@ distribution.
#pragma once #pragma once
#include "Export.h" #include "Export.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "Types.h"
#include "df/building.h" #include "df/building.h"
#include "df/building_type.h"
#include "df/civzone_type.h" #include "df/civzone_type.h"
#include "df/furnace_type.h" #include "df/furnace_type.h"
#include "df/workshop_type.h" #include "df/workshop_type.h"
@ -92,6 +94,9 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/ */
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes); DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::building *building, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::building *building, df::specific_ref_type type);
/** /**
* Sets the owner unit for the building. * Sets the owner unit for the building.
*/ */
@ -178,5 +183,8 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector<df::job_i
*/ */
DFHACK_EXPORT bool deconstruct(df::building *bld); DFHACK_EXPORT bool deconstruct(df::building *bld);
void updateBuildings(color_ostream& out, void* ptr);
void clearBuildings(color_ostream& out);
} }
} }

@ -0,0 +1,60 @@
#pragma once
#ifndef EVENT_MANAGER_H_INCLUDED
#define EVENT_MANAGER_H_INCLUDED
#include "Core.h"
#include "Export.h"
#include "ColorText.h"
#include "PluginManager.h"
#include "Console.h"
namespace DFHack {
namespace EventManager {
namespace EventType {
enum EventType {
TICK,
JOB_INITIATED,
JOB_COMPLETED,
UNIT_DEATH,
ITEM_CREATED,
BUILDING,
CONSTRUCTION,
SYNDROME,
INVASION,
EVENT_MAX
};
}
struct EventHandler {
void (*eventHandler)(color_ostream&, void*); //called when the event happens
int32_t freq;
EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
}
bool operator==(EventHandler& handle) const {
return eventHandler == handle.eventHandler && freq == handle.freq;
}
bool operator!=(EventHandler& handle) const {
return !( *this == handle);
}
};
struct SyndromeData {
int32_t unitId;
int32_t syndromeIndex;
SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) {
}
};
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void unregisterAll(Plugin* plugin);
void manageEvents(color_ostream& out);
void onStateChange(color_ostream& out, state_change_event event);
}
}
#endif

@ -36,55 +36,55 @@ distribution.
namespace DFHack namespace DFHack
{ {
// SDL stuff // SDL stuff
typedef signed short SINT16; typedef signed short SINT16;
typedef struct typedef struct
{ {
int16_t x, y; int16_t x, y;
uint16_t w, h; uint16_t w, h;
} DFSDL_Rect; } DFSDL_Rect;
typedef struct typedef struct
{ {
uint32_t flags; uint32_t flags;
void* format; // PixelFormat* void* format; // PixelFormat*
int w, h; int w, h;
int pitch; int pitch;
void* pixels; void* pixels;
void* userdata; // as far as i could see DF doesnt use this void* userdata; // as far as i could see DF doesnt use this
int locked; int locked;
void* lock_data; void* lock_data;
DFSDL_Rect clip_rect; DFSDL_Rect clip_rect;
void* map; void* map;
int refcount; int refcount;
} DFSDL_Surface; } DFSDL_Surface;
// ========= // =========
struct DFTileSurface struct DFTileSurface
{ {
bool paintOver; // draw over original tile? bool paintOver; // draw over original tile?
DFSDL_Surface* surface; // from where it should be drawn DFSDL_Surface* surface; // from where it should be drawn
DFSDL_Rect* rect; // from which coords (NULL to draw whole surface) DFSDL_Rect* rect; // from which coords (NULL to draw whole surface)
DFSDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst) DFSDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst)
}; };
class DFHACK_EXPORT Graphic : public Module class DFHACK_EXPORT Graphic : public Module
{ {
public: public:
Graphic(); Graphic();
~Graphic(); ~Graphic();
bool Finish() bool Finish()
{ {
return true; return true;
} }
bool Register(DFTileSurface* (*func)(int,int)); bool Register(DFTileSurface* (*func)(int,int));
bool Unregister(DFTileSurface* (*func)(int,int)); bool Unregister(DFTileSurface* (*func)(int,int));
DFTileSurface* Call(int x, int y); DFTileSurface* Call(int x, int y);
private: private:
struct Private; struct Private;
Private *d; Private *d;
}; };
} }

@ -29,6 +29,8 @@ distribution.
#include "ColorText.h" #include "ColorText.h"
#include <string> #include <string>
#include "Types.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/init.h" #include "df/init.h"
#include "df/ui.h" #include "df/ui.h"
@ -116,6 +118,9 @@ namespace DFHack
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
int y1, y2; int y1, y2;
bool menu_on, area_on, menu_forced; bool menu_on, area_on, menu_forced;
rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); }
rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); }
}; };
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims(); DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();

@ -40,6 +40,7 @@ distribution.
#include "df/building_actual.h" #include "df/building_actual.h"
#include "df/body_part_raw.h" #include "df/body_part_raw.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
#include "df/job_item_vector_id.h"
namespace df namespace df
{ {
@ -86,7 +87,8 @@ namespace DFHack
bool find(const std::string &token); bool find(const std::string &token);
bool matches(const df::job_item &item, MaterialInfo *mat = NULL); bool matches(df::job_item_vector_id vec_id);
bool matches(const df::job_item &item, MaterialInfo *mat = NULL, bool skip_vector = false);
}; };
inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) { inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) {
@ -123,6 +125,10 @@ struct dfh_item
namespace Items namespace Items
{ {
DFHACK_EXPORT bool isCasteMaterial(df::item_type itype);
DFHACK_EXPORT int getSubtypeCount(df::item_type itype);
DFHACK_EXPORT df::itemdef *getSubtypeDef(df::item_type itype, int subtype);
/// Look for a particular item by ID /// Look for a particular item by ID
DFHACK_EXPORT df::item * findItemByID(int32_t id); DFHACK_EXPORT df::item * findItemByID(int32_t id);
@ -145,6 +151,11 @@ DFHACK_EXPORT df::item *getContainer(df::item *item);
/// which items does it contain? /// which items does it contain?
DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items); DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items);
/// which building holds it?
DFHACK_EXPORT df::building *getHolderBuilding(df::item *item);
/// which unit holds it?
DFHACK_EXPORT df::unit *getHolderUnit(df::item *item);
/// Returns the true position of the item. /// Returns the true position of the item.
DFHACK_EXPORT df::coord getPosition(df::item *item); DFHACK_EXPORT df::coord getPosition(df::item *item);
@ -155,7 +166,7 @@ DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coo
DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container); DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container);
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode); DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1); df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Hauled, int body_part = -1);
/// Makes the item removed and marked for garbage collection /// Makes the item removed and marked for garbage collection
DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false); DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false);

@ -28,10 +28,13 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "Types.h"
#include <ostream> #include <ostream>
#include "DataDefs.h" #include "DataDefs.h"
#include "df/job_item_ref.h" #include "df/job_item_ref.h"
#include "df/item_type.h"
namespace df namespace df
{ {
@ -46,7 +49,7 @@ namespace DFHack
{ {
namespace Job { namespace Job {
// Duplicate the job structure. It is not linked into any DF lists. // Duplicate the job structure. It is not linked into any DF lists.
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false);
// Delete a cloned structure. // Delete a cloned structure.
DFHACK_EXPORT void deleteJobStruct(df::job *job); DFHACK_EXPORT void deleteJobStruct(df::job *job);
@ -54,6 +57,9 @@ namespace DFHack
DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx); DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx);
DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job); DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::job *job, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::job *job, df::specific_ref_type type);
DFHACK_EXPORT df::building *getHolder(df::job *job); DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job); DFHACK_EXPORT df::unit *getWorker(df::job *job);
@ -69,6 +75,9 @@ namespace DFHack
DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item, DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item,
df::job_item_ref::T_role role, df::job_item_ref::T_role role,
int filter_idx = -1, int insert_idx = -1); int filter_idx = -1, int insert_idx = -1);
DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype);
DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index);
} }
DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b);

@ -47,14 +47,6 @@ namespace MapExtras
class DFHACK_EXPORT MapCache; class DFHACK_EXPORT MapCache;
template<class R, class T> inline R index_tile(T &v, df::coord2d p) {
return v[p.x&15][p.y&15];
}
inline bool is_valid_tile_coord(df::coord2d p) {
return (p.x & ~15) == 0 && (p.y & ~15) == 0;
}
class Block; class Block;
class BlockInfo class BlockInfo
@ -119,8 +111,8 @@ public:
{ {
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
return t_matpair( return t_matpair(
index_tile<int16_t>(basemats->mattype,p), index_tile<int16_t>(basemats->mat_type,p),
index_tile<int16_t>(basemats->matindex,p) index_tile<int16_t>(basemats->mat_index,p)
); );
} }
bool isVeinAt(df::coord2d p) bool isVeinAt(df::coord2d p)
@ -159,8 +151,8 @@ public:
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
if (tiles->con_info) if (tiles->con_info)
return t_matpair( return t_matpair(
index_tile<int16_t>(tiles->con_info->mattype,p), index_tile<int16_t>(tiles->con_info->mat_type,p),
index_tile<int16_t>(tiles->con_info->matindex,p) index_tile<int16_t>(tiles->con_info->mat_index,p)
); );
return baseMaterialAt(p); return baseMaterialAt(p);
} }
@ -292,8 +284,8 @@ private:
struct ConInfo { struct ConInfo {
df::tile_bitmask constructed; df::tile_bitmask constructed;
df::tiletype tiles[16][16]; df::tiletype tiles[16][16];
t_blockmaterials mattype; t_blockmaterials mat_type;
t_blockmaterials matindex; t_blockmaterials mat_index;
}; };
struct TileInfo { struct TileInfo {
df::tile_bitmask frozen; df::tile_bitmask frozen;
@ -312,8 +304,8 @@ private:
}; };
struct BasematInfo { struct BasematInfo {
df::tile_bitmask dirty; df::tile_bitmask dirty;
t_blockmaterials mattype; t_blockmaterials mat_type;
t_blockmaterials matindex; t_blockmaterials mat_index;
t_blockmaterials layermat; t_blockmaterials layermat;
BasematInfo(); BasematInfo();

@ -32,7 +32,6 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "modules/Vegetation.h"
#include <vector> #include <vector>
#include "BitArray.h" #include "BitArray.h"
#include "modules/Materials.h" #include "modules/Materials.h"
@ -151,6 +150,21 @@ typedef uint8_t biome_indices40d [9];
*/ */
typedef uint16_t t_temperatures [16][16]; typedef uint16_t t_temperatures [16][16];
/**
* Index a tile array by a 2D coordinate, clipping it to mod 16
*/
template<class R, class T> inline R index_tile(T &v, df::coord2d p) {
return v[p.x&15][p.y&15];
}
/**
* Check if a 2D coordinate is in the 0-15 range.
*/
inline bool is_valid_tile_coord(df::coord2d p) {
return (p.x & ~15) == 0 && (p.y & ~15) == 0;
}
/** /**
* The Maps module * The Maps module
* \ingroup grp_modules * \ingroup grp_modules
@ -294,6 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which );
DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2);
} }
} }
#endif #endif

@ -338,10 +338,10 @@ namespace DFHack
*/ */
struct t_material struct t_material
{ {
t_itemType itemType; t_itemType item_type;
t_itemSubtype subType; t_itemSubtype item_subtype;
t_materialType material; t_materialType mat_type;
t_materialIndex index; t_materialIndex mat_index;
uint32_t flags; uint32_t flags;
}; };
/** /**

@ -27,7 +27,10 @@ distribution.
#include "Module.h" #include "Module.h"
#include "BitArray.h" #include "BitArray.h"
#include "ColorText.h" #include "ColorText.h"
#include "Types.h"
#include <string> #include <string>
#include <set>
#include "DataDefs.h" #include "DataDefs.h"
#include "df/graphic.h" #include "df/graphic.h"
@ -50,6 +53,8 @@ namespace DFHack
{ {
class Core; class Core;
typedef std::set<df::interface_key> interface_key_set;
/** /**
* The Screen module * The Screen module
* \ingroup grp_modules * \ingroup grp_modules
@ -76,6 +81,8 @@ namespace DFHack
bool valid() const { return tile >= 0; } bool valid() const { return tile >= 0; }
bool empty() const { return ch == 0 && tile == 0; } bool empty() const { return ch == 0 && tile == 0; }
// NOTE: LuaApi.cpp assumes this struct is plain data and has empty destructor
Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false)
: ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)),
tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0)
@ -92,11 +99,67 @@ namespace DFHack
: ch(ch), fg(fg), bg(bg), bold(bold), : ch(ch), fg(fg), bg(bg), bold(bold),
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg) tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
{} {}
void adjust(int8_t nfg) { fg = nfg&7; bold = !!(nfg&8); }
void adjust(int8_t nfg, bool nbold) { fg = nfg; bold = nbold; }
void adjust(int8_t nfg, int8_t nbg) { adjust(nfg); bg = nbg; }
void adjust(int8_t nfg, bool nbold, int8_t nbg) { adjust(nfg, nbold); bg = nbg; }
Pen color(int8_t nfg) const { Pen cp(*this); cp.adjust(nfg); return cp; }
Pen color(int8_t nfg, bool nbold) const { Pen cp(*this); cp.adjust(nfg, nbold); return cp; }
Pen color(int8_t nfg, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbg); return cp; }
Pen color(int8_t nfg, bool nbold, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbold, nbg); return cp; }
Pen chtile(char ch) { Pen cp(*this); cp.ch = ch; return cp; }
Pen chtile(char ch, int tile) { Pen cp(*this); cp.ch = ch; cp.tile = tile; return cp; }
};
struct DFHACK_EXPORT ViewRect {
rect2d view, clip;
ViewRect(rect2d area) : view(area), clip(area) {}
ViewRect(rect2d area, rect2d clip) : view(area), clip(clip) {}
bool isDefunct() const {
return clip.first.x > clip.second.x || clip.first.y > clip.second.y;
}
int width() const { return view.second.x-view.first.x+1; }
int height() const { return view.second.y-view.first.y+1; }
df::coord2d local(df::coord2d pos) const {
return df::coord2d(pos.x - view.first.x, pos.y - view.first.y);
}
df::coord2d global(df::coord2d pos) const {
return df::coord2d(pos.x + view.first.x, pos.y + view.first.y);
}
df::coord2d global(int x, int y) const {
return df::coord2d(x + view.first.x, y + view.first.y);
}
bool inClipGlobal(int x, int y) const {
return x >= clip.first.x && x <= clip.second.x &&
y >= clip.first.y && y <= clip.second.y;
}
bool inClipGlobal(df::coord2d pos) const {
return inClipGlobal(pos.x, pos.y);
}
bool inClipLocal(int x, int y) const {
return inClipGlobal(x + view.first.x, y + view.first.y);
}
bool inClipLocal(df::coord2d pos) const {
return inClipLocal(pos.x, pos.y);
}
ViewRect viewport(rect2d area) const {
rect2d nview(global(area.first), global(area.second));
return ViewRect(nview, intersect(nview, clip));
}
}; };
DFHACK_EXPORT df::coord2d getMousePos(); DFHACK_EXPORT df::coord2d getMousePos();
DFHACK_EXPORT df::coord2d getWindowSize(); DFHACK_EXPORT df::coord2d getWindowSize();
inline rect2d getScreenRect() {
return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1));
}
/// Returns the state of [GRAPHICS:YES/NO] /// Returns the state of [GRAPHICS:YES/NO]
DFHACK_EXPORT bool inGraphicsMode(); DFHACK_EXPORT bool inGraphicsMode();
@ -128,6 +191,80 @@ namespace DFHack
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
/// Retrieve the string representation of the bound key.
DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key);
/// A painter class that implements a clipping area and cursor/pen state
struct DFHACK_EXPORT Painter : ViewRect {
df::coord2d gcursor;
Pen cur_pen, cur_key_pen;
static const Pen default_pen;
static const Pen default_key_pen;
Painter(const ViewRect &area, const Pen &pen = default_pen, const Pen &kpen = default_key_pen)
: ViewRect(area), gcursor(area.view.first), cur_pen(pen), cur_key_pen(kpen)
{}
df::coord2d cursor() const { return local(gcursor); }
int cursorX() const { return gcursor.x - view.first.x; }
int cursorY() const { return gcursor.y - view.first.y; }
bool isValidPos() const { return inClipGlobal(gcursor); }
Painter viewport(rect2d area) const {
return Painter(ViewRect::viewport(area), cur_pen, cur_key_pen);
}
Painter &seek(df::coord2d pos) { gcursor = global(pos); return *this; }
Painter &seek(int x, int y) { gcursor = global(x,y); return *this; }
Painter &advance(int dx) { gcursor.x += dx; return *this; }
Painter &advance(int dx, int dy) { gcursor.x += dx; gcursor.y += dy; return *this; }
Painter &newline(int dx = 0) { gcursor.y++; gcursor.x = view.first.x + dx; return *this; }
const Pen &pen() const { return cur_pen; }
Painter &pen(const Pen &np) { cur_pen = np; return *this; }
Painter &pen(int8_t fg) { cur_pen.adjust(fg); return *this; }
const Pen &key_pen() const { return cur_key_pen; }
Painter &key_pen(const Pen &np) { cur_key_pen = np; return *this; }
Painter &key_pen(int8_t fg) { cur_key_pen.adjust(fg); return *this; }
Painter &clear() {
fillRect(Pen(' ',0,0,false), clip.first.x, clip.first.y, clip.second.x, clip.second.y);
return *this;
}
Painter &fill(const rect2d &area, const Pen &pen) {
rect2d irect = intersect(area, clip);
fillRect(pen, irect.first.x, irect.first.y, irect.second.x, irect.second.y);
return *this;
}
Painter &fill(const rect2d &area) { return fill(area, cur_pen); }
Painter &tile(const Pen &pen) {
if (isValidPos()) paintTile(pen, gcursor.x, gcursor.y);
return advance(1);
}
Painter &tile() { return tile(cur_pen); }
Painter &tile(char ch) { return tile(cur_pen.chtile(ch)); }
Painter &tile(char ch, int tileid) { return tile(cur_pen.chtile(ch, tileid)); }
Painter &string(const std::string &str, const Pen &pen) {
do_paint_string(str, pen); return advance(str.size());
}
Painter &string(const std::string &str) { return string(str, cur_pen); }
Painter &string(const std::string &str, int8_t fg) { return string(str, cur_pen.color(fg)); }
Painter &key(df::interface_key kc, const Pen &pen) {
return string(getKeyDisplay(kc), pen);
}
Painter &key(df::interface_key kc) { return key(kc, cur_key_pen); }
private:
void do_paint_string(const std::string &str, const Pen &pen);
};
} }
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {

@ -205,6 +205,9 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target);
/// Returns the true position of the unit (non-trivial in case of caged). /// Returns the true position of the unit (non-trivial in case of caged).
DFHACK_EXPORT df::coord getPosition(df::unit *unit); DFHACK_EXPORT df::coord getPosition(df::unit *unit);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::unit *unit, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::unit *unit, df::specific_ref_type type);
DFHACK_EXPORT df::item *getContainer(df::unit *unit); DFHACK_EXPORT df::item *getContainer(df::unit *unit);
DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick);
@ -235,6 +238,8 @@ DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false); DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {

@ -1,70 +0,0 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#ifndef CL_MOD_VEGETATION
#define CL_MOD_VEGETATION
/**
* \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
* @ingroup grp_modules
*/
#include "Export.h"
#include "DataDefs.h"
#include "df/plant.h"
namespace DFHack
{
namespace Vegetation
{
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
// "Simplified" copy of plant
struct t_plant {
df::language_name name;
df::plant_flags flags;
int16_t material;
df::coord pos;
int32_t grow_counter;
uint16_t temperature_1;
uint16_t temperature_2;
int32_t is_burning;
int32_t hitpoints;
int16_t update_order;
//std::vector<void *> unk1;
//int32_t unk2;
//uint16_t temperature_3;
//uint16_t temperature_4;
//uint16_t temperature_5;
// Pointer to original object, in case you want to modify it
df::plant *origin;
};
DFHACK_EXPORT bool isValid();
DFHACK_EXPORT uint32_t getCount();
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
}
}
#endif

@ -37,6 +37,12 @@ distribution.
#include "DataDefs.h" #include "DataDefs.h"
namespace df
{
struct tile_bitmask;
struct map_block;
}
namespace DFHack namespace DFHack
{ {
typedef df::game_mode GameMode; typedef df::game_mode GameMode;
@ -55,8 +61,6 @@ namespace DFHack
class DFContextShared; class DFContextShared;
class DFHACK_EXPORT PersistentDataItem { class DFHACK_EXPORT PersistentDataItem {
friend class World;
int id; int id;
std::string key_value; std::string key_value;
@ -65,13 +69,66 @@ namespace DFHack
public: public:
static const int NumInts = 7; static const int NumInts = 7;
bool isValid() { return id != 0; } bool isValid() const { return id != 0; }
int entry_id() { return -id; } int entry_id() const { return -id; }
const std::string &key() { return key_value; } int raw_id() const { return id; }
const std::string &key() const { return key_value; }
std::string &val() { return *str_value; } std::string &val() { return *str_value; }
const std::string &val() const { return *str_value; }
int &ival(int i) { return int_values[i]; } int &ival(int i) { return int_values[i]; }
int ival(int i) const { return int_values[i]; }
// Pack binary data into string field.
// Since DF serialization chokes on NUL bytes,
// use bit magic to ensure none of the bytes is 0.
// Choose the lowest bit for padding so that
// sign-extend can be used normally.
size_t data_size() const { return str_value->size(); }
bool check_data(size_t off, size_t sz = 1) {
return (str_value->size() >= off+sz);
}
void ensure_data(size_t off, size_t sz = 0) {
if (str_value->size() < off+sz) str_value->resize(off+sz, '\x01');
}
uint8_t *pdata(size_t off) { return (uint8_t*)&(*str_value)[off]; }
static const size_t int7_size = 1;
uint8_t get_uint7(size_t off) {
uint8_t *p = pdata(off);
return p[0]>>1;
}
int8_t get_int7(size_t off) {
uint8_t *p = pdata(off);
return int8_t(p[0])>>1;
}
void set_uint7(size_t off, uint8_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
}
void set_int7(size_t off, int8_t val) { set_uint7(off, val); }
static const size_t int28_size = 4;
uint32_t get_uint28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20);
}
int32_t get_int28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((int8_t(p[3])&~1)<<20);
}
void set_uint28(size_t off, uint32_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
p[1] = uint8_t((val>>6) | 1);
p[2] = uint8_t((val>>13) | 1);
p[3] = uint8_t((val>>20) | 1);
}
void set_int28(size_t off, int32_t val) { set_uint28(off, val); }
PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem() : id(0), str_value(0), int_values(0) {}
PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv)
@ -83,54 +140,45 @@ namespace DFHack
* \ingroup grp_modules * \ingroup grp_modules
* \ingroup grp_world * \ingroup grp_world
*/ */
class DFHACK_EXPORT World : public Module namespace World
{ {
public:
World();
~World();
bool Start();
bool Finish();
///true if paused, false if not ///true if paused, false if not
bool ReadPauseState(); DFHACK_EXPORT bool ReadPauseState();
///true if paused, false if not ///true if paused, false if not
void SetPauseState(bool paused); DFHACK_EXPORT void SetPauseState(bool paused);
uint32_t ReadCurrentTick(); DFHACK_EXPORT uint32_t ReadCurrentTick();
uint32_t ReadCurrentYear(); DFHACK_EXPORT uint32_t ReadCurrentYear();
uint32_t ReadCurrentMonth(); DFHACK_EXPORT uint32_t ReadCurrentMonth();
uint32_t ReadCurrentDay(); DFHACK_EXPORT uint32_t ReadCurrentDay();
uint8_t ReadCurrentWeather(); DFHACK_EXPORT uint8_t ReadCurrentWeather();
void SetCurrentWeather(uint8_t weather); DFHACK_EXPORT void SetCurrentWeather(uint8_t weather);
bool ReadGameMode(t_gamemodes& rd); DFHACK_EXPORT bool ReadGameMode(t_gamemodes& rd);
bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous DFHACK_EXPORT bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous
std::string ReadWorldFolder(); DFHACK_EXPORT std::string ReadWorldFolder();
// Store data in fake historical figure names. // Store data in fake historical figure names.
// This ensures that the values are stored in save games. // This ensures that the values are stored in save games.
PersistentDataItem AddPersistentData(const std::string &key); DFHACK_EXPORT PersistentDataItem AddPersistentData(const std::string &key);
PersistentDataItem GetPersistentData(const std::string &key); DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key);
PersistentDataItem GetPersistentData(int entry_id); DFHACK_EXPORT PersistentDataItem GetPersistentData(int entry_id);
// Calls GetPersistentData(key); if not found, adds and sets added to true. // Calls GetPersistentData(key); if not found, adds and sets added to true.
// The result can still be not isValid() e.g. if the world is not loaded. // The result can still be not isValid() e.g. if the world is not loaded.
PersistentDataItem GetPersistentData(const std::string &key, bool *added); DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key, bool *added);
// Lists all items with the given key. // Lists all items with the given key.
// If prefix is true, search for keys starting with key+"/". // If prefix is true, search for keys starting with key+"/".
// GetPersistentData(&vec,"",true) returns all items. // GetPersistentData(&vec,"",true) returns all items.
// Items have alphabetic order by key; same key ordering is undefined. // Items have alphabetic order by key; same key ordering is undefined.
void GetPersistentData(std::vector<PersistentDataItem> *vec, DFHACK_EXPORT void GetPersistentData(std::vector<PersistentDataItem> *vec,
const std::string &key, bool prefix = false); const std::string &key, bool prefix = false);
// Deletes the item; returns true if success. // Deletes the item; returns true if success.
bool DeletePersistentData(const PersistentDataItem &item); DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item);
void ClearPersistentCache(); DFHACK_EXPORT void ClearPersistentCache();
private: DFHACK_EXPORT df::tile_bitmask *getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create = false);
struct Private; DFHACK_EXPORT bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block);
Private *d; }
bool BuildPersistentCache();
};
} }
#endif #endif

@ -0,0 +1,123 @@
-- Simple binary patch with IDA dif file support.
local _ENV = mkmodule('binpatch')
local function load_patch(name)
local filename = name
if not string.match(filename, '[./\\]') then
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
end
local file, err = io.open(filename, 'r')
if not file then
if string.match(err, ': No such file or directory') then
return nil, 'patch not found'
end
end
local old_bytes = {}
local new_bytes = {}
for line in file:lines() do
if string.match(line, '^%x+:') then
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
if not offset then
file:close()
return nil, 'could not parse: '..line
end
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
if oldv > 255 or newv > 255 then
file:close()
return nil, 'invalid byte values: '..line
end
old_bytes[offset] = oldv
new_bytes[offset] = newv
end
end
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
end
local function rebase_table(input)
local output = {}
local base = dfhack.internal.getImageBase()
for k,v in pairs(input) do
local offset = dfhack.internal.adjustOffset(k)
if not offset then
return nil, string.format('invalid offset: %x', k)
end
output[base + offset] = v
end
return output
end
local function rebase_patch(patch)
local nold, err = rebase_table(patch.old_bytes)
if not nold then return nil, err end
local nnew, err = rebase_table(patch.new_bytes)
if not nnew then return nil, err end
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
end
BinaryPatch = defclass(BinaryPatch)
BinaryPatch.ATTRS {
name = DEFAULT_NIL,
old_bytes = DEFAULT_NIL,
new_bytes = DEFAULT_NIL,
}
function load_dif_file(name)
local patch, err = load_patch(name)
if not patch then return nil, err end
local rpatch, err = rebase_patch(patch)
if not rpatch then return nil, err end
return BinaryPatch(rpatch)
end
function BinaryPatch:status()
local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes)
if old_ok then
return 'removed'
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
return 'applied'
else
return 'conflict', addr
end
end
function BinaryPatch:isApplied()
return dfhack.internal.patchBytes({}, self.new_bytes)
end
function BinaryPatch:apply()
local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes)
if ok then
return true, 'applied the patch'
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
return true, 'patch is already applied'
else
return false, string.format('conflict at address %x', addr)
end
end
function BinaryPatch:isRemoved()
return dfhack.internal.patchBytes({}, self.old_bytes)
end
function BinaryPatch:remove()
local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes)
if ok then
return true, 'removed the patch'
elseif dfhack.internal.patchBytes({}, self.old_bytes) then
return true, 'patch is already removed'
else
return false, string.format('conflict at address %x', addr)
end
end
return _ENV

@ -3,16 +3,16 @@
local _ENV = mkmodule('class') local _ENV = mkmodule('class')
-- Metatable template for a class -- Metatable template for a class
class_obj = {} or class_obj class_obj = class_obj or {}
-- Methods shared by all classes -- Methods shared by all classes
common_methods = {} or common_methods common_methods = common_methods or {}
-- Forbidden names for class fields and methods. -- Forbidden names for class fields and methods.
reserved_names = { super = true, ATTRS = true } reserved_names = { super = true, ATTRS = true }
-- Attribute table metatable -- Attribute table metatable
attrs_meta = {} or attrs_meta attrs_meta = attrs_meta or {}
-- Create or updates a class; a class has metamethods and thus own metatable. -- Create or updates a class; a class has metamethods and thus own metatable.
function defclass(class,parent) function defclass(class,parent)
@ -65,10 +65,14 @@ end
local function apply_attrs(obj, attrs, init_table) local function apply_attrs(obj, attrs, init_table)
for k,v in pairs(attrs) do for k,v in pairs(attrs) do
if v == DEFAULT_NIL then local init_v = init_table[k]
v = nil if init_v ~= nil then
obj[k] = init_v
elseif v == DEFAULT_NIL then
obj[k] = nil
else
obj[k] = v
end end
obj[k] = init_table[k] or v
end end
end end
@ -133,6 +137,14 @@ function common_methods:callback(method, ...)
return dfhack.curry(self[method], self, ...) return dfhack.curry(self[method], self, ...)
end end
function common_methods:cb_getfield(field)
return function() return self[field] end
end
function common_methods:cb_setfield(field)
return function(val) self[field] = val end
end
function common_methods:assign(data) function common_methods:assign(data)
for k,v in pairs(data) do for k,v in pairs(data) do
self[k] = v self[k] = v

@ -125,6 +125,10 @@ end
-- Misc functions -- Misc functions
NEWLINE = "\n"
COMMA = ","
PERIOD = "."
function printall(table) function printall(table)
local ok,f,t,k = pcall(pairs,table) local ok,f,t,k = pcall(pairs,table)
if ok then if ok then
@ -157,6 +161,14 @@ function xyz2pos(x,y,z)
end end
end end
function same_xyz(a,b)
return a and b and a.x == b.x and a.y == b.y and a.z == b.z
end
function get_path_xyz(path,i)
return path.x[i], path.y[i], path.z[i]
end
function pos2xy(pos) function pos2xy(pos)
if pos then if pos then
local x = pos.x local x = pos.x
@ -174,6 +186,14 @@ function xy2pos(x,y)
end end
end end
function same_xy(a,b)
return a and b and a.x == b.x and a.y == b.y
end
function get_path_xy(path,i)
return path.x[i], path.y[i]
end
function safe_index(obj,idx,...) function safe_index(obj,idx,...)
if obj == nil or idx == nil then if obj == nil or idx == nil then
return nil return nil
@ -308,9 +328,11 @@ end
-- Command scripts -- Command scripts
dfhack.internal.scripts = dfhack.internal.scripts or {} local internal = dfhack.internal
local scripts = dfhack.internal.scripts internal.scripts = internal.scripts or {}
local scripts = internal.scripts
local hack_path = dfhack.getHackPath() local hack_path = dfhack.getHackPath()
function dfhack.run_script(name,...) function dfhack.run_script(name,...)
@ -329,5 +351,42 @@ function dfhack.run_script(name,...)
return f(...) return f(...)
end end
-- Per-save init file
function dfhack.getSavePath()
if dfhack.isWorldLoaded() then
return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir
end
end
if dfhack.is_core_context then
dfhack.onStateChange.DFHACK_PER_SAVE = function(op)
if op == SC_WORLD_LOADED or op == SC_WORLD_UNLOADED then
if internal.save_init then
if internal.save_init.onUnload then
safecall(internal.save_init.onUnload)
end
internal.save_init = nil
end
local path = dfhack.getSavePath()
if path and op == SC_WORLD_LOADED then
local env = setmetatable({ SAVE_PATH = path }, { __index = base_env })
local f,perr = loadfile(path..'/raw/init.lua', 't', env)
if f == nil then
if not string.match(perr, 'No such file or directory') then
dfhack.printerr(perr)
end
elseif safecall(f) then
internal.save_init = env
end
end
elseif internal.save_init and internal.save_init.onStateChange then
safecall(internal.save_init.onStateChange, op)
end
end
end
-- Feed the table back to the require() mechanism. -- Feed the table back to the require() mechanism.
return dfhack return dfhack

@ -334,7 +334,22 @@ local trap_inputs = {
}, },
[df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } } [df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } }
} }
local siegeengine_input = {
[df.siegeengine_type.Catapult] = {
{
item_type=df.item_type.CATAPULTPARTS,
vector_id=df.job_item_vector_id.CATAPULTPARTS,
quantity=3
}
},
[df.siegeengine_type.Ballista] = {
{
item_type=df.item_type.BALLISTAPARTS,
vector_id=df.job_item_vector_id.BALLISTAPARTS,
quantity=3
}
},
}
--[[ Functions for lookup in tables. ]] --[[ Functions for lookup in tables. ]]
local function get_custom_inputs(custom) local function get_custom_inputs(custom)
@ -359,6 +374,8 @@ local function get_inputs_by_type(type,subtype,custom)
end end
elseif type == df.building_type.Trap then elseif type == df.building_type.Trap then
return trap_inputs[subtype] return trap_inputs[subtype]
elseif type == df.building_type.SiegeEngine then
return siegeengine_input[subtype]
else else
return building_inputs[type] return building_inputs[type]
end end

@ -0,0 +1,591 @@
local _ENV = mkmodule('dfhack.workshops')
local utils = require 'utils'
input_filter_defaults = {
item_type = -1,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
-- Instead of noting those that allow artifacts, mark those that forbid them.
-- Leaves actually enabling artifacts to the discretion of the API user,
-- which is the right thing because unlike the game UI these filters are
-- used in a way that does not give the user a chance to choose manually.
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 fuel={item_type=df.item_type.BAR,mat_type=df.builtin_mats.COAL}
jobs_furnace={
[df.furnace_type.Smelter]={
{
name="Melt metal object",
items={fuel,{flags2={allow_melt_dump=true}}},--also maybe melt_designated
job_fields={job_type=df.job_type.MeltMetalObject}
}
},
[df.furnace_type.MagmaSmelter]={
{
name="Melt metal object",
items={{flags2={allow_melt_dump=true}}},--also maybe melt_designated
job_fields={job_type=df.job_type.MeltMetalObject}
}
},
--[[ [df.furnace_type.MetalsmithsForge]={
unpack(concat(furnaces,mechanism,anvil,crafts,coins,flask))
},
]]
--MetalsmithsForge,
--MagmaForge
--[[
forges:
weapons and ammo-> from raws...
armor -> raws
furniture -> builtins?
siege eq-> builtin (only balista head)
trap eq -> from raws+ mechanisms
other object-> anvil, crafts, goblets,toys,instruments,nestbox... (raws?) flask, coins,stud with iron
metal clothing-> raws???
]]
[df.furnace_type.GlassFurnace]={
{
name="collect sand",
items={},
job_fields={job_type=df.job_type.CollectSand}
},
--glass crafts x3
},
[df.furnace_type.WoodFurnace]={
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
{
name="make charcoal",
items={{}},
job_fields={job_type=df.job_type.MakeCharcoal}
},
{
name="make ash",
items={{}},
job_fields={job_type=df.job_type.MakeAsh}
}
},
[df.furnace_type.Kiln]={
{
name="collect clay",
items={},
job_fields={job_type=df.job_type.CollectClay}
}
},
}
jobs_workshop={
[df.workshop_type.Jewelers]={
{
name="cut gems",
items={{item_type=df.item_type.ROUGH,flags1={unrotten=true}}},
job_fields={job_type=df.job_type.CutGems}
},
{
name="encrust finished goods with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
{
name="encrust ammo with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
{
name="encrust furniture with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
},
[df.workshop_type.Fishery]={
{
name="prepare raw fish",
items={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}},
job_fields={job_type=df.job_type.PrepareRawFish}
},
{
name="extract from raw fish",
items={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
job_fields={job_type=df.job_type.ExtractFromRawFish}
},
{
name="catch live fish",
items={},
job_fields={job_type=df.job_type.CatchLiveFish}
}, -- no items?
},
[df.workshop_type.Still]={
{
name="brew drink",
items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}},
job_fields={job_type=df.job_type.BrewDrink}
},
{
name="extract from plants",
items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}},
job_fields={job_type=df.job_type.ExtractFromPlants}
},
--mead from raws?
},
[df.workshop_type.Masons]={
defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true},
{
name="construct armor stand",
items={{}},
job_fields={job_type=df.job_type.ConstructArmorStand}
},
{
name="construct blocks",
items={{}},
job_fields={job_type=df.job_type.ConstructBlocks}
},
{
name="construct throne",
items={{}},
job_fields={job_type=df.job_type.ConstructThrone}
},
{
name="construct coffin",
items={{}},
job_fields={job_type=df.job_type.ConstructCoffin}
},
{
name="construct door",
items={{}},
job_fields={job_type=df.job_type.ConstructDoor}
},
{
name="construct floodgate",
items={{}},
job_fields={job_type=df.job_type.ConstructFloodgate}
},
{
name="construct hatch cover",
items={{}},
job_fields={job_type=df.job_type.ConstructHatchCover}
},
{
name="construct grate",
items={{}},
job_fields={job_type=df.job_type.ConstructGrate}
},
{
name="construct cabinet",
items={{}},
job_fields={job_type=df.job_type.ConstructCabinet}
},
{
name="construct chest",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct statue",
items={{}},
job_fields={job_type=df.job_type.ConstructStatue}
},
{
name="construct slab",
items={{}},
job_fields={job_type=df.job_type.ConstructSlab}
},
{
name="construct table",
items={{}},
job_fields={job_type=df.job_type.ConstructTable}
},
{
name="construct weapon rack",
items={{}},
job_fields={job_type=df.job_type.ConstructWeaponRack}
},
{
name="construct quern",
items={{}},
job_fields={job_type=df.job_type.ConstructQuern}
},
{
name="construct millstone",
items={{}},
job_fields={job_type=df.job_type.ConstructMillstone}
},
},
[df.workshop_type.Carpenters]={
--training weapons, wooden shields
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
{
name="make barrel",
items={{}},
job_fields={job_type=df.job_type.MakeBarrel}
},
{
name="make bucket",
items={{}},
job_fields={job_type=df.job_type.MakeBucket}
},
{
name="make animal trap",
items={{}},
job_fields={job_type=df.job_type.MakeAnimalTrap}
},
{
name="make cage",
items={{}},
job_fields={job_type=df.job_type.MakeCage}
},
{
name="construct bed",
items={{}},
job_fields={job_type=df.job_type.ConstructBed}
},
{
name="construct bin",
items={{}},
job_fields={job_type=df.job_type.ConstructBin}
},
{
name="construct armor stand",
items={{}},
job_fields={job_type=df.job_type.ConstructArmorStand}
},
{
name="construct blocks",
items={{}},
job_fields={job_type=df.job_type.ConstructBlocks}
},
{
name="construct throne",
items={{}},
job_fields={job_type=df.job_type.ConstructThrone}
},
{
name="construct coffin",
items={{}},
job_fields={job_type=df.job_type.ConstructCoffin}
},
{
name="construct door",
items={{}},
job_fields={job_type=df.job_type.ConstructDoor}
},
{
name="construct floodgate",
items={{}},
job_fields={job_type=df.job_type.ConstructFloodgate}
},
{
name="construct hatch cover",
items={{}},
job_fields={job_type=df.job_type.ConstructHatchCover}
},
{
name="construct grate",
items={{}},
job_fields={job_type=df.job_type.ConstructGrate}
},
{
name="construct cabinet",
items={{}},
job_fields={job_type=df.job_type.ConstructCabinet}
},
{
name="construct chest",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct statue",
items={{}},
job_fields={job_type=df.job_type.ConstructStatue}
},
{
name="construct table",
items={{}},
job_fields={job_type=df.job_type.ConstructTable}
},
{
name="construct weapon rack",
items={{}},
job_fields={job_type=df.job_type.ConstructWeaponRack}
},
{
name="construct splint",
items={{}},
job_fields={job_type=df.job_type.ConstructSplint}
},
{
name="construct crutch",
items={{}},
job_fields={job_type=df.job_type.ConstructCrutch}
},
},
[df.workshop_type.Kitchen]={
--mat_type=2,3,4
defaults={flags1={unrotten=true,cookable=true}},
{
name="prepare easy meal",
items={{flags1={solid=true}},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=2}
},
{
name="prepare fine meal",
items={{flags1={solid=true}},{},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=3}
},
{
name="prepare lavish meal",
items={{flags1={solid=true}},{},{},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=4}
},
},
[df.workshop_type.Butchers]={
{
name="butcher an animal",
items={{flags1={butcherable=true,unrotten=true,nearby=true}}},
job_fields={job_type=df.job_type.ButcherAnimal}
},
{
name="extract from land animal",
items={{flags1={extract_bearing_vermin=true,unrotten=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
job_fields={job_type=df.job_type.ExtractFromLandAnimal}
},
{
name="catch live land animal",
items={},
job_fields={job_type=df.job_type.CatchLiveLandAnimal}
},
},
[df.workshop_type.Mechanics]={
{
name="construct mechanisms",
items={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,
flags3={hard=true}}},
job_fields={job_type=df.job_type.ConstructMechanisms}
},
{
name="construct traction bench",
items={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}},
job_fields={job_type=df.job_type.ConstructTractionBench}
},
},
[df.workshop_type.Loom]={
{
name="weave plant thread cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={plant=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave silk thread cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={silk=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave yarn cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={yarn=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave inorganic cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},mat_type=0}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="collect webs",
items={{item_type=df.item_type.THREAD,quantity=10,min_dimension=10,flags1={undisturbed=true}}},
job_fields={job_type=df.job_type.CollectWebs}
},
},
[df.workshop_type.Leatherworks]={
defaults={item_type=SKIN_TANNED},
{
name="construct leather bag",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct waterskin",
items={{}},
job_fields={job_type=df.job_type.MakeFlask}
},
{
name="construct backpack",
items={{}},
job_fields={job_type=df.job_type.MakeBackpack}
},
{
name="construct quiver",
items={{}},
job_fields={job_type=df.job_type.MakeQuiver}
},
{
name="sew leather image",
items={{item_type=-1,flags1={empty=true},flags2={sewn_imageless=true}},{}},
job_fields={job_type=df.job_type.SewImage}
},
},
[df.workshop_type.Dyers]={
{
name="dye thread",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={dyeable=true}},
{flags1={unrotten=true},flags2={dye=true}}},
job_fields={job_type=df.job_type.DyeThread}
},
{
name="dye cloth",
items={{item_type=df.item_type.CLOTH,quantity=10000,min_dimension=10000,flags2={dyeable=true}},
{flags1={unrotten=true},flags2={dye=true}}},
job_fields={job_type=df.job_type.DyeThread}
},
},
[df.workshop_type.Siege]={
{
name="construct balista parts",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.ConstructBallistaParts}
},
{
name="construct catapult parts",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.ConstructCatapultParts}
},
{
name="assemble balista arrow",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
},
{
name="assemble tipped balista arrow",
items={{item_type=df.item_type.WOOD},{item_type=df.item_type.BALLISTAARROWHEAD}},
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
},
},
}
local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2)
if bid1~=-1 and bid2~=-1 and bid1~=bid2 then
return false
end
if wid1~=-1 and wid2~=-1 and wid1~=wid2 then
return false
end
if cid1~=-1 and cid2~=-1 and cid1~=cid2 then
return false
end
return true
end
local function scanRawsReaction(buildingId,workshopId,customId)
local ret={}
for idx,reaction in ipairs(df.global.world.raws.reactions) do
for k,v in pairs(reaction.building.type) do
if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then
table.insert(ret,reaction)
end
end
end
return ret
end
local function reagentToJobItem(reagent,react_id,reagentId)
local ret_item
ret_item=utils.clone_with_default(reagent, input_filter_defaults)
ret_item.reaction_id=react_id
ret_item.reagent_index=reagentId
return ret_item
end
local function addReactionJobs(ret,bid,wid,cid)
local reactions=scanRawsReaction(bid,wid or -1,cid or -1)
for idx,react in pairs(reactions) do
local job={name=react.name,
items={},job_fields={job_type=df.job_type.CustomReaction,reaction_name=react.code}
}
for reagentId,reagent in pairs(react.reagents) do
table.insert(job.items,reagentToJobItem(reagent,idx,reagentId))
end
if react.flags.FUEL then
table.insert(job.items,fuel)
end
table.insert(ret,job)
end
end
local function scanRawsOres()
local ret={}
for idx,ore in ipairs(df.global.world.raws.inorganics) do
if #ore.metal_ore.mat_index~=0 then
ret[idx]=ore
end
end
return ret
end
local function addSmeltJobs(ret,use_fuel)
local ores=scanRawsOres()
for idx,ore in pairs(ores) do
print("adding:",ore.material.state_name.Solid)
printall(ore)
local job={name="smelt "..ore.material.state_name.Solid,job_fields={job_type=df.job_type.SmeltOre,mat_type=df.builtin_mats.INORGANIC,mat_index=idx},items={
{item_type=df.item_type.BOULDER,mat_type=df.builtin_mats.INORGANIC,mat_index=idx,vector_id=df.job_item_vector_id.BOULDER}}}
if use_fuel then
table.insert(job.items,fuel)
end
table.insert(ret,job)
end
return ret
end
function getJobs(buildingId,workshopId,customId)
local ret={}
local c_jobs
if buildingId==df.building_type.Workshop then
c_jobs=jobs_workshop[workshopId]
elseif buildingId==df.building_type.Furnace then
c_jobs=jobs_furnace[workshopId]
if workshopId == df.furnace_type.Smelter or workshopId == df.furnace_type.MagmaSmelter then
c_jobs=utils.clone(c_jobs,true)
addSmeltJobs(c_jobs,workshopId == df.furnace_type.Smelter)
end
else
return nil
end
if c_jobs==nil then
c_jobs={}
else
c_jobs=utils.clone(c_jobs,true)
end
addReactionJobs(c_jobs,buildingId,workshopId,customId)
for jobId,contents in pairs(c_jobs) do
if jobId~="defaults" then
local entry={}
entry.name=contents.name
local lclDefaults=utils.clone(input_filter_defaults,true)
if c_jobs.defaults ~=nil then
utils.assign(lclDefaults,c_jobs.defaults)
end
entry.items={}
for k,item in pairs(contents.items) do
entry.items[k]=utils.clone(lclDefaults,true)
utils.assign(entry.items[k],item)
end
if contents.job_fields~=nil then
entry.job_fields={}
utils.assign(entry.job_fields,contents.job_fields)
end
ret[jobId]=entry
end
end
--get jobs, add in from raws
return ret
end
return _ENV

@ -6,7 +6,17 @@ local dscreen = dfhack.screen
USE_GRAPHICS = dscreen.inGraphicsMode() USE_GRAPHICS = dscreen.inGraphicsMode()
CLEAR_PEN = {ch=32,fg=0,bg=0} local to_pen = dfhack.pen.parse
CLEAR_PEN = to_pen{ch=32,fg=0,bg=0}
local FAKE_INPUT_KEYS = {
_MOUSE_L = true,
_MOUSE_R = true,
_MOUSE_L_DOWN = true,
_MOUSE_R_DOWN = true,
_STRING = true,
}
function simulateInput(screen,...) function simulateInput(screen,...)
local keys = {} local keys = {}
@ -14,7 +24,7 @@ function simulateInput(screen,...)
local kv = arg local kv = arg
if type(arg) == 'string' then if type(arg) == 'string' then
kv = df.interface_key[arg] kv = df.interface_key[arg]
if kv == nil then if kv == nil and not FAKE_INPUT_KEYS[arg] then
error('Invalid keycode: '..arg) error('Invalid keycode: '..arg)
end end
end end
@ -48,57 +58,140 @@ end
function mkdims_wh(x1,y1,w,h) function mkdims_wh(x1,y1,w,h)
return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h } return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h }
end end
function inset(rect,dx1,dy1,dx2,dy2)
return mkdims_xy(
rect.x1+dx1, rect.y1+dy1,
rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1)
)
end
function is_in_rect(rect,x,y) function is_in_rect(rect,x,y)
return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2 return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
end end
local function to_pen(default, pen, bg, bold) local function align_coord(gap,align,lv,rv)
if pen == nil then if gap <= 0 then
return default or {} return 0
elseif type(pen) ~= 'table' then end
return {fg=pen,bg=bg,bold=bold} if not align then
if rv and not lv then
align = 1.0
elseif lv and not rv then
align = 0.0
else
align = 0.5
end
end
return math.floor(gap*align)
end
function compute_frame_rect(wavail,havail,spec,xgap,ygap)
if not spec then
return mkdims_wh(0,0,wavail,havail)
end
local sw = wavail - (spec.l or 0) - (spec.r or 0)
local sh = havail - (spec.t or 0) - (spec.b or 0)
local rqw = math.min(sw, (spec.w or sw)+xgap)
local rqh = math.min(sh, (spec.h or sh)+ygap)
local ax = align_coord(sw - rqw, spec.xalign, spec.l, spec.r)
local ay = align_coord(sh - rqh, spec.yalign, spec.t, spec.b)
local rect = mkdims_wh((spec.l or 0) + ax, (spec.t or 0) + ay, rqw, rqh)
rect.wgap = sw - rqw
rect.hgap = sh - rqh
return rect
end
local function parse_inset(inset)
local l,r,t,b
if type(inset) == 'table' then
l,r = inset.l or inset.x, inset.r or inset.x
t,b = inset.t or inset.y, inset.b or inset.y
else else
return pen l = inset or 0
t,r,b = l,l,l
end end
return l,r,t,b
end end
---------------------------- function inset_frame(rect, inset, gap)
-- Clipped painter object -- gap = gap or 0
---------------------------- local l,t,r,b = parse_inset(inset)
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
end
function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame)
gap = gap or 0
local l,t,r,b = parse_inset(inset)
local xgap,ygap = 0,0
if inner_frame then
xgap,ygap = gap*2+l+r, gap*2+t+b
end
local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap)
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
return rect, body
end
Painter = defclass(Painter, nil) function blink_visible(delay)
return math.floor(dfhack.getTickCount()/delay) % 2 == 0
end
function Painter:init(args) function getKeyDisplay(code)
local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) if type(code) == 'string' then
local crect = args.clip_rect or rect code = df.interface_key[code]
self:assign{ end
x = rect.x1, y = rect.y1, return dscreen.getKeyDisplay(code)
x1 = rect.x1, clip_x1 = crect.x1,
y1 = rect.y1, clip_y1 = crect.y1,
x2 = rect.x2, clip_x2 = crect.x2,
y2 = rect.y2, clip_y2 = crect.y2,
width = rect.x2-rect.x1+1,
height = rect.y2-rect.y1+1,
cur_pen = to_pen(nil, args.pen or COLOR_GREY)
}
end end
function Painter.new(rect, pen) -----------------------------------
return Painter{ rect = rect, pen = pen } -- Clipped view rectangle object --
-----------------------------------
ViewRect = defclass(ViewRect, nil)
function ViewRect:init(args)
if args.view_rect then
self:assign(args.view_rect)
else
local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize())
local crect = args.clip_rect or rect
self:assign{
x1 = rect.x1, clip_x1 = crect.x1,
y1 = rect.y1, clip_y1 = crect.y1,
x2 = rect.x2, clip_x2 = crect.x2,
y2 = rect.y2, clip_y2 = crect.y2,
width = rect.x2-rect.x1+1,
height = rect.y2-rect.y1+1,
}
end
if args.clip_view then
local cr = args.clip_view
self:assign{
clip_x1 = math.max(self.clip_x1, cr.clip_x1),
clip_y1 = math.max(self.clip_y1, cr.clip_y1),
clip_x2 = math.min(self.clip_x2, cr.clip_x2),
clip_y2 = math.min(self.clip_y2, cr.clip_y2),
}
end
end end
function Painter:isValidPos() function ViewRect:isDefunct()
return self.x >= self.clip_x1 and self.x <= self.clip_x2 return (self.clip_x1 > self.clip_x2 or self.clip_y1 > self.clip_y2)
and self.y >= self.clip_y1 and self.y <= self.clip_y2
end end
function Painter:viewport(x,y,w,h) function ViewRect:inClipGlobalXY(x,y)
return x >= self.clip_x1 and x <= self.clip_x2
and y >= self.clip_y1 and y <= self.clip_y2
end
function ViewRect:inClipLocalXY(x,y)
return (x+self.x1) >= self.clip_x1 and (x+self.x1) <= self.clip_x2
and (y+self.y1) >= self.clip_y1 and (y+self.y1) <= self.clip_y2
end
function ViewRect:localXY(x,y)
return x-self.x1, y-self.y1
end
function ViewRect:globalXY(x,y)
return x+self.x1, y+self.y1
end
function ViewRect:viewport(x,y,w,h)
if type(x) == 'table' then if type(x) == 'table' then
x,y,w,h = x.x1, x.y1, x.width, x.height x,y,w,h = x.x1, x.y1, x.width, x.height
end end
@ -113,17 +206,59 @@ function Painter:viewport(x,y,w,h)
clip_y1 = math.max(self.clip_y1, y1), clip_y1 = math.max(self.clip_y1, y1),
clip_x2 = math.min(self.clip_x2, x2), clip_x2 = math.min(self.clip_x2, x2),
clip_y2 = math.min(self.clip_y2, y2), clip_y2 = math.min(self.clip_y2, y2),
-- Pen
cur_pen = self.cur_pen
} }
return mkinstance(ViewRect, vp)
end
----------------------------
-- Clipped painter object --
----------------------------
Painter = defclass(Painter, ViewRect)
function Painter:init(args)
self.x = self.x1
self.y = self.y1
self.cur_pen = to_pen(args.pen or COLOR_GREY)
self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN)
end
function Painter.new(rect, pen)
return Painter{ rect = rect, pen = pen }
end
function Painter.new_view(view_rect, pen)
return Painter{ view_rect = view_rect, pen = pen }
end
function Painter.new_xy(x1,y1,x2,y2,pen)
return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen }
end
function Painter.new_wh(x,y,w,h,pen)
return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen }
end
function Painter:isValidPos()
return self:inClipGlobalXY(self.x, self.y)
end
function Painter:viewport(x,y,w,h)
local vp = ViewRect.viewport(x,y,w,h)
vp.cur_pen = self.cur_pen
vp.cur_key_pen = self.cur_key_pen
return mkinstance(Painter, vp):seek(0,0) return mkinstance(Painter, vp):seek(0,0)
end end
function Painter:localX() function Painter:cursor()
return self.x - self.x1, self.y - self.y1
end
function Painter:cursorX()
return self.x - self.x1 return self.x - self.x1
end end
function Painter:localY() function Painter:cursorY()
return self.y - self.y1 return self.y - self.y1
end end
@ -151,10 +286,12 @@ function Painter:pen(pen,...)
end end
function Painter:color(fg,bold,bg) function Painter:color(fg,bold,bg)
self.cur_pen = copyall(self.cur_pen) self.cur_pen = to_pen(self.cur_pen, fg, bg, bold)
self.cur_pen.fg = fg return self
self.cur_pen.bold = bold end
if bg then self.cur_pen.bg = bg end
function Painter:key_pen(pen,...)
self.cur_key_pen = to_pen(self.cur_key_pen, pen, ...)
return self return self
end end
@ -210,16 +347,150 @@ function Painter:string(text,pen,...)
return self:advance(#text, nil) return self:advance(#text, nil)
end end
function Painter:key(code,pen,...)
return self:string(
getKeyDisplay(code),
to_pen(self.cur_key_pen, pen, ...)
)
end
--------------------------
-- Abstract view object --
--------------------------
View = defclass(View)
View.ATTRS {
active = true,
visible = true,
view_id = DEFAULT_NIL,
}
function View:init(args)
self.subviews = {}
end
function View:addviews(list)
if not list then return end
local sv = self.subviews
for _,obj in ipairs(list) do
table.insert(sv, obj)
local id = obj.view_id
if id and type(id) ~= 'number' and sv[id] == nil then
sv[id] = obj
end
end
for _,dir in ipairs(list) do
for id,obj in pairs(dir.subviews) do
if id and type(id) ~= 'number' and sv[id] == nil then
sv[id] = obj
end
end
end
end
function View:getWindowSize()
local rect = self.frame_body
return rect.width, rect.height
end
function View:getMousePos()
local rect = self.frame_body
local x,y = dscreen.getMousePos()
if rect and rect:inClipGlobalXY(x,y) then
return rect:localXY(x,y)
end
end
function View:computeFrame(parent_rect)
return mkdims_wh(0,0,parent_rect.width,parent_rect.height)
end
function View:updateSubviewLayout(frame_body)
for _,child in ipairs(self.subviews) do
child:updateLayout(frame_body)
end
end
function View:updateLayout(parent_rect)
if not parent_rect then
parent_rect = self.frame_parent_rect
else
self.frame_parent_rect = parent_rect
end
self:invoke_before('preUpdateLayout', parent_rect)
local frame_rect,body_rect = self:computeFrame(parent_rect)
self.frame_rect = frame_rect
self.frame_body = parent_rect:viewport(body_rect or frame_rect)
self:invoke_after('postComputeFrame', self.frame_body)
self:updateSubviewLayout(self.frame_body)
self:invoke_after('postUpdateLayout', self.frame_body)
end
function View:renderSubviews(dc)
for _,child in ipairs(self.subviews) do
if child.visible then
child:render(dc)
end
end
end
function View:render(dc)
self:onRenderFrame(dc, self.frame_rect)
local sub_dc = Painter{
view_rect = self.frame_body,
clip_view = dc
}
self:onRenderBody(sub_dc)
self:renderSubviews(sub_dc)
end
function View:onRenderFrame(dc,rect)
end
function View:onRenderBody(dc)
end
function View:inputToSubviews(keys)
local children = self.subviews
for i=#children,1,-1 do
local child = children[i]
if child.visible and child.active and child:onInput(keys) then
return true
end
end
return false
end
function View:onInput(keys)
return self:inputToSubviews(keys)
end
------------------------ ------------------------
-- Base screen object -- -- Base screen object --
------------------------ ------------------------
Screen = defclass(Screen) Screen = defclass(Screen, View)
Screen.text_input_mode = false Screen.text_input_mode = false
function Screen:postinit() function Screen:postinit()
self:updateLayout() self:onResize(dscreen.getWindowSize())
end end
Screen.isDismissed = dscreen.isDismissed Screen.isDismissed = dscreen.isDismissed
@ -236,14 +507,6 @@ function Screen:invalidate()
dscreen.invalidate() dscreen.invalidate()
end end
function Screen:getWindowSize()
return dscreen.getWindowSize()
end
function Screen:getMousePos()
return dscreen.getMousePos()
end
function Screen:renderParent() function Screen:renderParent()
if self._native and self._native.parent then if self._native and self._native.parent then
self._native.parent:render() self._native.parent:render()
@ -258,21 +521,22 @@ function Screen:sendInputToParent(...)
end end
end end
function Screen:show(below) function Screen:show(parent)
if self._native then if self._native then
error("This screen is already on display") error("This screen is already on display")
end end
self:onAboutToShow(below) parent = parent or dfhack.gui.getCurViewscreen(true)
if not dscreen.show(self, below) then self:onAboutToShow(parent)
if not dscreen.show(self, parent.child) then
error('Could not show screen') error('Could not show screen')
end end
end end
function Screen:onAboutToShow() function Screen:onAboutToShow(parent)
end end
function Screen:onShow() function Screen:onShow()
self:updateLayout() self:onResize(dscreen.getWindowSize())
end end
function Screen:dismiss() function Screen:dismiss()
@ -288,10 +552,11 @@ function Screen:onDestroy()
end end
function Screen:onResize(w,h) function Screen:onResize(w,h)
self:updateLayout() self:updateLayout(ViewRect{ rect = mkdims_wh(0,0,w,h) })
end end
function Screen:updateLayout() function Screen:onRender()
self:render(Painter.new())
end end
------------------------ ------------------------
@ -300,28 +565,28 @@ end
-- Plain grey-colored frame. -- Plain grey-colored frame.
GREY_FRAME = { GREY_FRAME = {
frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, frame_pen = to_pen{ ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY },
title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE }, title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_WHITE },
signature_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
} }
-- The usual boundary used by the DF screens. Often has fancy pattern in tilesets. -- The usual boundary used by the DF screens. Often has fancy pattern in tilesets.
BOUNDARY_FRAME = { BOUNDARY_FRAME = {
frame_pen = { ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, frame_pen = to_pen{ ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK },
title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
signature_pen = { fg = COLOR_BLACK, bg = COLOR_DARKGREY }, signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_DARKGREY },
} }
GREY_LINE_FRAME = { GREY_LINE_FRAME = {
frame_pen = { ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, frame_pen = to_pen{ ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK },
h_frame_pen = { ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, h_frame_pen = to_pen{ ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK },
v_frame_pen = { ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, v_frame_pen = to_pen{ ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK },
lt_frame_pen = { ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, lt_frame_pen = to_pen{ ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK },
lb_frame_pen = { ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, lb_frame_pen = to_pen{ ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK },
rt_frame_pen = { ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, rt_frame_pen = to_pen{ ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK },
rb_frame_pen = { ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, rb_frame_pen = to_pen{ ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK },
title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
signature_pen = { fg = COLOR_DARKGREY, bg = COLOR_BLACK }, signature_pen = to_pen{ fg = COLOR_DARKGREY, bg = COLOR_BLACK },
} }
function paint_frame(x1,y1,x2,y2,style,title) function paint_frame(x1,y1,x2,y2,style,title)
@ -353,68 +618,31 @@ FramedScreen.ATTRS{
frame_title = DEFAULT_NIL, frame_title = DEFAULT_NIL,
frame_width = DEFAULT_NIL, frame_width = DEFAULT_NIL,
frame_height = DEFAULT_NIL, frame_height = DEFAULT_NIL,
frame_inset = 0,
frame_background = CLEAR_PEN,
} }
local function hint_coord(gap,hint)
if hint and hint > 0 then
return math.min(hint,gap)
elseif hint and hint < 0 then
return math.max(0,gap-hint)
else
return math.floor(gap/2)
end
end
function FramedScreen:getWantedFrameSize() function FramedScreen:getWantedFrameSize()
return self.frame_width, self.frame_height return self.frame_width, self.frame_height
end end
function FramedScreen:updateFrameSize() function FramedScreen:computeFrame(parent_rect)
local sw, sh = dscreen.getWindowSize() local sw, sh = parent_rect.width, parent_rect.height
local iw, ih = sw-2, sh-2 local fw, fh = self:getWantedFrameSize(parent_rect)
local fw, fh = self:getWantedFrameSize() return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true)
local width = math.min(fw or iw, iw)
local height = math.min(fh or ih, ih)
local gw, gh = iw-width, ih-height
local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint)
self.frame_rect = mkdims_wh(x1+1,y1+1,width,height)
self.frame_opaque = (gw == 0 and gh == 0)
end
function FramedScreen:updateLayout()
self:updateFrameSize()
end end
function FramedScreen:getWindowSize() function FramedScreen:onRenderFrame(dc, rect)
local rect = self.frame_rect local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2
return rect.width, rect.height
end
function FramedScreen:getMousePos() if rect.wgap <= 0 and rect.hgap <= 0 then
local rect = self.frame_rect dc:clear()
local x,y = dscreen.getMousePos()
if is_in_rect(rect,x,y) then
return x-rect.x1, y-rect.y1
end
end
function FramedScreen:onRender()
local rect = self.frame_rect
local x1,y1,x2,y2 = rect.x1-1, rect.y1-1, rect.x2+1, rect.y2+1
if self.frame_opaque then
dscreen.clear()
else else
self:renderParent() self:renderParent()
dscreen.fillRect(CLEAR_PEN,x1,y1,x2,y2) dc:fill(rect, self.frame_background)
end end
paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title)
self:onRenderBody(Painter.new(rect))
end
function FramedScreen:onRenderBody(dc)
end end
return _ENV return _ENV

@ -0,0 +1,285 @@
-- Stock dialog for selecting buildings
local _ENV = mkmodule('gui.buildings')
local gui = require('gui')
local widgets = require('gui.widgets')
local dlg = require('gui.dialogs')
local utils = require('utils')
ARROW = string.char(26)
WORKSHOP_ABSTRACT={
[df.building_type.Civzone]=true,[df.building_type.Stockpile]=true,
}
WORKSHOP_SPECIAL={
[df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true,
[df.building_type.Construction]=true,[df.building_type.SiegeEngine]=true
}
BuildingDialog = defclass(BuildingDialog, gui.FramedScreen)
BuildingDialog.focus_path = 'BuildingDialog'
BuildingDialog.ATTRS{
prompt = 'Type or select a building from this list',
frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
frame_title = 'Select Building',
-- new attrs
none_caption = 'none',
hide_none = false,
use_abstract = true,
use_workshops = true,
use_tool_workshop=true,
use_furnace = true,
use_construction = true,
use_siege = true,
use_trap = true,
use_custom = true,
building_filter = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_cancel = DEFAULT_NIL,
on_close = DEFAULT_NIL,
}
function BuildingDialog:init(info)
self:addviews{
widgets.Label{
text = {
self.prompt, '\n\n',
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
},
text_pen = COLOR_WHITE,
frame = { l = 0, t = 0 },
},
widgets.Label{
view_id = 'back',
visible = false,
text = { { key = 'LEAVESCREEN', text = ': Back' } },
frame = { r = 0, b = 0 },
auto_width = true,
},
widgets.FilteredList{
view_id = 'list',
not_found_label = 'No matching buildings',
frame = { l = 0, r = 0, t = 4, b = 2 },
icon_width = 2,
on_submit = self:callback('onSubmitItem'),
},
widgets.Label{
text = { {
key = 'SELECT', text = ': Select',
disabled = function() return not self.subviews.list:canSubmit() end
} },
frame = { l = 0, b = 0 },
}
}
self:initBuiltinMode()
end
function BuildingDialog:getWantedFrameSize(rect)
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
end
function BuildingDialog:onDestroy()
if self.on_close then
self.on_close()
end
end
function BuildingDialog:initBuiltinMode()
local choices = {}
if not self.hide_none then
table.insert(choices, { text = self.none_caption, type_id = -1, subtype_id = -1, custom_id=-1})
end
if self.use_workshops then
table.insert(choices, {
icon = ARROW, text = 'workshop', key = 'CUSTOM_SHIFT_W',
cb = self:callback('initWorkshopMode')
})
end
if self.use_furnace then
table.insert(choices, {
icon = ARROW, text = 'furnaces', key = 'CUSTOM_SHIFT_F',
cb = self:callback('initFurnaceMode')
})
end
if self.use_trap then
table.insert(choices, {
icon = ARROW, text = 'traps', key = 'CUSTOM_SHIFT_T',
cb = self:callback('initTrapMode')
})
end
if self.use_construction then
table.insert(choices, {
icon = ARROW, text = 'constructions', key = 'CUSTOM_SHIFT_C',
cb = self:callback('initConstructionMode')
})
end
if self.use_siege then
table.insert(choices, {
icon = ARROW, text = 'siege engine', key = 'CUSTOM_SHIFT_S',
cb = self:callback('initSiegeMode')
})
end
if self.use_custom then
table.insert(choices, {
icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U',
cb = self:callback('initCustomMode')
})
end
for i=0,df.building_type._last_item do
if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then
self:addBuilding(choices, df.building_type[i], i, -1,-1,nil)
end
end
self:pushContext('Any building', choices)
end
function BuildingDialog:initWorkshopMode()
local choices = {}
for i=0,df.workshop_type._last_item do
if i~=df.workshop_type.Custom and (i~=df.workshop_type.Tool or self.use_tool_workshop) then
self:addBuilding(choices, df.workshop_type[i], df.building_type.Workshop, i,-1,nil)
end
end
self:pushContext('Workshops', choices)
end
function BuildingDialog:initTrapMode()
local choices = {}
for i=0,df.trap_type._last_item do
self:addBuilding(choices, df.trap_type[i], df.building_type.Trap, i,-1,nil)
end
self:pushContext('Traps', choices)
end
function BuildingDialog:initConstructionMode()
local choices = {}
for i=0,df.construction_type._last_item do
self:addBuilding(choices, df.construction_type[i], df.building_type.Construction, i,-1,nil)
end
self:pushContext('Constructions', choices)
end
function BuildingDialog:initFurnaceMode()
local choices = {}
for i=0,df.furnace_type._last_item do
self:addBuilding(choices, df.furnace_type[i], df.building_type.Furnace, i,-1,nil)
end
self:pushContext('Furnaces', choices)
end
function BuildingDialog:initSiegeMode()
local choices = {}
for i=0,df.siegeengine_type._last_item do
self:addBuilding(choices, df.siegeengine_type[i], df.building_type.SiegeEngine, i,-1,nil)
end
self:pushContext('Siege weapons', choices)
end
function BuildingDialog:initCustomMode()
local choices = {}
local raws=df.global.world.raws.buildings.all
for k,v in pairs(raws) do
self:addBuilding(choices, v.name, df.building_type.Workshop,df.workshop_type.Custom,v.id,v)
end
self:pushContext('Custom workshops', choices)
end
function BuildingDialog:addBuilding(choices, name,type_id, subtype_id, custom_id, parent)
-- Check the filter
if self.building_filter and not self.building_filter(name,type_id,subtype_id,custom_id, parent) then
return
end
table.insert(choices, {
text = name:lower(),
customshop = parent,
type_id = type_id, subtype_id = subtype_id, custom_id=custom_id
})
end
function BuildingDialog:pushContext(name, choices)
if not self.back_stack then
self.back_stack = {}
self.subviews.back.visible = false
else
table.insert(self.back_stack, {
context_str = self.context_str,
all_choices = self.subviews.list:getChoices(),
edit_text = self.subviews.list:getFilter(),
selected = self.subviews.list:getSelected(),
})
self.subviews.back.visible = true
end
self.context_str = name
self.subviews.list:setChoices(choices, 1)
end
function BuildingDialog:onGoBack()
local save = table.remove(self.back_stack)
self.subviews.back.visible = (#self.back_stack > 0)
self.context_str = save.context_str
self.subviews.list:setChoices(save.all_choices)
self.subviews.list:setFilter(save.edit_text, save.selected)
end
function BuildingDialog:submitBuilding(type_id,subtype_id,custom_id,choice,index)
self:dismiss()
if self.on_select then
self.on_select(type_id,subtype_id,custom_id,choice,index)
end
end
function BuildingDialog:onSubmitItem(idx, item)
if item.cb then
item:cb(idx)
else
self:submitBuilding(item.type_id, item.subtype_id,item.custom_id,item,idx)
end
end
function BuildingDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
self:onGoBack()
else
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
end
end
function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter)
BuildingDialog{
frame_title = title,
prompt = prompt,
building_filter = build_filter,
on_select = on_select,
on_cancel = on_cancel,
}:show()
end
return _ENV

@ -3,6 +3,7 @@
local _ENV = mkmodule('gui.dialogs') local _ENV = mkmodule('gui.dialogs')
local gui = require('gui') local gui = require('gui')
local widgets = require('gui.widgets')
local utils = require('utils') local utils = require('utils')
local dscreen = dfhack.screen local dscreen = dfhack.screen
@ -13,48 +14,35 @@ MessageBox.focus_path = 'MessageBox'
MessageBox.ATTRS{ MessageBox.ATTRS{
frame_style = gui.GREY_LINE_FRAME, frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
-- new attrs -- new attrs
text = {},
on_accept = DEFAULT_NIL, on_accept = DEFAULT_NIL,
on_cancel = DEFAULT_NIL, on_cancel = DEFAULT_NIL,
on_close = DEFAULT_NIL, on_close = DEFAULT_NIL,
text_pen = DEFAULT_NIL,
} }
function MessageBox:preinit(info) function MessageBox:init(info)
if type(info.text) == 'string' then self:addviews{
info.text = utils.split_string(info.text, "\n") widgets.Label{
end view_id = 'label',
text = info.text,
text_pen = info.text_pen,
frame = { l = 0, t = 0 },
auto_height = true
}
}
end end
function MessageBox:getWantedFrameSize() function MessageBox:getWantedFrameSize()
local text = self.text local label = self.subviews.label
local w = #(self.frame_title or '') + 4 local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4)
w = math.max(w, 20) return math.max(width, label:getTextWidth()), label:getTextHeight()
w = math.max(self.frame_width or w, w)
for _, l in ipairs(text) do
w = math.max(w, #l)
end
local h = #text+1
if h > 1 then
h = h+1
end
return w+2, #text+2
end end
function MessageBox:onRenderBody(dc) function MessageBox:onRenderFrame(dc,rect)
if #self.text > 0 then MessageBox.super.onRenderFrame(self,dc,rect)
dc:newline(1):pen(self.text_pen or COLOR_GREY)
for _, l in ipairs(self.text or {}) do
dc:string(l):newline(1)
end
end
if self.on_accept then if self.on_accept then
local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1 dc:seek(rect.x1+2,rect.y2):key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM')
dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC')
dscreen.paintString({fg=COLOR_GREY},x+3,y,'/')
dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y')
end end
end end
@ -75,6 +63,8 @@ function MessageBox:onInput(keys)
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
else
self:inputToSubviews(keys)
end end
end end
@ -102,8 +92,6 @@ InputBox = defclass(InputBox, MessageBox)
InputBox.focus_path = 'InputBox' InputBox.focus_path = 'InputBox'
InputBox.ATTRS{ InputBox.ATTRS{
input = '',
input_pen = DEFAULT_NIL,
on_input = DEFAULT_NIL, on_input = DEFAULT_NIL,
} }
@ -111,46 +99,36 @@ function InputBox:preinit(info)
info.on_accept = nil info.on_accept = nil
end end
function InputBox:init(info)
self:addviews{
widgets.EditField{
view_id = 'edit',
text = info.input,
text_pen = info.input_pen,
frame = { l = 0, r = 0, h = 1 },
}
}
end
function InputBox:getWantedFrameSize() function InputBox:getWantedFrameSize()
local mw, mh = InputBox.super.getWantedFrameSize(self) local mw, mh = InputBox.super.getWantedFrameSize(self)
self.subviews.edit.frame.t = mh+1
return mw, mh+2 return mw, mh+2
end end
function InputBox:onRenderBody(dc)
InputBox.super.onRenderBody(self, dc)
dc:newline(1)
dc:pen(self.input_pen or COLOR_LIGHTCYAN)
dc:fill(1,dc:localY(),dc.width-2,dc:localY())
local cursor = '_'
if math.floor(dfhack.getTickCount()/300) % 2 == 0 then
cursor = ' '
end
local txt = self.input .. cursor
if #txt > dc.width-2 then
txt = string.char(27)..string.sub(txt, #txt-dc.width+4)
end
dc:string(txt)
end
function InputBox:onInput(keys) function InputBox:onInput(keys)
if keys.SELECT then if keys.SELECT then
self:dismiss() self:dismiss()
if self.on_input then if self.on_input then
self.on_input(self.input) self.on_input(self.subviews.edit.text)
end end
elseif keys.LEAVESCREEN then elseif keys.LEAVESCREEN then
self:dismiss() self:dismiss()
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
elseif keys._STRING then else
if keys._STRING == 0 then self:inputToSubviews(keys)
self.input = string.sub(self.input, 1, #self.input-1)
else
self.input = self.input .. string.char(keys._STRING)
end
end end
end end
@ -171,97 +149,92 @@ ListBox = defclass(ListBox, MessageBox)
ListBox.focus_path = 'ListBox' ListBox.focus_path = 'ListBox'
ListBox.ATTRS{ ListBox.ATTRS{
selection = 0, with_filter = false,
choices = {}, cursor_pen = DEFAULT_NIL,
select_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL,
on_input = DEFAULT_NIL on_select = DEFAULT_NIL,
on_select2 = DEFAULT_NIL,
select2_hint = DEFAULT_NIL,
} }
function InputBox:preinit(info) function ListBox:preinit(info)
info.on_accept = nil info.on_accept = nil
end end
function ListBox:init(info) function ListBox:init(info)
self.page_top = 0 local spen = dfhack.pen.parse(COLOR_CYAN, self.select_pen, nil, false)
end local cpen = dfhack.pen.parse(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true)
function ListBox:getWantedFrameSize()
local mw, mh = ListBox.super.getWantedFrameSize(self)
return mw, mh+#self.choices
end
function ListBox:onRenderBody(dc)
ListBox.super.onRenderBody(self, dc)
dc:newline(1)
if self.selection>dc.height-3 then local list_widget = widgets.List
self.page_top=self.selection-(dc.height-3) if self.with_filter then
elseif self.selection<self.page_top and self.selection >0 then list_widget = widgets.FilteredList
self.page_top=self.selection-1
end end
for i,entry in ipairs(self.choices) do
if type(entry)=="table" then local on_submit2
entry=entry[1] if self.select2_hint or self.on_select2 then
end on_submit2 = function(sel, obj)
if i>self.page_top then self:dismiss()
if i == self.selection then if self.on_select2 then self.on_select2(sel, obj) end
dc:pen(self.select_pen or COLOR_LIGHTCYAN) local cb = obj.on_select2
else if cb then cb(obj, sel) end
dc:pen(self.text_pen or COLOR_GREY)
end
dc:string(entry)
dc:newline(1)
end end
end end
end
function ListBox:moveCursor(delta) self:addviews{
local newsel=self.selection+delta list_widget{
if #self.choices ~=0 then view_id = 'list',
if newsel<1 or newsel>#self.choices then selected = info.selected,
newsel=newsel % #self.choices choices = info.choices,
end icon_width = info.icon_width,
text_pen = spen,
cursor_pen = cpen,
on_submit = function(sel,obj)
self:dismiss()
if self.on_select then self.on_select(sel, obj) end
local cb = obj.on_select or obj[2]
if cb then cb(obj, sel) end
end,
on_submit2 = on_submit2,
frame = { l = 0, r = 0 },
}
}
end
function ListBox:onRenderFrame(dc,rect)
ListBox.super.onRenderFrame(self,dc,rect)
if self.select2_hint then
dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_DARKGREY)
end end
self.selection=newsel
end end
function ListBox:onInput(keys) function ListBox:getWantedFrameSize()
if keys.SELECT then local mw, mh = InputBox.super.getWantedFrameSize(self)
self:dismiss() local list = self.subviews.list
local choice=self.choices[self.selection] list.frame.t = mh+1
if self.on_input then return math.max(mw, list:getContentWidth()), mh+1+math.min(20,list:getContentHeight())
self.on_input(self.selection,choice) end
end
if choice and choice[2] then function ListBox:onInput(keys)
choice[2](choice,self.selection) -- maybe reverse the arguments? if keys.LEAVESCREEN then
end
elseif keys.LEAVESCREEN then
self:dismiss() self:dismiss()
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
elseif keys.CURSOR_UP then else
self:moveCursor(-1) self:inputToSubviews(keys)
elseif keys.CURSOR_DOWN then
self:moveCursor(1)
elseif keys.CURSOR_UP_FAST then
self:moveCursor(-10)
elseif keys.CURSOR_DOWN_FAST then
self:moveCursor(10)
end end
end end
function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter)
ListBox{ ListBox{
frame_title = title, frame_title = title,
text = text, text = text,
text_pen = tcolor, text_pen = tcolor,
choices = choices, choices = choices,
on_input = on_input, on_select = on_select,
on_cancel = on_cancel, on_cancel = on_cancel,
frame_width = min_width, frame_width = min_width,
with_filter = filter,
}:show() }:show()
end end

@ -238,6 +238,19 @@ local function get_movement_delta(key, delta, big_step)
end end
end end
HOTKEY_KEYS = {}
for i,v in ipairs(df.global.ui.main.hotkeys) do
HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v
end
local function get_hotkey_target(key)
local hk = HOTKEY_KEYS[key]
if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then
return xyz2pos(hk.x, hk.y, hk.z)
end
end
function Viewport:scrollByKey(key) function Viewport:scrollByKey(key)
local dx, dy, dz = get_movement_delta(key, 10, 20) local dx, dy, dz = get_movement_delta(key, 10, 20)
if dx then if dx then
@ -247,14 +260,20 @@ function Viewport:scrollByKey(key)
self.z + dz self.z + dz
) )
else else
return self local hk_pos = get_hotkey_target(key)
if hk_pos then
return self:centerOn(hk_pos)
else
return self
end
end end
end end
DwarfOverlay = defclass(DwarfOverlay, gui.Screen) DwarfOverlay = defclass(DwarfOverlay, gui.Screen)
function DwarfOverlay:updateLayout() function DwarfOverlay:updateLayout(parent_rect)
self.df_layout = getPanelLayout() self.df_layout = getPanelLayout()
DwarfOverlay.super.updateLayout(self, parent_rect)
end end
function DwarfOverlay:getViewport(old_vp) function DwarfOverlay:getViewport(old_vp)
@ -285,9 +304,11 @@ function DwarfOverlay:selectBuilding(building,cursor,viewport,gap)
end end
function DwarfOverlay:propagateMoveKeys(keys) function DwarfOverlay:propagateMoveKeys(keys)
for code,_ in pairs(MOVEMENT_KEYS) do for code,_ in pairs(keys) do
if keys[code] then if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then
self:sendInputToParent(code) if not HOTKEY_KEYS[code] or get_hotkey_target(code) then
self:sendInputToParent(code)
end
return code return code
end end
end end
@ -304,8 +325,8 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor)
return 'A_MOVE_SAME_SQUARE' return 'A_MOVE_SAME_SQUARE'
end end
for code,_ in pairs(MOVEMENT_KEYS) do for code,_ in pairs(keys) do
if keys[code] then if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then
local vp = self:getViewport():scrollByKey(code) local vp = self:getViewport():scrollByKey(code)
if (cursor and not no_clip_cursor) or no_clip_cursor == false then if (cursor and not no_clip_cursor) or no_clip_cursor == false then
vp = vp:reveal(anchor,4,20,4,true) vp = vp:reveal(anchor,4,20,4,true)
@ -328,37 +349,46 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor)
return 'A_MOVE_SAME_SQUARE' return 'A_MOVE_SAME_SQUARE'
end end
for code,_ in pairs(MOVEMENT_KEYS) do for code,_ in pairs(keys) do
if keys[code] then if MOVEMENT_KEYS[code] then
local dx, dy, dz = get_movement_delta(code, 1, 10) local dx, dy, dz = get_movement_delta(code, 1, 10)
local ncur = xyz2pos(cx+dx, cy+dy, cz+dz) local ncur = xyz2pos(cx+dx, cy+dy, cz+dz)
if dfhack.maps.isValidTilePos(ncur) then if dfhack.maps.isValidTilePos(ncur) then
setCursorPos(ncur) setCursorPos(ncur)
self:getViewport():reveal(ncur,4,10,6,true):set() self:getViewport():reveal(ncur,4,10,6,true):set()
return code
end end
return code
elseif HOTKEY_KEYS[code] then
local pos = get_hotkey_target(code)
if pos and dfhack.maps.isValidTilePos(pos) then
setCursorPos(pos)
self:getViewport():centerOn(pos):set()
end
return code
end end
end end
end end
function DwarfOverlay:onAboutToShow(below) function DwarfOverlay:onAboutToShow(parent)
local screen = dfhack.gui.getCurViewscreen() if not df.viewscreen_dwarfmodest:is_instance(parent) then
if below then screen = below.parent end
if not df.viewscreen_dwarfmodest:is_instance(screen) then
error("This screen requires the main dwarfmode view") error("This screen requires the main dwarfmode view")
end end
end end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay) MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
function MenuOverlay:updateLayout() MenuOverlay.ATTRS {
MenuOverlay.super.updateLayout(self) frame_inset = 0,
self.frame_rect = self.df_layout.menu frame_background = CLEAR_PEN,
end }
MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize function MenuOverlay:computeFrame(parent_rect)
MenuOverlay.getMousePos = gui.FramedScreen.getMousePos return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset)
end
function MenuOverlay:onAboutToShow(below) function MenuOverlay:onAboutToShow(below)
MenuOverlay.super.onAboutToShow(self,below) MenuOverlay.super.onAboutToShow(self,below)
@ -369,7 +399,7 @@ function MenuOverlay:onAboutToShow(below)
end end
end end
function MenuOverlay:onRender() function MenuOverlay:render(dc)
self:renderParent() self:renderParent()
local menu = self.df_layout.menu local menu = self.df_layout.menu
@ -380,7 +410,11 @@ function MenuOverlay:onRender()
menu.x1+1, menu.y2+1, "DFHack" menu.x1+1, menu.y2+1, "DFHack"
) )
self:onRenderBody(gui.Painter.new(menu)) if self.frame_background then
dc:fill(menu, self.frame_background)
end
MenuOverlay.super.render(self, dc)
end end
end end

@ -0,0 +1,337 @@
-- Stock dialog for selecting materials
local _ENV = mkmodule('gui.materials')
local gui = require('gui')
local widgets = require('gui.widgets')
local dlg = require('gui.dialogs')
local utils = require('utils')
ARROW = string.char(26)
CREATURE_BASE = 19
PLANT_BASE = 419
MaterialDialog = defclass(MaterialDialog, gui.FramedScreen)
MaterialDialog.focus_path = 'MaterialDialog'
MaterialDialog.ATTRS{
prompt = 'Type or select a material from this list',
frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
frame_title = 'Select Material',
-- new attrs
none_caption = 'none',
hide_none = false,
use_inorganic = true,
use_creature = true,
use_plant = true,
mat_filter = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_cancel = DEFAULT_NIL,
on_close = DEFAULT_NIL,
}
function MaterialDialog:init(info)
self:addviews{
widgets.Label{
text = {
self.prompt, '\n\n',
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
},
text_pen = COLOR_WHITE,
frame = { l = 0, t = 0 },
},
widgets.Label{
view_id = 'back',
visible = false,
text = { { key = 'LEAVESCREEN', text = ': Back' } },
frame = { r = 0, b = 0 },
auto_width = true,
},
widgets.FilteredList{
view_id = 'list',
not_found_label = 'No matching materials',
frame = { l = 0, r = 0, t = 4, b = 2 },
icon_width = 2,
on_submit = self:callback('onSubmitItem'),
},
widgets.Label{
text = { {
key = 'SELECT', text = ': Select',
disabled = function() return not self.subviews.list:canSubmit() end
} },
frame = { l = 0, b = 0 },
}
}
self:initBuiltinMode()
end
function MaterialDialog:getWantedFrameSize(rect)
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
end
function MaterialDialog:onDestroy()
if self.on_close then
self.on_close()
end
end
function MaterialDialog:initBuiltinMode()
local choices = {}
if not self.hide_none then
table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 })
end
if self.use_inorganic then
table.insert(choices, {
icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I',
cb = self:callback('initInorganicMode')
})
end
if self.use_creature then
table.insert(choices, {
icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C',
cb = self:callback('initCreatureMode')
})
end
if self.use_plant then
table.insert(choices, {
icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P',
cb = self:callback('initPlantMode')
})
end
local table = df.global.world.raws.mat_table.builtin
for i=0,df.builtin_mats._last_item do
self:addMaterial(choices, table[i], i, -1, false, nil)
end
self:pushContext('Any material', choices)
end
function MaterialDialog:initInorganicMode()
local choices = {}
for i,mat in ipairs(df.global.world.raws.inorganics) do
self:addMaterial(choices, mat.material, 0, i, false, mat)
end
self:pushContext('Inorganic materials', choices)
end
function MaterialDialog:initCreatureMode()
local choices = {}
for i,v in ipairs(df.global.world.raws.creatures.all) do
self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i)
end
self:pushContext('Creature materials', choices)
end
function MaterialDialog:initPlantMode()
local choices = {}
for i,v in ipairs(df.global.world.raws.plants.all) do
self:addObjectChoice(choices, v, v.name, PLANT_BASE, i)
end
self:pushContext('Plant materials', choices)
end
function MaterialDialog:addObjectChoice(choices, obj, name, typ, index)
-- Check if any eligible children
local count = #obj.material
local idx = 0
if self.mat_filter then
count = 0
for i,v in ipairs(obj.material) do
if self.mat_filter(v, obj, typ+i, index) then
count = count + 1
if count > 1 then break end
idx = i
end
end
end
-- Create an entry
if count < 1 then
return
elseif count == 1 then
self:addMaterial(choices, obj.material[idx], typ+idx, index, true, obj)
else
table.insert(choices, {
icon = ARROW, text = name, mat_type = typ, mat_index = index,
obj = obj, cb = self:callback('onSelectObj')
})
end
end
function MaterialDialog:onSelectObj(item)
local choices = {}
for i,v in ipairs(item.obj.material) do
self:addMaterial(choices, v, item.mat_type+i, item.mat_index, false, item.obj)
end
self:pushContext(item.text, choices)
end
function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix, parent)
-- Check the filter
if self.mat_filter and not self.mat_filter(mat, parent, typ, idx) then
return
end
-- Find the material name
local state = 0
if mat.heat.melting_point <= 10015 then
state = 1
end
local name = mat.state_name[state]
name = string.gsub(name, '^frozen ','')
name = string.gsub(name, '^molten ','')
name = string.gsub(name, '^condensed ','')
-- Add prefix if requested
local key
if pfix and mat.prefix ~= '' then
name = mat.prefix .. ' ' .. name
key = mat.prefix
end
table.insert(choices, {
text = name,
search_key = key,
material = mat,
mat_type = typ, mat_index = idx
})
end
function MaterialDialog:pushContext(name, choices)
if not self.back_stack then
self.back_stack = {}
self.subviews.back.visible = false
else
table.insert(self.back_stack, {
context_str = self.context_str,
all_choices = self.subviews.list:getChoices(),
edit_text = self.subviews.list:getFilter(),
selected = self.subviews.list:getSelected(),
})
self.subviews.back.visible = true
end
self.context_str = name
self.subviews.list:setChoices(choices, 1)
end
function MaterialDialog:onGoBack()
local save = table.remove(self.back_stack)
self.subviews.back.visible = (#self.back_stack > 0)
self.context_str = save.context_str
self.subviews.list:setChoices(save.all_choices)
self.subviews.list:setFilter(save.edit_text, save.selected)
end
function MaterialDialog:submitMaterial(typ, index)
self:dismiss()
if self.on_select then
self.on_select(typ, index)
end
end
function MaterialDialog:onSubmitItem(idx, item)
if item.cb then
item:cb(idx)
else
self:submitMaterial(item.mat_type, item.mat_index)
end
end
function MaterialDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
self:onGoBack()
else
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
end
end
function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter)
MaterialDialog{
frame_title = title,
prompt = prompt,
mat_filter = mat_filter,
on_select = on_select,
on_cancel = on_cancel,
}:show()
end
function ItemTypeDialog(args)
args.text = args.prompt or 'Type or select an item type'
args.text_pen = COLOR_WHITE
args.with_filter = true
args.icon_width = 2
local choices = {}
if not args.hide_none then
table.insert(choices, {
icon = '?', text = args.none_caption or 'none',
item_type = -1, item_subtype = -1
})
end
local filter = args.item_filter
for itype = 0,df.item_type._last_item do
local attrs = df.item_type.attrs[itype]
local defcnt = dfhack.items.getSubtypeCount(itype)
if not filter or filter(itype,-1) then
local name = attrs.caption or df.item_type[itype]
local icon
if defcnt >= 0 then
name = 'any '..name
icon = '+'
end
table.insert(choices, {
icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1
})
end
if defcnt > 0 then
for subtype = 0,defcnt-1 do
local def = dfhack.items.getSubtypeDef(itype, subtype)
if not filter or filter(itype,subtype,def) then
table.insert(choices, {
icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype
})
end
end
end
end
args.choices = choices
if args.on_select then
local cb = args.on_select
args.on_select = function(idx, obj)
return cb(obj.item_type, obj.item_subtype)
end
end
return dlg.ListBox(args)
end
return _ENV

@ -0,0 +1,164 @@
-- Support for scripted interaction sequences via coroutines.
local _ENV = mkmodule('gui.script')
local dlg = require('gui.dialogs')
--[[
Example:
start(function()
sleep(100, 'frames')
print(showYesNoPrompt('test', 'print true?'))
end)
]]
-- Table of running background scripts.
if not scripts then
scripts = {}
setmetatable(scripts, { __mode = 'k' })
end
local function do_resume(inst, ...)
inst.gen = inst.gen + 1
return (dfhack.saferesume(inst.coro, ...))
end
-- Starts a new background script by calling the function.
function start(fn,...)
local coro = coroutine.create(fn)
local inst = {
coro = coro,
gen = 0,
}
scripts[coro] = inst
return do_resume(inst, ...)
end
-- Checks if called from a background script
function in_script()
return scripts[coroutine.running()] ~= nil
end
local function getinst()
local inst = scripts[coroutine.running()]
if not inst then
error('Not in a gui script coroutine.')
end
return inst
end
local function invoke_resume(inst,gen,quiet,...)
local state = coroutine.status(inst.coro)
if state ~= 'suspended' then
if state ~= 'dead' then
dfhack.printerr(debug.traceback('resuming a non-waiting continuation'))
end
elseif inst.gen > gen then
if not quiet then
dfhack.printerr(debug.traceback('resuming an expired continuation'))
end
else
do_resume(inst, ...)
end
end
-- Returns a callback that resumes the script from wait with given return values
function mkresume(...)
local inst = getinst()
return curry(invoke_resume, inst, inst.gen, false, ...)
end
-- Like mkresume, but does not complain if already resumed from this wait
function qresume(...)
local inst = getinst()
return curry(invoke_resume, inst, inst.gen, true, ...)
end
-- Wait until a mkresume callback is called, then return its arguments.
-- Once it returns, all mkresume callbacks created before are invalidated.
function wait()
getinst() -- check that the context is right
return coroutine.yield()
end
-- Wraps dfhack.timeout for coroutines.
function sleep(time, quantity)
if dfhack.timeout(time, quantity, mkresume()) then
wait()
return true
else
return false
end
end
-- Some dialog wrappers:
function showMessage(title, text, tcolor)
dlg.MessageBox{
frame_title = title,
text = text,
text_pen = tcolor,
on_close = qresume(nil)
}:show()
return wait()
end
function showYesNoPrompt(title, text, tcolor)
dlg.MessageBox{
frame_title = title,
text = text,
text_pen = tcolor,
on_accept = mkresume(true),
on_cancel = mkresume(false),
on_close = qresume(nil)
}:show()
return wait()
end
function showInputPrompt(title, text, tcolor, input, min_width)
dlg.InputBox{
frame_title = title,
text = text,
text_pen = tcolor,
input = input,
frame_width = min_width,
on_input = mkresume(true),
on_cancel = mkresume(false),
on_close = qresume(nil)
}:show()
return wait()
end
function showListPrompt(title, text, tcolor, choices, min_width, filter)
dlg.ListBox{
frame_title = title,
text = text,
text_pen = tcolor,
choices = choices,
frame_width = min_width,
with_filter = filter,
on_select = mkresume(true),
on_cancel = mkresume(false),
on_close = qresume(nil)
}:show()
return wait()
end
function showMaterialPrompt(title, prompt)
require('gui.materials').MaterialDialog{
frame_title = title,
prompt = prompt,
on_select = mkresume(true,
on_cancel = mkresume(false),
on_close = qresume(nil)
}:show()
return wait()
end
return _ENV

@ -0,0 +1,783 @@
-- Simple widgets for screens
local _ENV = mkmodule('gui.widgets')
local gui = require('gui')
local utils = require('utils')
local dscreen = dfhack.screen
local function show_view(view,vis)
if view then
view.visible = vis
end
end
local function getval(obj)
if type(obj) == 'function' then
return obj()
else
return obj
end
end
local function map_opttab(tab,idx)
if tab then
return tab[idx]
else
return idx
end
end
------------
-- Widget --
------------
Widget = defclass(Widget, gui.View)
Widget.ATTRS {
frame = DEFAULT_NIL,
frame_inset = DEFAULT_NIL,
frame_background = DEFAULT_NIL,
}
function Widget:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset)
end
function Widget:onRenderFrame(dc, rect)
if self.frame_background then
dc:fill(rect, self.frame_background)
end
end
-----------
-- Panel --
-----------
Panel = defclass(Panel, Widget)
Panel.ATTRS {
on_render = DEFAULT_NIL,
on_layout = DEFAULT_NIL,
}
function Panel:init(args)
self:addviews(args.subviews)
end
function Panel:onRenderBody(dc)
if self.on_render then self.on_render(dc) end
end
function Panel:postComputeFrame(body)
if self.on_layout then self.on_layout(body) end
end
-----------
-- Pages --
-----------
Pages = defclass(Pages, Panel)
function Pages:init(args)
for _,v in ipairs(self.subviews) do
v.visible = false
end
self:setSelected(args.selected or 1)
end
function Pages:setSelected(idx)
if type(idx) ~= 'number' then
local key = idx
if type(idx) == 'string' then
key = self.subviews[key]
end
idx = utils.linear_index(self.subviews, key)
if not idx then
error('Unknown page: '..key)
end
end
show_view(self.subviews[self.selected], false)
self.selected = math.min(math.max(1, idx), #self.subviews)
show_view(self.subviews[self.selected], true)
end
function Pages:getSelected()
return self.selected, self.subviews[self.selected]
end
----------------
-- Edit field --
----------------
EditField = defclass(EditField, Widget)
EditField.ATTRS{
text = '',
text_pen = DEFAULT_NIL,
on_char = DEFAULT_NIL,
on_change = DEFAULT_NIL,
on_submit = DEFAULT_NIL,
}
function EditField:onRenderBody(dc)
dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0)
local cursor = '_'
if not self.active or gui.blink_visible(300) then
cursor = ' '
end
local txt = self.text .. cursor
if #txt > dc.width then
txt = string.char(27)..string.sub(txt, #txt-dc.width+2)
end
dc:string(txt)
end
function EditField:onInput(keys)
if self.on_submit and keys.SELECT then
self.on_submit(self.text)
return true
elseif keys._STRING then
local old = self.text
if keys._STRING == 0 then
self.text = string.sub(old, 1, #old-1)
else
local cv = string.char(keys._STRING)
if not self.on_char or self.on_char(cv, old) then
self.text = old .. cv
end
end
if self.on_change and self.text ~= old then
self.on_change(self.text, old)
end
return true
end
end
-----------
-- Label --
-----------
function parse_label_text(obj)
local text = obj.text or {}
if type(text) ~= 'table' then
text = { text }
end
local curline = nil
local out = { }
local active = nil
local idtab = nil
for _,v in ipairs(text) do
local vv
if type(v) == 'string' then
vv = utils.split_string(v, NEWLINE)
else
vv = { v }
end
for i = 1,#vv do
local cv = vv[i]
if i > 1 then
if not curline then
table.insert(out, {})
end
curline = nil
end
if cv ~= '' then
if not curline then
curline = {}
table.insert(out, curline)
end
if type(cv) == 'string' then
table.insert(curline, { text = cv })
else
table.insert(curline, cv)
if cv.on_activate then
active = active or {}
table.insert(active, cv)
end
if cv.id then
idtab = idtab or {}
idtab[cv.id] = cv
end
end
end
end
end
obj.text_lines = out
obj.text_active = active
obj.text_ids = idtab
end
local function is_disabled(token)
return (token.disabled ~= nil and getval(token.disabled)) or
(token.enabled ~= nil and not getval(token.enabled))
end
function render_text(obj,dc,x0,y0,pen,dpen,disabled)
local width = 0
for iline,line in ipairs(obj.text_lines) do
local x = 0
if dc then
dc:seek(x+x0,y0+iline-1)
end
for _,token in ipairs(line) do
token.line = iline
token.x1 = x
if token.gap then
x = x + token.gap
if dc then
dc:advance(token.gap)
end
end
if token.tile then
x = x + 1
if dc then
dc:char(nil, token.tile)
end
end
if token.text or token.key then
local text = ''..(getval(token.text) or '')
local keypen
if dc then
local tpen = getval(token.pen)
if disabled or is_disabled(token) then
dc:pen(getval(token.dpen) or tpen or dpen)
keypen = COLOR_GREEN
else
dc:pen(tpen or pen)
keypen = COLOR_LIGHTGREEN
end
end
local width = getval(token.width)
local padstr
if width then
x = x + width
if #text > width then
text = string.sub(text,1,width)
else
if token.pad_char then
padstr = string.rep(token.pad_char,width-#text)
end
if dc and token.rjustify then
if padstr then dc:string(padstr) else dc:advance(width-#text) end
end
end
else
x = x + #text
end
if token.key then
local keystr = gui.getKeyDisplay(token.key)
local sep = token.key_sep or ''
x = x + #keystr
if sep == '()' then
if dc then
dc:string(text)
dc:string(' ('):string(keystr,keypen):string(')')
end
x = x + 3
else
if dc then
dc:string(keystr,keypen):string(sep):string(text)
end
x = x + #sep
end
else
if dc then
dc:string(text)
end
end
if width and dc and not token.rjustify then
if padstr then dc:string(padstr) else dc:advance(width-#text) end
end
end
token.x2 = x
end
width = math.max(width, x)
end
obj.text_width = width
end
function check_text_keys(self, keys)
if self.text_active then
for _,item in ipairs(self.text_active) do
if item.key and keys[item.key] and not is_disabled(item) then
item.on_activate()
return true
end
end
end
end
Label = defclass(Label, Widget)
Label.ATTRS{
text_pen = COLOR_WHITE,
text_dpen = COLOR_DARKGREY,
disabled = DEFAULT_NIL,
enabled = DEFAULT_NIL,
auto_height = true,
auto_width = false,
}
function Label:init(args)
self:setText(args.text)
end
function Label:setText(text)
self.text = text
parse_label_text(self)
if self.auto_height then
self.frame = self.frame or {}
self.frame.h = self:getTextHeight()
end
end
function Label:preUpdateLayout()
if self.auto_width then
self.frame = self.frame or {}
self.frame.w = self:getTextWidth()
end
end
function Label:itemById(id)
if self.text_ids then
return self.text_ids[id]
end
end
function Label:getTextHeight()
return #self.text_lines
end
function Label:getTextWidth()
render_text(self)
return self.text_width
end
function Label:onRenderBody(dc)
render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self))
end
function Label:onInput(keys)
if not is_disabled(self) then
return check_text_keys(self, keys)
end
end
----------
-- List --
----------
List = defclass(List, Widget)
STANDARDSCROLL = {
STANDARDSCROLL_UP = -1,
STANDARDSCROLL_DOWN = 1,
STANDARDSCROLL_PAGEUP = '-page',
STANDARDSCROLL_PAGEDOWN = '+page',
}
SECONDSCROLL = {
SECONDSCROLL_UP = -1,
SECONDSCROLL_DOWN = 1,
SECONDSCROLL_PAGEUP = '-page',
SECONDSCROLL_PAGEDOWN = '+page',
}
List.ATTRS{
text_pen = COLOR_CYAN,
cursor_pen = COLOR_LIGHTCYAN,
inactive_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_submit = DEFAULT_NIL,
on_submit2 = DEFAULT_NIL,
row_height = 1,
scroll_keys = STANDARDSCROLL,
icon_width = DEFAULT_NIL,
}
function List:init(info)
self.page_top = 1
self.page_size = 1
if info.choices then
self:setChoices(info.choices, info.selected)
else
self.choices = {}
self.selected = 1
end
end
function List:setChoices(choices, selected)
self.choices = choices or {}
for i,v in ipairs(self.choices) do
if type(v) ~= 'table' then
v = { text = v }
self.choices[i] = v
end
v.text = v.text or v.caption or v[1]
parse_label_text(v)
end
self:setSelected(selected)
end
function List:setSelected(selected)
self.selected = selected or self.selected or 1
self:moveCursor(0, true)
return self.selected
end
function List:getChoices()
return self.choices
end
function List:getSelected()
if #self.choices > 0 then
return self.selected, self.choices[self.selected]
end
end
function List:getContentWidth()
local width = 0
for i,v in ipairs(self.choices) do
render_text(v)
local roww = v.text_width
if v.key then
roww = roww + 3 + #gui.getKeyDisplay(v.key)
end
width = math.max(width, roww)
end
return width + (self.icon_width or 0)
end
function List:getContentHeight()
return #self.choices * self.row_height
end
function List:postComputeFrame(body)
self.page_size = math.max(1, math.floor(body.height / self.row_height))
self:moveCursor(0)
end
function List:moveCursor(delta, force_cb)
local page = math.max(1, self.page_size)
local cnt = #self.choices
if cnt < 1 then
self.page_top = 1
self.selected = 1
if force_cb and self.on_select then
self.on_select(nil,nil)
end
return
end
local off = self.selected+delta-1
local ds = math.abs(delta)
if ds > 1 then
if off >= cnt+ds-1 then
off = 0
else
off = math.min(cnt-1, off)
end
if off <= -ds then
off = cnt-1
else
off = math.max(0, off)
end
end
self.selected = 1 + off % cnt
self.page_top = 1 + page * math.floor((self.selected-1) / page)
if (force_cb or delta ~= 0) and self.on_select then
self.on_select(self:getSelected())
end
end
function List:onRenderBody(dc)
local choices = self.choices
local top = self.page_top
local iend = math.min(#choices, top+self.page_size-1)
local iw = self.icon_width
local function paint_icon(icon, obj)
if type(icon) ~= 'string' then
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
for i = top,iend do
local obj = choices[i]
local current = (i == self.selected)
local cur_pen = self.cursor_pen
local cur_dpen = self.text_pen
if not self.active then
cur_pen = self.inactive_pen or self.cursor_pen
end
local y = (i - top)*self.row_height
local icon = getval(obj.icon)
if iw and icon then
dc:seek(0, y)
paint_icon(icon, obj)
end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
local ip = dc.width
if obj.key then
local keystr = gui.getKeyDisplay(obj.key)
ip = ip-2-#keystr
dc:seek(ip,y):pen(self.text_pen)
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
end
if icon and not iw then
dc:seek(ip-1,y)
paint_icon(icon, obj)
end
end
end
function List:submit()
if self.on_submit and #self.choices > 0 then
self.on_submit(self:getSelected())
end
end
function List:submit2()
if self.on_submit2 and #self.choices > 0 then
self.on_submit2(self:getSelected())
end
end
function List:onInput(keys)
if self.on_submit and keys.SELECT then
self:submit()
return true
elseif self.on_submit2 and keys.SEC_SELECT then
self:submit2()
return true
else
for k,v in pairs(self.scroll_keys) do
if keys[k] then
if v == '+page' then
v = self.page_size
elseif v == '-page' then
v = -self.page_size
end
self:moveCursor(v)
return true
end
end
for i,v in ipairs(self.choices) do
if v.key and keys[v.key] then
self:setSelected(i)
self:submit()
return true
end
end
local current = self.choices[self.selected]
if current then
return check_text_keys(current, keys)
end
end
end
-------------------
-- Filtered List --
-------------------
FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS {
edit_below = false,
}
function FilteredList:init(info)
self.edit = EditField{
text_pen = info.edit_pen or info.cursor_pen,
frame = { l = info.icon_width, t = 0, h = 1 },
on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'),
}
self.list = List{
frame = { t = 2 },
text_pen = info.text_pen,
cursor_pen = info.cursor_pen,
inactive_pen = info.inactive_pen,
icon_pen = info.icon_pen,
row_height = info.row_height,
scroll_keys = info.scroll_keys,
icon_width = info.icon_width,
}
if self.edit_below then
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
self.list.frame = { t = 0, b = 2 }
end
if info.on_select then
self.list.on_select = function()
return info.on_select(self:getSelected())
end
end
if info.on_submit then
self.list.on_submit = function()
return info.on_submit(self:getSelected())
end
end
if info.on_submit2 then
self.list.on_submit2 = function()
return info.on_submit2(self:getSelected())
end
end
self.not_found = Label{
visible = true,
text = info.not_found_label or 'No matches',
text_pen = COLOR_LIGHTRED,
frame = { l = info.icon_width, t = self.list.frame.t },
}
self:addviews{ self.edit, self.list, self.not_found }
if info.choices then
self:setChoices(info.choices, info.selected)
else
self.choices = {}
end
end
function FilteredList:getChoices()
return self.choices
end
function FilteredList:setChoices(choices, pos)
choices = choices or {}
self.choices = choices
self.edit.text = ''
self.list:setChoices(choices, pos)
self.not_found.visible = (#choices == 0)
end
function FilteredList:submit()
return self.list:submit()
end
function FilteredList:submit2()
return self.list:submit2()
end
function FilteredList:canSubmit()
return not self.not_found.visible
end
function FilteredList:getSelected()
local i,v = self.list:getSelected()
if i then
return map_opttab(self.choice_index, i), v
end
end
function FilteredList:getContentWidth()
return self.list:getContentWidth()
end
function FilteredList:getContentHeight()
return self.list:getContentHeight() + 2
end
function FilteredList:getFilter()
return self.edit.text, self.list.choices
end
function FilteredList:setFilter(filter, pos)
local choices = self.choices
local cidx = nil
filter = filter or ''
self.edit.text = filter
if filter ~= '' then
local tokens = utils.split_string(filter, ' ')
local ipos = pos
choices = {}
cidx = {}
pos = nil
for i,v in ipairs(self.choices) do
local ok = true
local search_key = v.search_key or v.text
for _,key in ipairs(tokens) do
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
ok = false
break
end
end
if ok then
table.insert(choices, v)
cidx[#choices] = i
if ipos == i then
pos = #choices
end
end
end
end
self.choice_index = cidx
self.list:setChoices(choices, pos)
self.not_found.visible = (#choices == 0)
end
function FilteredList:onFilterChange(text)
self:setFilter(text)
end
local bad_chars = {
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
['['] = true, [']'] = true, ['('] = true, [')'] = true,
}
function FilteredList:onFilterChar(char, text)
if bad_chars[char] then
return false
end
if char == ' ' then
return string.match(text, '%S$')
end
return true
end
return _ENV

@ -24,7 +24,7 @@ function CheckedArray:__len()
return self.count return self.count
end end
function CheckedArray:__index(idx) function CheckedArray:__index(idx)
if type(idx) == number then if type(idx) == "number" then
if idx >= self.count then if idx >= self.count then
error('Index out of bounds: '..tostring(idx)) error('Index out of bounds: '..tostring(idx))
end end
@ -195,6 +195,26 @@ function MemoryArea:delete()
for k,v in pairs(self) do self[k] = nil end for k,v in pairs(self) do self[k] = nil end
end end
-- Static code segment search
function get_code_segment()
local cstart, cend
for i,mem in ipairs(dfhack.internal.getMemRanges()) do
if mem.read and mem.execute
and (string.match(mem.name,'/dwarfort%.exe$')
or string.match(mem.name,'/Dwarf_Fortress$')
or string.match(mem.name,'Dwarf Fortress%.exe'))
then
cstart = mem.start_addr
cend = mem.end_addr
end
end
if cstart and cend then
return MemoryArea.new(cstart, cend)
end
end
-- Static data segment search -- Static data segment search
local function find_data_segment() local function find_data_segment()
@ -358,7 +378,7 @@ end
-- Interactive search utility -- Interactive search utility
function DiffSearcher:find_interactive(prompt,data_type,condition_cb) function DiffSearcher:find_interactive(prompt,data_type,condition_cb,iter_limit)
enum = enum or {} enum = enum or {}
-- Loop for restarting search from scratch -- Loop for restarting search from scratch
@ -374,6 +394,11 @@ function DiffSearcher:find_interactive(prompt,data_type,condition_cb)
while true do while true do
print('') print('')
if iter_limit and ccursor >= iter_limit then
dfhack.printerr(' Iteration limit reached without a solution.')
break
end
local ok, value, delta = condition_cb(ccursor) local ok, value, delta = condition_cb(ccursor)
ccursor = ccursor + 1 ccursor = ccursor + 1

@ -283,6 +283,33 @@ function clone_with_default(obj,default,force)
return rv return rv
end end
-- Parse an integer value into a bitfield table
function parse_bitfield_int(value, type_ref)
if value == 0 then
return nil
end
local res = {}
for i,v in ipairs(type_ref) do
if bit32.extract(value, i) ~= 0 then
res[v] = true
end
end
return res
end
-- List the enabled flag names in the bitfield table
function list_bitfield_flags(bitfield, list)
list = list or {}
if bitfield then
for name,val in pairs(bitfield) do
if val then
table.insert(list, name)
end
end
end
return list
end
-- Sort a vector or lua table -- Sort a vector or lua table
function sort_vector(vector,field,cmp) function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp) local fcmp = compare_field(field,cmp)
@ -302,6 +329,34 @@ function sort_vector(vector,field,cmp)
return vector return vector
end end
-- Linear search
function linear_index(vector,key,field)
local min,max
if df.isvalid(vector) then
min,max = 0,#vector-1
else
min,max = 1,#vector
end
if field then
for i=min,max do
local obj = vector[i]
if obj[field] == key then
return i, obj
end
end
else
for i=min,max do
local obj = vector[i]
if obj == key then
return i, obj
end
end
end
return nil
end
-- Binary search in a vector or lua table -- Binary search in a vector or lua table
function binsearch(vector,key,field,cmp,min,max) function binsearch(vector,key,field,cmp,min,max)
if not(min and max) then if not(min and max) then
@ -402,6 +457,7 @@ function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z) return xyz2pos(building.centerx, building.centery, building.z)
end end
-- Split the string by the given delimiter
function split_string(self, delimiter) function split_string(self, delimiter)
local result = { } local result = { }
local from = 1 local from = 1

@ -25,11 +25,16 @@ distribution.
#include "Internal.h" #include "Internal.h"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <map>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
#include <map>
using namespace std; using namespace std;
#include "ColorText.h"
#include "VersionInfo.h" #include "VersionInfo.h"
#include "MemAccess.h" #include "MemAccess.h"
#include "Types.h" #include "Types.h"
@ -77,6 +82,14 @@ using df::global::building_next_id;
using df::global::process_jobs; using df::global::process_jobs;
using df::building_def; using df::building_def;
struct CoordHash {
size_t operator()(const df::coord pos) const {
return pos.x*65537 + pos.y*17 + pos.z;
}
};
static unordered_map<df::coord, int32_t, CoordHash> locationToBuilding;
static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile)
{ {
if (!extent.extents) if (!extent.extents)
@ -178,6 +191,20 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true; return true;
} }
df::general_ref *Buildings::getGeneralRef(df::building *building, df::general_ref_type type)
{
CHECK_NULL_POINTER(building);
return findRef(building->general_refs, type);
}
df::specific_ref *Buildings::getSpecificRef(df::building *building, df::specific_ref_type type)
{
CHECK_NULL_POINTER(building);
return findRef(building->specific_refs, type);
}
bool Buildings::setOwner(df::building *bld, df::unit *unit) bool Buildings::setOwner(df::building *bld, df::unit *unit)
{ {
CHECK_NULL_POINTER(bld); CHECK_NULL_POINTER(bld);
@ -222,6 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos)
if (!occ || !occ->bits.building) if (!occ || !occ->bits.building)
return NULL; return NULL;
// Try cache lookup in case it works:
auto cached = locationToBuilding.find(pos);
if (cached != locationToBuilding.end())
{
auto building = df::building::find(cached->second);
if (building && building->z == pos.z &&
building->isSettingOccupancy() &&
containsTile(building, pos, false))
{
return building;
}
}
// The authentic method, i.e. how the game generally does this:
auto &vec = df::building::get_vector(); auto &vec = df::building::get_vector();
for (size_t i = 0; i < vec.size(); i++) for (size_t i = 0; i < vec.size(); i++)
{ {
@ -762,7 +804,7 @@ static void markBuildingTiles(df::building *bld, bool remove)
static void linkRooms(df::building *bld) static void linkRooms(df::building *bld)
{ {
auto &vec = world->buildings.other[buildings_other_id::ANY_FREE]; auto &vec = world->buildings.other[buildings_other_id::IN_PLAY];
bool changed = false; bool changed = false;
@ -895,7 +937,7 @@ static bool linkForConstruct(df::job* &job, df::building *bld)
job = new df::job(); job = new df::job();
job->job_type = df::job_type::ConstructBuilding; job->job_type = df::job_type::ConstructBuilding;
job->pos = df::coord(bld->centerx, bld->centery, bld->z); job->pos = df::coord(bld->centerx, bld->centery, bld->z);
job->references.push_back(ref); job->general_refs.push_back(ref);
bld->jobs.push_back(job); bld->jobs.push_back(job);
@ -1063,3 +1105,61 @@ bool Buildings::deconstruct(df::building *bld)
return true; return true;
} }
static unordered_map<int32_t, df::coord> corner1;
static unordered_map<int32_t, df::coord> corner2;
void Buildings::clearBuildings(color_ostream& out) {
corner1.clear();
corner2.clear();
locationToBuilding.clear();
}
void Buildings::updateBuildings(color_ostream& out, void* ptr)
{
int32_t id = (int32_t)ptr;
auto building = df::building::find(id);
if (building)
{
// Already cached -> weird, so bail out
if (corner1.count(id))
return;
// Civzones cannot be cached because they can
// overlap each other and normal buildings.
if (!building->isSettingOccupancy())
return;
df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z);
df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z);
corner1[id] = p1;
corner2[id] = p2;
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,building->z);
if (containsTile(building, pt, false))
locationToBuilding[pt] = id;
}
}
}
else if (corner1.count(id))
{
//existing building: destroy it
df::coord p1 = corner1[id];
df::coord p2 = corner2[id];
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,p1.z);
auto cur = locationToBuilding.find(pt);
if (cur != locationToBuilding.end() && cur->second == id)
locationToBuilding.erase(cur);
}
}
corner1.erase(id);
corner2.erase(id);
}
}

@ -0,0 +1,503 @@
#include "Core.h"
#include "Console.h"
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "modules/EventManager.h"
#include "modules/Job.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/construction.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
#include <map>
#include <unordered_map>
#include <unordered_set>
using namespace std;
using namespace DFHack;
using namespace EventManager;
/*
* TODO:
* error checking
* consider a typedef instead of a struct for EventHandler
**/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
multimap<uint32_t, EventHandler> tickQueue;
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[EventType::EVENT_MAX];
const uint32_t ticksPerYear = 403200;
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
}
void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( !Core::getInstance().isWorldLoaded() ) {
tick = 0;
if ( absolute ) {
Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n");
}
}
if ( absolute ) {
tick = 0;
}
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
return;
}
void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) {
for ( multimap<Plugin*, EventHandler>::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) {
if ( (*i).first != plugin )
break;
EventHandler handle = (*i).second;
if ( handle == handler ) {
handlers[e].erase(i);
break;
}
}
return;
}
void DFHack::EventManager::unregisterAll(Plugin* plugin) {
for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) {
if ( (*i).first != plugin )
break;
//shenanigans to avoid concurrent modification
EventHandler getRidOf = (*i).second;
bool didSomething;
do {
didSomething = false;
for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) {
EventHandler candidate = (*j).second;
if ( getRidOf != candidate )
continue;
tickQueue.erase(j);
didSomething = true;
break;
}
} while(didSomething);
}
for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) {
handlers[a].erase(plugin);
}
return;
}
static void manageTickEvent(color_ostream& out);
static void manageJobInitiatedEvent(color_ostream& out);
static void manageJobCompletedEvent(color_ostream& out);
static void manageUnitDeathEvent(color_ostream& out);
static void manageItemCreationEvent(color_ostream& out);
static void manageBuildingEvent(color_ostream& out);
static void manageConstructionEvent(color_ostream& out);
static void manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(color_ostream& out);
//tick event
static uint32_t lastTick = 0;
//job initiated
static int32_t lastJobId = -1;
//job completed
static unordered_map<int32_t, df::job*> prevJobs;
//unit death
static unordered_set<int32_t> livingUnits;
//item creation
static int32_t nextItem;
//building
static int32_t nextBuilding;
static unordered_set<int32_t> buildings;
//construction
static unordered_set<df::construction*> constructions;
static bool gameLoaded;
//invasion
static int32_t nextInvasion;
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false;
if ( !doOnce ) {
//TODO: put this somewhere else
doOnce = true;
EventHandler buildingHandler(Buildings::updateBuildings, 100);
DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL);
//out.print("Registered listeners.\n %d", __LINE__);
}
if ( event == DFHack::SC_WORLD_UNLOADED ) {
lastTick = 0;
lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
}
prevJobs.clear();
tickQueue.clear();
livingUnits.clear();
nextItem = -1;
nextBuilding = -1;
buildings.clear();
constructions.clear();
Buildings::clearBuildings(out);
gameLoaded = false;
nextInvasion = -1;
} else if ( event == DFHack::SC_WORLD_LOADED ) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
multimap<uint32_t,EventHandler> newTickQueue;
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) {
newTickQueue.insert(pair<uint32_t,EventHandler>(tick + (*i).first, (*i).second));
}
tickQueue.clear();
tickQueue.insert(newTickQueue.begin(), newTickQueue.end());
nextItem = 0;
nextBuilding = 0;
lastTick = 0;
nextInvasion = df::global::ui->invasions.next_id;
gameLoaded = true;
}
}
void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) {
return;
}
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( tick <= lastTick )
return;
lastTick = tick;
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
int32_t min = 1000000000;
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < min )
min = bob.freq;
}
eventFrequency[a] = min;
}
manageTickEvent(out);
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
}
return;
}
static void manageTickEvent(color_ostream& out) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
while ( !tickQueue.empty() ) {
if ( tick < (*tickQueue.begin()).first )
break;
EventHandler handle = (*tickQueue.begin()).second;
tickQueue.erase(tickQueue.begin());
handle.eventHandler(out, (void*)tick);
}
}
static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1;
return;
}
if ( lastJobId+1 == *df::global::job_next_id ) {
return; //no new jobs
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end());
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->id <= lastJobId )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)link->item);
}
}
lastJobId = *df::global::job_next_id - 1;
}
static void manageJobCompletedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
nowJobs[link->item->id] = link->item;
}
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
if ( nowJobs.find((*i).first) != nowJobs.end() )
continue;
//recently finished or cancelled job!
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second);
}
}
//erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
}
prevJobs.clear();
//create new jobs
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) {
continue;
}*/
df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob;
}
/*//get rid of old pointers to deallocated jobs
for ( size_t a = 0; a < toDelete.size(); a++ ) {
prevJobs.erase(a);
}*/
}
static void manageUnitDeathEvent(color_ostream& out) {
if ( handlers[EventType::UNIT_DEATH].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit->counters.death_id == -1 ) {
livingUnits.insert(unit->id);
continue;
}
//dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)unit->id);
}
livingUnits.erase(unit->id);
}
}
static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a];
//invaders
if ( item->flags.bits.foreign )
continue;
//traders who bring back your items?
if ( item->flags.bits.trader )
continue;
//migrants
if ( item->flags.bits.owned )
continue;
//spider webs don't count
if ( item->flags.bits.spider_web )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)item->id);
}
}
nextItem = *df::global::item_next_id;
}
static void manageBuildingEvent(color_ostream& out) {
/*
* TODO: could be faster
* consider looking at jobs: building creation / destruction
**/
if ( handlers[EventType::BUILDING].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) {
//out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a);
//the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter
continue;
}
buildings.insert(a);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)a);
}
}
nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings
unordered_set<int32_t> toDelete;
for ( auto a = buildings.begin(); a != buildings.end(); a++ ) {
int32_t id = *a;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 )
continue;
toDelete.insert(id);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)id);
}
}
for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) {
int32_t id = *a;
buildings.erase(id);
}
//out.print("Sent building event.\n %d", __LINE__);
}
static void manageConstructionEvent(color_ostream& out) {
if ( handlers[EventType::CONSTRUCTION].empty() )
return;
unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*,EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
for ( auto a = constructions.begin(); a != constructions.end(); a++ ) {
df::construction* construction = *a;
if ( constructionsNow.find(construction) != constructionsNow.end() )
continue;
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
df::construction* construction = *a;
if ( constructions.find(construction) != constructions.end() )
continue;
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
constructions.clear();
constructions.insert(constructionsNow.begin(), constructionsNow.end());
}
static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
df::unit* unit = *a;
if ( unit->flags1.bits.dead )
continue;
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
df::unit_syndrome* syndrome = unit->syndromes.active[b];
uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
if ( startTime <= eventLastTick[EventType::SYNDROME] )
continue;
SyndromeData data(unit->id, b);
for ( auto c = copy.begin(); c != copy.end(); c++ ) {
EventHandler handle = (*c).second;
handle.eventHandler(out, (void*)&data);
}
}
}
}
static void manageInvasionEvent(color_ostream& out) {
if ( handlers[EventType::INVASION].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion )
return;
nextInvasion = df::global::ui->invasions.next_id;
for ( auto a = copy.begin(); a != copy.end(); a++ ) {
EventHandler handle = (*a).second;
handle.eventHandler(out, (void*)nextInvasion);
}
}

@ -48,63 +48,63 @@ Module* DFHack::createGraphic()
struct Graphic::Private struct Graphic::Private
{ {
bool Started; bool Started;
vector<DFTileSurface* (*)(int,int)> funcs; vector<DFTileSurface* (*)(int,int)> funcs;
}; };
Graphic::Graphic() Graphic::Graphic()
{ {
d = new Private; d = new Private;
d->Started = true; d->Started = true;
} }
Graphic::~Graphic() Graphic::~Graphic()
{ {
delete d; delete d;
} }
bool Graphic::Register(DFTileSurface* (*func)(int,int)) bool Graphic::Register(DFTileSurface* (*func)(int,int))
{ {
d->funcs.push_back(func); d->funcs.push_back(func);
return true; return true;
} }
bool Graphic::Unregister(DFTileSurface* (*func)(int,int)) bool Graphic::Unregister(DFTileSurface* (*func)(int,int))
{ {
if ( d->funcs.empty() ) return false; if ( d->funcs.empty() ) return false;
vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin(); vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin();
while ( it != d->funcs.end() ) while ( it != d->funcs.end() )
{ {
if ( *it == func ) if ( *it == func )
{ {
d->funcs.erase(it); d->funcs.erase(it);
return true; return true;
} }
it++; it++;
} }
return false; return false;
} }
// This will return first DFTileSurface it can get (or NULL if theres none) // This will return first DFTileSurface it can get (or NULL if theres none)
DFTileSurface* Graphic::Call(int x, int y) DFTileSurface* Graphic::Call(int x, int y)
{ {
if ( d->funcs.empty() ) return NULL; if ( d->funcs.empty() ) return NULL;
DFTileSurface* temp = NULL; DFTileSurface* temp = NULL;
vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin(); vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin();
while ( it != d->funcs.end() ) while ( it != d->funcs.end() )
{ {
temp = (*it)(x,y); temp = (*it)(x,y);
if ( temp != NULL ) if ( temp != NULL )
{ {
return temp; return temp;
} }
it++; it++;
} }
return NULL; return NULL;
} }

@ -85,6 +85,8 @@ using namespace DFHack;
#include "df/assign_trade_status.h" #include "df/assign_trade_status.h"
#include "df/announcement_flags.h" #include "df/announcement_flags.h"
#include "df/announcements.h" #include "df/announcements.h"
#include "df/stop_depart_condition.h"
#include "df/route_stockpile_link.h"
using namespace df::enums; using namespace df::enums;
using df::global::gview; using df::global::gview;
@ -303,6 +305,45 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += "/List"; focus += "/List";
break; break;
case Hauling:
if (ui->hauling.in_assign_vehicle)
{
auto vehicle = vector_get(ui->hauling.vehicles, ui->hauling.cursor_vehicle);
focus += "/AssignVehicle/" + std::string(vehicle ? "Some" : "None");
}
else
{
int idx = ui->hauling.cursor_top;
auto route = vector_get(ui->hauling.view_routes, idx);
auto stop = vector_get(ui->hauling.view_stops, idx);
std::string tag = stop ? "Stop" : (route ? "Route" : "None");
if (ui->hauling.in_name)
focus += "/Rename/" + tag;
else if (ui->hauling.in_stop)
{
int sidx = ui->hauling.cursor_stop;
auto cond = vector_get(ui->hauling.stop_conditions, sidx);
auto link = vector_get(ui->hauling.stop_links, sidx);
focus += "/DefineStop";
if (cond)
focus += "/Cond/" + enum_item_key(cond->mode);
else if (link)
{
focus += "/Link/";
if (link->mode.bits.give) focus += "Give";
if (link->mode.bits.take) focus += "Take";
}
else
focus += "/None";
}
else
focus += "/Select/" + tag;
}
break;
default: default:
break; break;
} }

@ -109,30 +109,47 @@ using df::global::proj_next_id;
ITEM(PANTS, pants, itemdef_pantsst) \ ITEM(PANTS, pants, itemdef_pantsst) \
ITEM(FOOD, food, itemdef_foodst) ITEM(FOOD, food, itemdef_foodst)
bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_) int Items::getSubtypeCount(df::item_type itype)
{ {
using namespace df::enums::item_type; using namespace df::enums::item_type;
type = type_;
subtype = subtype_;
custom = NULL;
df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs;
switch (type_) { switch (itype) {
case NONE: #define ITEM(type,vec,tclass) \
return false; case type: \
return defs.vec.size();
ITEMDEF_VECTORS
#undef ITEM
default:
return -1;
}
}
df::itemdef *Items::getSubtypeDef(df::item_type itype, int subtype)
{
using namespace df::enums::item_type;
df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs;
switch (itype) {
#define ITEM(type,vec,tclass) \ #define ITEM(type,vec,tclass) \
case type: \ case type: \
custom = vector_get(defs.vec, subtype); \ return vector_get(defs.vec, subtype);
break;
ITEMDEF_VECTORS ITEMDEF_VECTORS
#undef ITEM #undef ITEM
default: default:
break; return NULL;
} }
}
bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_)
{
type = type_;
subtype = subtype_;
custom = Items::getSubtypeDef(type_, subtype_);
return isValid(); return isValid();
} }
@ -171,6 +188,10 @@ ITEMDEF_VECTORS
break; break;
} }
const char *name = ENUM_ATTR(item_type, caption, type);
if (name)
return name;
return toLower(ENUM_KEY_STR(item_type, type)); return toLower(ENUM_KEY_STR(item_type, type));
} }
@ -219,19 +240,51 @@ ITEMDEF_VECTORS
return (subtype >= 0); return (subtype >= 0);
} }
bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) bool Items::isCasteMaterial(df::item_type itype)
{
return ENUM_ATTR(item_type, is_caste_mat, itype);
}
bool ItemTypeInfo::matches(df::job_item_vector_id vec_id)
{
auto other_id = ENUM_ATTR(job_item_vector_id, other, vec_id);
auto explicit_item = ENUM_ATTR(items_other_id, item, other_id);
if (explicit_item != item_type::NONE && type != explicit_item)
return false;
auto generic_item = ENUM_ATTR(items_other_id, generic_item, other_id);
if (generic_item.size > 0)
{
for (size_t i = 0; i < generic_item.size; i++)
if (generic_item.items[i] == type)
return true;
return false;
}
return true;
}
bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool skip_vector)
{ {
using namespace df::enums::item_type; using namespace df::enums::item_type;
if (!isValid()) if (!isValid())
return mat ? mat->matches(item) : false; return mat ? mat->matches(item) : false;
df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; if (Items::isCasteMaterial(type) && mat && !mat->isNone())
df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; return false;
if (!skip_vector && !matches(item.vector_id))
return false;
df::job_item_flags1 ok1, mask1, item_ok1, item_mask1, xmask1;
df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2;
df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3;
ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = 0; ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = xmask1.whole = 0;
ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = 0; ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = xmask2.whole = 0;
ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0; ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0;
if (mat) { if (mat) {
@ -252,12 +305,16 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat)
RQ(1,not_bin); RQ(1,lye_bearing); RQ(1,not_bin); RQ(1,lye_bearing);
RQ(2,dye); RQ(2,dyeable); RQ(2,dyed); RQ(2,glass_making); RQ(2,screw); RQ(2,dye); RQ(2,dyeable); RQ(2,dyed); RQ(2,glass_making); RQ(2,screw);
RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe); RQ(2,non_economic); RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe);
RQ(2,totemable); RQ(2,plaster_containing); RQ(2,body_part); RQ(2,lye_milk_free); RQ(2,totemable); RQ(2,plaster_containing); RQ(2,body_part); RQ(2,lye_milk_free);
RQ(2,blunt); RQ(2,unengraved); RQ(2,hair_wool); RQ(2,blunt); RQ(2,unengraved); RQ(2,hair_wool);
RQ(3,any_raw_material); RQ(3,non_pressed); RQ(3,food_storage); RQ(3,any_raw_material); RQ(3,non_pressed); RQ(3,food_storage);
// only checked if boulder
xmask2.bits.non_economic = true;
// Compute the ok mask // Compute the ok mask
OK(1,solid); OK(1,solid);
@ -277,7 +334,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat)
case BOULDER: case BOULDER:
OK(1,sharpenable); OK(1,sharpenable);
OK(2,non_economic); xmask2.bits.non_economic = false;
case BAR: case BAR:
OK(3,any_raw_material); OK(3,any_raw_material);
case BLOCKS: case BLOCKS:
@ -305,11 +362,13 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat)
case CAGE: case CAGE:
OK(1,milk); OK(1,milk);
OK(1,milkable); OK(1,milkable);
xmask1.bits.cookable = true;
break; break;
case BUCKET: case BUCKET:
case FLASK: case FLASK:
OK(1,milk); OK(1,milk);
xmask1.bits.cookable = true;
break; break;
case TOOL: case TOOL:
@ -317,6 +376,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat)
OK(1,milk); OK(1,milk);
OK(2,lye_milk_free); OK(2,lye_milk_free);
OK(2,blunt); OK(2,blunt);
xmask1.bits.cookable = true;
if (VIRTUAL_CAST_VAR(def, df::itemdef_toolst, custom)) { if (VIRTUAL_CAST_VAR(def, df::itemdef_toolst, custom)) {
df::tool_uses key(tool_uses::FOOD_STORAGE); df::tool_uses key(tool_uses::FOOD_STORAGE);
@ -332,11 +392,13 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat)
OK(1,milk); OK(1,milk);
OK(2,lye_milk_free); OK(2,lye_milk_free);
OK(3,food_storage); OK(3,food_storage);
xmask1.bits.cookable = true;
break; break;
case BOX: case BOX:
OK(1,bag); OK(1,sand_bearing); OK(1,milk); OK(1,bag); OK(1,sand_bearing); OK(1,milk);
OK(2,dye); OK(2,plaster_containing); OK(2,dye); OK(2,plaster_containing);
xmask1.bits.cookable = true;
break; break;
case BIN: case BIN:
@ -403,6 +465,9 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat)
#undef OK #undef OK
#undef RQ #undef RQ
mask1.whole &= ~xmask1.whole;
mask2.whole &= ~xmask2.whole;
return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && return bits_match(item.flags1.whole, ok1.whole, mask1.whole) &&
bits_match(item.flags2.whole, ok2.whole, mask2.whole) && bits_match(item.flags2.whole, ok2.whole, mask2.whole) &&
bits_match(item.flags3.whole, ok3.whole, mask3.whole) && bits_match(item.flags3.whole, ok3.whole, mask3.whole) &&
@ -430,10 +495,10 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item)
item.id = itreal->id; item.id = itreal->id;
item.age = itreal->age; item.age = itreal->age;
item.flags = itreal->flags; item.flags = itreal->flags;
item.matdesc.itemType = itreal->getType(); item.matdesc.item_type = itreal->getType();
item.matdesc.subType = itreal->getSubtype(); item.matdesc.item_subtype = itreal->getSubtype();
item.matdesc.material = itreal->getMaterial(); item.matdesc.mat_type = itreal->getMaterial();
item.matdesc.index = itreal->getMaterialIndex(); item.matdesc.mat_index = itreal->getMaterialIndex();
item.wear_level = itreal->getWear(); item.wear_level = itreal->getWear();
item.quality = itreal->getQuality(); item.quality = itreal->getQuality();
item.quantity = itreal->getStackSize(); item.quantity = itreal->getStackSize();
@ -444,7 +509,7 @@ df::general_ref *Items::getGeneralRef(df::item *item, df::general_ref_type type)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
return findRef(item->itemrefs, type); return findRef(item->general_refs, type);
} }
df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type) df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type)
@ -465,9 +530,9 @@ bool Items::setOwner(df::item *item, df::unit *unit)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
for (int i = item->itemrefs.size()-1; i >= 0; i--) for (int i = item->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
if (!strict_virtual_cast<df::general_ref_unit_itemownerst>(ref)) if (!strict_virtual_cast<df::general_ref_unit_itemownerst>(ref))
continue; continue;
@ -481,7 +546,7 @@ bool Items::setOwner(df::item *item, df::unit *unit)
} }
delete ref; delete ref;
vector_erase_at(item->itemrefs, i); vector_erase_at(item->general_refs, i);
} }
item->flags.bits.owned = false; item->flags.bits.owned = false;
@ -496,7 +561,7 @@ bool Items::setOwner(df::item *item, df::unit *unit)
ref->unit_id = unit->id; ref->unit_id = unit->id;
insert_into_vector(unit->owned_items, item->id); insert_into_vector(unit->owned_items, item->id);
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
} }
return true; return true;
@ -515,9 +580,9 @@ void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
items->clear(); items->clear();
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
if (ref->getType() != general_ref_type::CONTAINS_ITEM) if (ref->getType() != general_ref_type::CONTAINS_ITEM)
continue; continue;
@ -527,6 +592,20 @@ void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
} }
} }
df::building *Items::getHolderBuilding(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::BUILDING_HOLDER);
return ref ? ref->getBuilding() : NULL;
}
df::unit *Items::getHolderUnit(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::UNIT_HOLDER);
return ref ? ref->getUnit() : NULL;
}
df::coord Items::getPosition(df::item *item) df::coord Items::getPosition(df::item *item)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
@ -538,9 +617,9 @@ df::coord Items::getPosition(df::item *item)
if (item->flags.bits.in_inventory) if (item->flags.bits.in_inventory)
{ {
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -637,9 +716,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
if (item->world_data_id != -1) if (item->world_data_id != -1)
return false; return false;
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -669,9 +748,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
{ {
bool found = false; bool found = false;
for (int i = item->itemrefs.size()-1; i >= 0; i--) for (int i = item->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -688,7 +767,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
item2->flags.bits.weight_computed = false; item2->flags.bits.weight_computed = false;
removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); removeRef(item2->general_refs, general_ref_type::CONTAINS_ITEM, item->id);
} }
break; break;
@ -720,7 +799,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
} }
found = true; found = true;
vector_erase_at(item->itemrefs, i); vector_erase_at(item->general_refs, i);
delete ref; delete ref;
} }
@ -799,10 +878,10 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
container->flags.bits.weight_computed = false; container->flags.bits.weight_computed = false;
ref1->item_id = item->id; ref1->item_id = item->id;
container->itemrefs.push_back(ref1); container->general_refs.push_back(ref1);
ref2->item_id = container->id; ref2->item_id = container->id;
item->itemrefs.push_back(ref2); item->general_refs.push_back(ref2);
return true; return true;
} }
@ -833,7 +912,7 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::
item->flags.bits.in_building=true; item->flags.bits.in_building=true;
ref->building_id=building->id; ref->building_id=building->id;
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
auto con=new df::building_actual::T_contained_items; auto con=new df::building_actual::T_contained_items;
con->item=item; con->item=item;
@ -876,7 +955,7 @@ bool DFHack::Items::moveToInventory(
unit->inventory.push_back(newInventoryItem); unit->inventory.push_back(newInventoryItem);
holderReference->unit_id = unit->id; holderReference->unit_id = unit->id;
item->itemrefs.push_back(holderReference); item->general_refs.push_back(holderReference);
resetUnitInvFlags(unit, newInventoryItem); resetUnitInvFlags(unit, newInventoryItem);
@ -937,7 +1016,7 @@ df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item)
proj->item = item; proj->item = item;
ref->projectile_id = proj->id; ref->projectile_id = proj->id;
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
linked_list_append(&world->proj_list, proj->link); linked_list_append(&world->proj_list, proj->link);

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