Merge branch 'master' of https://github.com/angavrilov/dfhack into experimental-dontmerge

develop
Warmist 2012-11-02 20:29:27 +02:00
commit 6be65690f7
180 changed files with 12923 additions and 3273 deletions

3
.gitignore vendored

@ -36,6 +36,9 @@ build/tools
build/plugins build/plugins
build/depends build/depends
build/install_manifest.txt build/install_manifest.txt
build/README.html
build/LUA_API.html
build/COMPILE.html
#ignore Kdevelop stuff #ignore Kdevelop stuff
.kdev4 .kdev4

@ -0,0 +1,3 @@
FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py)
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE)

@ -63,7 +63,7 @@ set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "11") set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${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}")
@ -145,12 +145,33 @@ include_directories(depends/clsocket/src)
add_subdirectory(depends) add_subdirectory(depends)
#find_package(Docutils)
#set (RST_FILES
#"Readme"
#"Compile"
#"LUA Api"
#"Contributors"
#)
#set (RST_PROCESSED_FILES "")
#IF(RST2HTML_EXECUTABLE)
# foreach(F ${RST_FILES})
# add_custom_command(
# OUTPUT "${dfhack_BINARY_DIR}/${F}.html"
# COMMAND ${RST2HTML_EXECUTABLE} "${dfhack_SOURCE_DIR}/${F}.rst" "${dfhack_BINARY_DIR}/${F}.html"
# COMMENT "Translating ${F} to html"
# DEPENDS "${dfhack_SOURCE_DIR}/${F}.rst")
# list (APPEND RST_PROCESSED_FILES "${dfhack_BINARY_DIR}/${F}.html")
# endforeach()
# add_custom_target(HTML_DOCS ALL DEPENDS ${RST_PROCESSED_FILES})
#ENDIF()
# build the lib itself # build the lib itself
IF(BUILD_LIBRARY) IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
## install the default documentation files ## install the default documentation files
install(FILES LICENSE Readme.html Compile.html Lua\ API.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
#build the plugins #build the plugins

@ -3,13 +3,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" /> <meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
<title>Building DFHACK</title> <title>Building DFHACK</title>
<style type="text/css"> <style type="text/css">
/* /*
:Author: David Goodger (goodger@python.org) :Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7056 2011-06-17 10:50:48Z milde $ :Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
:Copyright: This stylesheet has been placed in the public domain. :Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils. Default cascading style sheet for the HTML output of Docutils.
@ -249,10 +249,18 @@ pre.address {
margin-top: 0 ; margin-top: 0 ;
font: inherit } font: inherit }
pre.literal-block, pre.doctest-block, pre.math { pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ; margin-left: 2em ;
margin-right: 2em } margin-right: 2em }
pre.code .ln { /* line numbers */
color: grey;
}
.code {
background-color: #eeeeee
}
span.classifier { span.classifier {
font-family: sans-serif ; font-family: sans-serif ;
font-style: oblique } font-style: oblique }
@ -326,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>
@ -395,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>
@ -420,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
@ -437,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>
@ -459,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">
@ -471,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>
@ -489,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>
@ -521,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,49 @@ 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
========
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
======= =======
Windows Windows
======= =======

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
<title>Contributors</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { /* line numbers */
color: grey;
}
.code {
background-color: #eeeeee
}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="contributors">
<h1 class="title">Contributors</h1>
<p>If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)</p>
<p>The following is a list of people who have contributed to <strong>DFHack</strong>.</p>
<ul class="simple">
<li>Petr Mrázek &lt;<a class="reference external" href="mailto:peterix&#64;gmail.com">peterix&#64;gmail.com</a>&gt;</li>
<li>Alexander Gavrilov &lt;<a class="reference external" href="mailto:angavrilov&#64;gmail.com">angavrilov&#64;gmail.com</a>&gt;</li>
<li>doomchild &lt;<a class="reference external" href="mailto:lee.crabtree&#64;gmail.com">lee.crabtree&#64;gmail.com</a>&gt;</li>
<li>Quietust &lt;<a class="reference external" href="mailto:quietust&#64;gmail.com">quietust&#64;gmail.com</a>&gt;</li>
<li>jj &lt;<a class="reference external" href="mailto:john-git&#64;ofjj.net">john-git&#64;ofjj.net</a>&gt;</li>
<li>Warmist &lt;<a class="reference external" href="mailto:warmist&#64;gmail.com">warmist&#64;gmail.com</a>&gt;</li>
<li>Robert Heinrich &lt;<a class="reference external" href="mailto:robertheinrich73&#64;googlemail.com">robertheinrich73&#64;googlemail.com</a>&gt;</li>
<li>simon &lt;<a class="reference external" href="mailto:simon&#64;banquise.net">simon&#64;banquise.net</a>&gt;</li>
<li>Kelly Martin &lt;<a class="reference external" href="mailto:kelly.lynn.martin&#64;gmail.com">kelly.lynn.martin&#64;gmail.com</a>&gt;</li>
<li>mizipzor &lt;<a class="reference external" href="mailto:mizipzor&#64;gmail.com">mizipzor&#64;gmail.com</a>&gt;</li>
<li>Simon Jackson &lt;<a class="reference external" href="mailto:sizeak&#64;hotmail.com">sizeak&#64;hotmail.com</a>&gt;</li>
<li>belal &lt;<a class="reference external" href="mailto:jimbelal&#64;gmail.com">jimbelal&#64;gmail.com</a>&gt;</li>
<li>RusAnon &lt;<a class="reference external" href="mailto:rusanon&#64;dollchan.ru">rusanon&#64;dollchan.ru</a>&gt;</li>
<li>Raoul XQ &lt;<a class="reference external" href="mailto:raoulxq&#64;gmail.com">raoulxq&#64;gmail.com</a>&gt;</li>
<li>Matthew Cline &lt;<a class="reference external" href="mailto:zelgadis&#64;sourceforge.net">zelgadis&#64;sourceforge.net</a>&gt;</li>
<li>Mike Stewart &lt;<a class="reference external" href="mailto:thewonderidiot&#64;gmail.com">thewonderidiot&#64;gmail.com</a>&gt;</li>
<li>Timothy Collett &lt;<a class="reference external" href="mailto:tcollett+github&#64;topazgryphon.org">tcollett+github&#64;topazgryphon.org</a>&gt;</li>
<li>RossM &lt;<a class="reference external" href="mailto:Ross&#64;Gnome">Ross&#64;Gnome</a>&gt;</li>
<li>Tom Prince &lt;<a class="reference external" href="mailto:tom.prince&#64;ualberta.net">tom.prince&#64;ualberta.net</a>&gt;</li>
<li>Jared Adams &lt;<a class="reference external" href="mailto:jaxad0127&#64;gmail.com">jaxad0127&#64;gmail.com</a>&gt;</li>
<li>expwnent &lt;<a class="reference external" href="mailto:q309185&#64;gmail.com">q309185&#64;gmail.com</a>&gt;</li>
<li>Erik Youngren &lt;<a class="reference external" href="mailto:artanis.00&#64;gmail.com">artanis.00&#64;gmail.com</a>&gt;</li>
<li>Espen Wiborg &lt;<a class="reference external" href="mailto:espen.wiborg&#64;telio.no">espen.wiborg&#64;telio.no</a>&gt;</li>
<li>Tim Walberg &lt;<a class="reference external" href="mailto:twalberg&#64;comcast.net">twalberg&#64;comcast.net</a>&gt;</li>
<li>Mikko Juola &lt;<a class="reference external" href="mailto:mikko.juola&#64;kolumbus.fi">mikko.juola&#64;kolumbus.fi</a>&gt;</li>
<li>rampaging-poet &lt;<a class="reference external" href="mailto:yrudoingthis&#64;hotmail.com">yrudoingthis&#64;hotmail.com</a>&gt;</li>
<li>U-glouglou\simon</li>
<li>Clayton Hughes &lt;<a class="reference external" href="mailto:clayton.hughes&#64;gmail.com">clayton.hughes&#64;gmail.com</a>&gt;</li>
<li>zilpin &lt;<a class="reference external" href="mailto:ziLpin&#64;gmail.com">ziLpin&#64;gmail.com</a>&gt;</li>
<li>Will Rogers &lt;<a class="reference external" href="mailto:wjrogers&#64;gmail.com">wjrogers&#64;gmail.com</a>&gt;</li>
<li>NMLittle &lt;<a class="reference external" href="mailto:nmlittle&#64;gmail.com">nmlittle&#64;gmail.com</a>&gt;</li>
<li>root</li>
<li>reverb</li>
<li>Zhentar &lt;<a class="reference external" href="mailto:Zhentar&#64;gmail.com">Zhentar&#64;gmail.com</a>&gt;</li>
<li>Valentin Ochs &lt;<a class="reference external" href="mailto:a&#64;0au.de">a&#64;0au.de</a>&gt;</li>
<li>Priit Laes &lt;<a class="reference external" href="mailto:plaes&#64;plaes.org">plaes&#64;plaes.org</a>&gt;</li>
<li>kmartin</li>
<li>Neil Little</li>
<li>rout &lt;<a class="reference external" href="mailto:rout.mail+github&#64;gmail.com">rout.mail+github&#64;gmail.com</a>&gt;</li>
<li>rofl0r &lt;<a class="reference external" href="mailto:retnyg&#64;gmx.net">retnyg&#64;gmx.net</a>&gt;</li>
<li>harlanplayford &lt;<a class="reference external" href="mailto:harlanplayford&#64;gmail.com">harlanplayford&#64;gmail.com</a>&gt;</li>
<li>gsvslto &lt;<a class="reference external" href="mailto:gsvslto&#64;gmail.com">gsvslto&#64;gmail.com</a>&gt;</li>
<li>sami</li>
<li>potato</li>
<li>playfordh &lt;<a class="reference external" href="mailto:harlanplayford&#64;gmail.com">harlanplayford&#64;gmail.com</a>&gt;</li>
<li>feng1st &lt;<a class="reference external" href="mailto:nf_xp&#64;hotmail.com">nf_xp&#64;hotmail.com</a>&gt;</li>
<li>comestible &lt;<a class="reference external" href="mailto:nickolas.g.russell&#64;gmail.com">nickolas.g.russell&#64;gmail.com</a>&gt;</li>
<li>Rumrusher &lt;<a class="reference external" href="mailto:Anuleakage&#64;yahoo.com">Anuleakage&#64;yahoo.com</a>&gt;</li>
<li>Rinin &lt;<a class="reference external" href="mailto:RininS&#64;Gmail.com">RininS&#64;Gmail.com</a>&gt;</li>
<li>Raoul van Putten</li>
<li>John Shade &lt;<a class="reference external" href="mailto:gsvslto&#64;gmail.com">gsvslto&#64;gmail.com</a>&gt;</li>
<li>John Beisley &lt;<a class="reference external" href="mailto:greatred&#64;gmail.com">greatred&#64;gmail.com</a>&gt;</li>
<li>Feng &lt;<a class="reference external" href="mailto:nf_xp&#64;hotmail.com">nf_xp&#64;hotmail.com</a>&gt;</li>
<li>Donald Ruegsegger &lt;<a class="reference external" href="mailto:druegsegger&#64;gmail.com">druegsegger&#64;gmail.com</a>&gt;</li>
<li>Caldfir &lt;<a class="reference external" href="mailto:caldfir&#64;hotmail.com">caldfir&#64;hotmail.com</a>&gt;</li>
<li>Antalia &lt;<a class="reference external" href="mailto:tamarakorr&#64;gmail.com">tamarakorr&#64;gmail.com</a>&gt;</li>
<li>Angus Mezick &lt;<a class="reference external" href="mailto:amezick&#64;gmail.com">amezick&#64;gmail.com</a>&gt;</li>
</ul>
<p>And those are the cool people who made <strong>stonesense</strong>.</p>
<ul class="simple">
<li>Kris Parker &lt;kaypy&gt;</li>
<li>Japa &lt;<a class="reference external" href="mailto:japa.mala.illo&#64;gmail.com">japa.mala.illo&#64;gmail.com</a>&gt;</li>
<li>Jonas Ask &lt;<a class="reference external" href="mailto:jonask84&#64;gmail.com">jonask84&#64;gmail.com</a>&gt;</li>
<li>Petr Mrázek &lt;<a class="reference external" href="mailto:peterix&#64;gmail.com">peterix&#64;gmail.com</a>&gt;</li>
<li>Caldfir &lt;<a class="reference external" href="mailto:aitken.tim&#64;gmail.com">aitken.tim&#64;gmail.com</a>&gt;</li>
<li>8Z &lt;<a class="reference external" href="mailto:git8z&#64;ya.ru">git8z&#64;ya.ru</a>&gt;</li>
<li>Alexander Gavrilov &lt;<a class="reference external" href="mailto:angavrilov&#64;gmail.com">angavrilov&#64;gmail.com</a>&gt;</li>
<li>Timothy Collett &lt;<a class="reference external" href="mailto:tcollett+github&#64;topazgryphon.org">tcollett+github&#64;topazgryphon.org</a>&gt;</li>
</ul>
</div>
</body>
</html>

@ -0,0 +1,74 @@
Contributors
============
If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)
The following is a list of people who have contributed to **DFHack**.
- Petr Mrázek <peterix@gmail.com>
- Alexander Gavrilov <angavrilov@gmail.com>
- doomchild <lee.crabtree@gmail.com>
- Quietust <quietust@gmail.com>
- jj <john-git@ofjj.net>
- Warmist <warmist@gmail.com>
- Robert Heinrich <robertheinrich73@googlemail.com>
- simon <simon@banquise.net>
- Kelly Martin <kelly.lynn.martin@gmail.com>
- mizipzor <mizipzor@gmail.com>
- Simon Jackson <sizeak@hotmail.com>
- belal <jimbelal@gmail.com>
- RusAnon <rusanon@dollchan.ru>
- Raoul XQ <raoulxq@gmail.com>
- Matthew Cline <zelgadis@sourceforge.net>
- Mike Stewart <thewonderidiot@gmail.com>
- Timothy Collett <tcollett+github@topazgryphon.org>
- RossM <Ross@Gnome>
- Tom Prince <tom.prince@ualberta.net>
- Jared Adams <jaxad0127@gmail.com>
- expwnent <q309185@gmail.com>
- Erik Youngren <artanis.00@gmail.com>
- Espen Wiborg <espen.wiborg@telio.no>
- Tim Walberg <twalberg@comcast.net>
- Mikko Juola <mikko.juola@kolumbus.fi>
- rampaging-poet <yrudoingthis@hotmail.com>
- U-glouglou\\simon
- Clayton Hughes <clayton.hughes@gmail.com>
- zilpin <ziLpin@gmail.com>
- Will Rogers <wjrogers@gmail.com>
- NMLittle <nmlittle@gmail.com>
- root
- reverb
- Zhentar <Zhentar@gmail.com>
- Valentin Ochs <a@0au.de>
- Priit Laes <plaes@plaes.org>
- kmartin
- Neil Little
- rout <rout.mail+github@gmail.com>
- rofl0r <retnyg@gmx.net>
- harlanplayford <harlanplayford@gmail.com>
- gsvslto <gsvslto@gmail.com>
- sami
- potato
- playfordh <harlanplayford@gmail.com>
- feng1st <nf_xp@hotmail.com>
- comestible <nickolas.g.russell@gmail.com>
- Rumrusher <Anuleakage@yahoo.com>
- Rinin <RininS@Gmail.com>
- Raoul van Putten
- John Shade <gsvslto@gmail.com>
- John Beisley <greatred@gmail.com>
- Feng <nf_xp@hotmail.com>
- Donald Ruegsegger <druegsegger@gmail.com>
- Caldfir <caldfir@hotmail.com>
- Antalia <tamarakorr@gmail.com>
- Angus Mezick <amezick@gmail.com>
And those are the cool people who made **stonesense**.
- Kris Parker <kaypy>
- Japa <japa.mala.illo@gmail.com>
- Jonas Ask <jonask84@gmail.com>
- Petr Mrázek <peterix@gmail.com>
- Caldfir <aitken.tim@gmail.com>
- 8Z <git8z@ya.ru>
- Alexander Gavrilov <angavrilov@gmail.com>
- Timothy Collett <tcollett+github@topazgryphon.org>

@ -2,7 +2,7 @@
License of dfhack License of dfhack
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -3,13 +3,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" /> <meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
<title>DFHack Lua API</title> <title>DFHack Lua API</title>
<style type="text/css"> <style type="text/css">
/* /*
:Author: David Goodger (goodger@python.org) :Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7056 2011-06-17 10:50:48Z milde $ :Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
:Copyright: This stylesheet has been placed in the public domain. :Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils. Default cascading style sheet for the HTML output of Docutils.
@ -249,10 +249,18 @@ pre.address {
margin-top: 0 ; margin-top: 0 ;
font: inherit } font: inherit }
pre.literal-block, pre.doctest-block, pre.math { pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ; margin-left: 2em ;
margin-right: 2em } margin-right: 2em }
pre.code .ln { /* line numbers */
color: grey;
}
.code {
background-color: #eeeeee
}
span.classifier { span.classifier {
font-family: sans-serif ; font-family: sans-serif ;
font-style: oblique } font-style: oblique }
@ -366,14 +374,15 @@ ul.auto-toc {
<li><a class="reference internal" href="#global-environment" id="id32">Global environment</a></li> <li><a class="reference internal" href="#global-environment" id="id32">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id33">utils</a></li> <li><a class="reference internal" href="#utils" id="id33">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id34">dumper</a></li> <li><a class="reference internal" href="#dumper" id="id34">dumper</a></li>
<li><a class="reference internal" href="#class" id="id35">class</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#plugins" id="id35">Plugins</a><ul> <li><a class="reference internal" href="#plugins" id="id36">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id36">burrows</a></li> <li><a class="reference internal" href="#burrows" id="id37">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id37">sort</a></li> <li><a class="reference internal" href="#sort" id="id38">sort</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#scripts" id="id38">Scripts</a></li> <li><a class="reference internal" href="#scripts" id="id39">Scripts</a></li>
</ul> </ul>
</div> </div>
<p>The current version of DFHack has extensive support for <p>The current version of DFHack has extensive support for
@ -780,7 +789,7 @@ running coroutine and let the C++ code release the core suspend
lock. Using an explicit <tt class="docutils literal">dfhack.with_suspend</tt> will prevent lock. Using an explicit <tt class="docutils literal">dfhack.with_suspend</tt> will prevent
this, forcing the function to block on input with lock held.</p> this, forcing the function to block on input with lock held.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,history_filename[,env]]])</span></tt></p>
<p>Starts an interactive lua interpreter, using the specified prompt <p>Starts an interactive lua interpreter, using the specified prompt
string, global environment and command-line history file.</p> string, global environment and command-line history file.</p>
<p>If the interactive console is not accessible, returns <em>nil, error</em>.</p> <p>If the interactive console is not accessible, returns <em>nil, error</em>.</p>
@ -923,6 +932,21 @@ Returns <em>entry, did_create_new</em></p>
and automatically stored in the save game, these save and retrieval and automatically stored in the save game, these save and retrieval
functions can just copy values in memory without doing any actual I/O. functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead.</p> However, currently every entry has a 180+-byte dead-weight overhead.</p>
<p>It is also possible to associate one bit per map tile with an entry,
using these two methods:</p>
<ul>
<li><p class="first"><tt class="docutils literal">entry:getTilemask(block[, create])</tt></p>
<p>Retrieves the tile bitmask associated with this entry in the given map
block. If <tt class="docutils literal">create</tt> is <em>true</em>, an empty mask is created if none exists;
otherwise the function returns <em>nil</em>, which must be assumed to be the same
as an all-zero mask.</p>
</li>
<li><p class="first"><tt class="docutils literal">entry:deleteTilemask(block)</tt></p>
<p>Deletes the associated tile mask from the given map block.</p>
</li>
</ul>
<p>Note that these masks are only saved in fortress mode, and also that deleting
the persistent entry will <strong>NOT</strong> delete the associated masks.</p>
</div> </div>
<div class="section" id="material-info-lookup"> <div class="section" id="material-info-lookup">
<h3><a class="toc-backref" href="#id17">Material info lookup</a></h3> <h3><a class="toc-backref" href="#id17">Material info lookup</a></h3>
@ -1032,6 +1056,9 @@ a full-screen item view of a container. Note that in the
last case, the highlighted <em>contained item</em> is returned, not last case, the highlighted <em>contained item</em> is returned, not
the container itself.</p> the container itself.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getSelectedBuilding([silent])</span></tt></p>
<p>Returns the building selected via <em>'q'</em>, <em>'t'</em>, <em>'k'</em> or <em>'i'</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAnnouncement(text,color[,is_bright])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Adds a regular announcement with given text, color, and brightness. <p>Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p> The is_bright boolean actually seems to invert the brightness.</p>
@ -1083,6 +1110,13 @@ above operations accordingly. If enabled, pauses and zooms to position.</p>
if there are any jobs with <tt class="docutils literal">first_id &lt;= id &lt; job_next_id</tt>, if there are any jobs with <tt class="docutils literal">first_id &lt;= id &lt; job_next_id</tt>,
a lua list containing them.</p> a lua list containing them.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.job.isSuitableItem(job_item, item_type, item_subtype)</tt></p>
<p>Does basic sanity checks to verify if the suggested item type matches
the flags in the job item.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)</tt></p>
<p>Likewise, if replacing material.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="units-module"> <div class="section" id="units-module">
@ -1146,6 +1180,10 @@ same checks the game uses to decide game-over by extinction.</p>
<p>Returns the age of the unit in years as a floating-point value. <p>Returns the age of the unit in years as a floating-point value.
If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p> If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNominalSkill(unit, skill[, use_rust])</tt></p>
<p>Retrieves the nominal skill level for the given unit. If <tt class="docutils literal">use_rust</tt>
is <em>true</em>, subtracts the rust penalty.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p>
<p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p> <p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li> </li>
@ -1201,6 +1239,12 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.getContainedItems(item)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.items.getContainedItems(item)</tt></p>
<p>Returns a list of items contained in this one.</p> <p>Returns a list of items contained in this one.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getHolderBuilding(item)</tt></p>
<p>Returns the holder building or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getHolderUnit(item)</tt></p>
<p>Returns the holder unit or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToGround(item,pos)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.items.moveToGround(item,pos)</tt></p>
<p>Move the item to the ground at position. Returns <em>false</em> if impossible.</p> <p>Move the item to the ground at position. Returns <em>false</em> if impossible.</p>
</li> </li>
@ -1219,6 +1263,15 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.makeProjectile(item)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.items.makeProjectile(item)</tt></p>
<p>Turns the item into a projectile, and returns the new object, or <em>nil</em> if impossible.</p> <p>Turns the item into a projectile, and returns the new object, or <em>nil</em> if impossible.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.items.isCasteMaterial(item_type)</tt></p>
<p>Returns <em>true</em> if this item type uses a creature/caste pair as its material.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getSubtypeCount(item_type)</tt></p>
<p>Returns the number of raw-defined subtypes of the given item type, or <em>-1</em> if not applicable.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getSubtypeDef(item_type, subtype)</tt></p>
<p>Returns the raw definition for the given item type and subtype, or <em>nil</em> if invalid.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="maps-module"> <div class="section" id="maps-module">
@ -1233,7 +1286,7 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p>
<p>Returns a map block object for given x,y,z in local block coordinates.</p> <p>Returns a map block object for given x,y,z in local block coordinates.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.isValidTilePos(coords)</tt>, or isValidTilePos(x,y,z)``</p> <li><p class="first"><tt class="docutils literal">dfhack.maps.isValidTilePos(coords)</tt>, or <tt class="docutils literal">isValidTilePos(x,y,z)</tt></p>
<p>Checks if the given df::coord or x,y,z in local tile coordinates are valid.</p> <p>Checks if the given df::coord or x,y,z in local tile coordinates are valid.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p>
@ -1242,6 +1295,12 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.ensureTileBlock(coords)</tt>, or <tt class="docutils literal">ensureTileBlock(x,y,z)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.ensureTileBlock(coords)</tt>, or <tt class="docutils literal">ensureTileBlock(x,y,z)</tt></p>
<p>Like <tt class="docutils literal">getTileBlock</tt>, but if the block is not allocated, try creating it.</p> <p>Like <tt class="docutils literal">getTileBlock</tt>, but if the block is not allocated, try creating it.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileType(coords)</tt>, or <tt class="docutils literal">getTileType(x,y,z)</tt></p>
<p>Returns the tile type at the given coordinates, or <em>nil</em> if invalid.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileFlags(coords)</tt>, or <tt class="docutils literal">getTileFlags(x,y,z)</tt></p>
<p>Returns designation and occupancy references for the given coordinates, or <em>nil, nil</em> if invalid.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt>, or <tt class="docutils literal">getRegionBiome(x,y)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt>, or <tt class="docutils literal">getRegionBiome(x,y)</tt></p>
<p>Returns the biome info struct for the given global map region.</p> <p>Returns the biome info struct for the given global map region.</p>
</li> </li>
@ -1270,6 +1329,18 @@ tools like liquids or tiletypes are used. It also cannot possibly
take into account anything that depends on the actual units, like take into account anything that depends on the actual units, like
burrows, or the presence of invaders.</p> burrows, or the presence of invaders.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.hasTileAssignment(tilemask)</tt></p>
<p>Checks if the tile_bitmask object is not <em>nil</em> and contains any set bits; returns <em>true</em> or <em>false</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileAssignment(tilemask,x,y)</tt></p>
<p>Checks if the tile_bitmask object is not <em>nil</em> and has the relevant bit set; returns <em>true</em> or <em>false</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.setTileAssignment(tilemask,x,y,enable)</tt></p>
<p>Sets the relevant bit in the tile_bitmask object to the <em>enable</em> argument.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.resetTileAssignment(tilemask[,enable])</span></tt></p>
<p>Sets all bits in the mask to the <em>enable</em> argument.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="burrows-module"> <div class="section" id="burrows-module">
@ -1608,6 +1679,16 @@ options; if multiple interpretations exist, the table will contain multiple keys
</dl> </dl>
<p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p> <p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedUnit()</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedItem()</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedJob()</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedBuilding()</tt></p>
<p>Implement these to provide a return value for the matching
<tt class="docutils literal"><span class="pre">dfhack.gui.getSelected...</span></tt> function.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="internal-api"> <div class="section" id="internal-api">
@ -1639,6 +1720,15 @@ global environment, persistent between calls to the script.</p>
If destination overlaps a completely invalid memory region, or another error If destination overlaps a completely invalid memory region, or another error
occurs, returns false.</p> occurs, returns false.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.patchBytes(write_table[, verify_table])</tt></p>
<p>The first argument must be a lua table, which is interpreted as a mapping from
memory addresses to byte values that should be stored there. The second argument
may be a similar table of values that need to be checked before writing anything.</p>
<p>The function takes care to either apply all of <tt class="docutils literal">write_table</tt>, or none of it.
An empty <tt class="docutils literal">write_table</tt> with a nonempty <tt class="docutils literal">verify_table</tt> can be used to reasonably
safely check if the memory contains certain values.</p>
<p>Returns <em>true</em> if successful, or <em>nil, error_msg, address</em> if not.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.memmove(dest,src,count)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.internal.memmove(dest,src,count)</tt></p>
<p>Wraps the standard memmove function. Accepts both numbers and refs as pointers.</p> <p>Wraps the standard memmove function. Accepts both numbers and refs as pointers.</p>
</li> </li>
@ -1785,6 +1875,15 @@ coordinates), returns <em>nil</em>.</p>
<li><p class="first"><tt class="docutils literal">xyz2pos(x,y,z)</tt></p> <li><p class="first"><tt class="docutils literal">xyz2pos(x,y,z)</tt></p>
<p>Returns a table with x, y and z as fields.</p> <p>Returns a table with x, y and z as fields.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">same_xyz(a,b)</tt></p>
<p>Checks if <tt class="docutils literal">a</tt> and <tt class="docutils literal">b</tt> have the same x, y and z fields.</p>
</li>
<li><p class="first"><tt class="docutils literal">get_path_xyz(path,i)</tt></p>
<p>Returns <tt class="docutils literal">path.x[i], path.y[i], path.z[i]</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">pos2xy(obj)</tt>, <tt class="docutils literal">xy2pos(x,y)</tt>, <tt class="docutils literal">same_xy(a,b)</tt>, <tt class="docutils literal">get_path_xy(a,b)</tt></p>
<p>Same as above, but for 2D coordinates.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">safe_index(obj,index...)</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">safe_index(obj,index...)</span></tt></p>
<p>Walks a sequence of dereferences, which may be represented by numbers or strings. <p>Walks a sequence of dereferences, which may be represented by numbers or strings.
Returns <em>nil</em> if any of obj or indices is <em>nil</em>, or a numeric index is out of array bounds.</p> Returns <em>nil</em> if any of obj or indices is <em>nil</em>, or a numeric index is out of array bounds.</p>
@ -1888,6 +1987,12 @@ utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')
</pre> </pre>
<p>(For an explanation of <tt class="docutils literal">new=true</tt>, see table assignment in the wrapper section)</p> <p>(For an explanation of <tt class="docutils literal">new=true</tt>, see table assignment in the wrapper section)</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">utils.erase_sorted_key(vector,key,field,cmpfun)</tt></p>
<p>Removes the item with the given key from the list. Returns: <em>did_erase, vector[idx], idx</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">utils.erase_sorted(vector,item,field,cmpfun)</tt></p>
<p>Exactly like <tt class="docutils literal">erase_sorted_key</tt>, but if field is specified, takes the key from <tt class="docutils literal">item[field]</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">utils.prompt_yes_no(prompt, default)</tt></p> <li><p class="first"><tt class="docutils literal">utils.prompt_yes_no(prompt, default)</tt></p>
<p>Presents a yes/no prompt to the user. If <tt class="docutils literal">default</tt> is not <em>nil</em>, <p>Presents a yes/no prompt to the user. If <tt class="docutils literal">default</tt> is not <em>nil</em>,
allows just pressing Enter to submit the default choice. allows just pressing Enter to submit the default choice.
@ -1917,16 +2022,88 @@ the other arguments see the original documentation link above.</p>
</li> </li>
</ul> </ul>
</div> </div>
<div class="section" id="class">
<h2><a class="toc-backref" href="#id35">class</a></h2>
<p>Implements a trivial single-inheritance class system.</p>
<ul>
<li><p class="first"><tt class="docutils literal">Foo = defclass(Foo[, ParentClass])</tt></p>
<p>Defines or updates class Foo. The <tt class="docutils literal">Foo = defclass(Foo)</tt> syntax
is needed so that when the module or script is reloaded, the
class identity will be preserved through the preservation of
global variable values.</p>
<p>The <tt class="docutils literal">defclass</tt> function is defined as a stub in the global
namespace, and using it will auto-load the class module.</p>
</li>
<li><p class="first"><tt class="docutils literal">Class.super</tt></p>
<p>This class field is set by defclass to the parent class, and
allows a readable <tt class="docutils literal">Class.super.method(self, <span class="pre">...)</span></tt> syntax for
calling superclass methods.</p>
</li>
<li><p class="first"><tt class="docutils literal">Class.ATTRS { foo = xxx, bar = yyy }</tt></p>
<p>Declares certain instance fields to be attributes, i.e. auto-initialized
from fields in the table used as the constructor argument. If omitted,
they are initialized with the default values specified in this declaration.</p>
<p>If the default value should be <em>nil</em>, use <tt class="docutils literal">ATTRS { foo = DEFAULT_NIL }</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">new_obj = Class{ foo = arg, bar = arg, ... }</tt></p>
<p>Calling the class as a function creates and initializes a new instance.
Initialization happens in this order:</p>
<ol class="arabic simple">
<li>An empty instance table is created, and its metatable set.</li>
<li>The <tt class="docutils literal">preinit</tt> method is called via <tt class="docutils literal">invoke_before</tt> (see below)
with the table used as argument to the class. This method is intended
for validating and tweaking that argument table.</li>
<li>Declared ATTRS are initialized from the argument table or their default values.</li>
<li>The <tt class="docutils literal">init</tt> method is called via <tt class="docutils literal">invoke_after</tt> with the argument table.
This is the main constructor method.</li>
<li>The <tt class="docutils literal">postinit</tt> method is called via <tt class="docutils literal">invoke_after</tt> with the argument table.
Place code that should be called after the object is fully constructed here.</li>
</ol>
</li>
</ul>
<p>Predefined instance methods:</p>
<ul>
<li><p class="first"><tt class="docutils literal">instance:assign{ foo = xxx }</tt></p>
<p>Assigns all values in the input table to the matching instance fields.</p>
</li>
<li><p class="first"><tt class="docutils literal">instance:callback(method_name, <span class="pre">[args...])</span></tt></p>
<p>Returns a closure that invokes the specified method of the class,
properly passing in self, and optionally a number of initial arguments too.
The arguments given to the closure are appended to these.</p>
</li>
<li><p class="first"><tt class="docutils literal">instance:invoke_before(method_name, <span class="pre">args...)</span></tt></p>
<p>Navigates the inheritance chain of the instance starting from the most specific
class, and invokes the specified method with the arguments if it is defined in
that specific class. Equivalent to the following definition in every class:</p>
<pre class="literal-block">
function Class:invoke_before(method, ...)
if rawget(Class, method) then
rawget(Class, method)(self, ...)
end
Class.super.invoke_before(method, ...)
end
</pre>
</li>
<li><p class="first"><tt class="docutils literal">instance:invoke_after(method_name, <span class="pre">args...)</span></tt></p>
<p>Like invoke_before, only the method is called after the recursive call to super,
i.e. invocations happen in the parent to child order.</p>
<p>These two methods are inspired by the Common Lisp before and after methods, and
are intended for implementing similar protocols for certain things. The class
library itself uses them for constructors.</p>
</li>
</ul>
<p>To avoid confusion, these methods cannot be redefined.</p>
</div>
</div> </div>
<div class="section" id="plugins"> <div class="section" id="plugins">
<h1><a class="toc-backref" href="#id35">Plugins</a></h1> <h1><a class="toc-backref" href="#id36">Plugins</a></h1>
<p>DFHack plugins may export native functions and events <p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua <tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p> module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p> <p>The following plugins have lua support.</p>
<div class="section" id="burrows"> <div class="section" id="burrows">
<h2><a class="toc-backref" href="#id36">burrows</a></h2> <h2><a class="toc-backref" href="#id37">burrows</a></h2>
<p>Implements extended burrow manipulations.</p> <p>Implements extended burrow manipulations.</p>
<p>Events:</p> <p>Events:</p>
<ul> <ul>
@ -1964,13 +2141,13 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p> <p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div> </div>
<div class="section" id="sort"> <div class="section" id="sort">
<h2><a class="toc-backref" href="#id37">sort</a></h2> <h2><a class="toc-backref" href="#id38">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it <p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p> calls lua code to perform the actual ordering of list items.</p>
</div> </div>
</div> </div>
<div class="section" id="scripts"> <div class="section" id="scripts">
<h1><a class="toc-backref" href="#id38">Scripts</a></h1> <h1><a class="toc-backref" href="#id39">Scripts</a></h1>
<p>Any files with the .lua extension placed into hack/scripts/* <p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans matching command name consists of the name of the file sans

@ -488,7 +488,7 @@ Input & Output
lock. Using an explicit ``dfhack.with_suspend`` will prevent lock. Using an explicit ``dfhack.with_suspend`` will prevent
this, forcing the function to block on input with lock held. this, forcing the function to block on input with lock held.
* ``dfhack.interpreter([prompt[,env[,history_filename]]])`` * ``dfhack.interpreter([prompt[,history_filename[,env]]])``
Starts an interactive lua interpreter, using the specified prompt Starts an interactive lua interpreter, using the specified prompt
string, global environment and command-line history file. string, global environment and command-line history file.
@ -646,6 +646,24 @@ and automatically stored in the save game, these save and retrieval
functions can just copy values in memory without doing any actual I/O. functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead. However, currently every entry has a 180+-byte dead-weight overhead.
It is also possible to associate one bit per map tile with an entry,
using these two methods:
* ``entry:getTilemask(block[, create])``
Retrieves the tile bitmask associated with this entry in the given map
block. If ``create`` is *true*, an empty mask is created if none exists;
otherwise the function returns *nil*, which must be assumed to be the same
as an all-zero mask.
* ``entry:deleteTilemask(block)``
Deletes the associated tile mask from the given map block.
Note that these masks are only saved in fortress mode, and also that deleting
the persistent entry will **NOT** delete the associated masks.
Material info lookup Material info lookup
-------------------- --------------------
@ -777,6 +795,10 @@ Gui module
last case, the highlighted *contained item* is returned, not last case, the highlighted *contained item* is returned, not
the container itself. the container itself.
* ``dfhack.gui.getSelectedBuilding([silent])``
Returns the building selected via *'q'*, *'t'*, *'k'* or *'i'*.
* ``dfhack.gui.showAnnouncement(text,color[,is_bright])`` * ``dfhack.gui.showAnnouncement(text,color[,is_bright])``
Adds a regular announcement with given text, color, and brightness. Adds a regular announcement with given text, color, and brightness.
@ -841,6 +863,15 @@ Job module
if there are any jobs with ``first_id <= id < job_next_id``, if there are any jobs with ``first_id <= id < job_next_id``,
a lua list containing them. a lua list containing them.
* ``dfhack.job.isSuitableItem(job_item, item_type, item_subtype)``
Does basic sanity checks to verify if the suggested item type matches
the flags in the job item.
* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)``
Likewise, if replacing material.
Units module Units module
------------ ------------
@ -914,6 +945,11 @@ Units module
Returns the age of the unit in years as a floating-point value. Returns the age of the unit in years as a floating-point value.
If ``true_age`` is true, ignores false identities. If ``true_age`` is true, ignores false identities.
* ``dfhack.units.getNominalSkill(unit, skill[, use_rust])``
Retrieves the nominal skill level for the given unit. If ``use_rust``
is *true*, subtracts the rust penalty.
* ``dfhack.units.getEffectiveSkill(unit, skill)`` * ``dfhack.units.getEffectiveSkill(unit, skill)``
Computes the effective rating for the given skill, taking into account exhaustion, pain etc. Computes the effective rating for the given skill, taking into account exhaustion, pain etc.
@ -983,6 +1019,14 @@ Items module
Returns a list of items contained in this one. Returns a list of items contained in this one.
* ``dfhack.items.getHolderBuilding(item)``
Returns the holder building or *nil*.
* ``dfhack.items.getHolderUnit(item)``
Returns the holder unit or *nil*.
* ``dfhack.items.moveToGround(item,pos)`` * ``dfhack.items.moveToGround(item,pos)``
Move the item to the ground at position. Returns *false* if impossible. Move the item to the ground at position. Returns *false* if impossible.
@ -1007,6 +1051,18 @@ Items module
Turns the item into a projectile, and returns the new object, or *nil* if impossible. Turns the item into a projectile, and returns the new object, or *nil* if impossible.
* ``dfhack.items.isCasteMaterial(item_type)``
Returns *true* if this item type uses a creature/caste pair as its material.
* ``dfhack.items.getSubtypeCount(item_type)``
Returns the number of raw-defined subtypes of the given item type, or *-1* if not applicable.
* ``dfhack.items.getSubtypeDef(item_type, subtype)``
Returns the raw definition for the given item type and subtype, or *nil* if invalid.
Maps module Maps module
----------- -----------
@ -1023,7 +1079,7 @@ Maps module
Returns a map block object for given x,y,z in local block coordinates. Returns a map block object for given x,y,z in local block coordinates.
* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)`` * ``dfhack.maps.isValidTilePos(coords)``, or ``isValidTilePos(x,y,z)``
Checks if the given df::coord or x,y,z in local tile coordinates are valid. Checks if the given df::coord or x,y,z in local tile coordinates are valid.
@ -1035,6 +1091,14 @@ Maps module
Like ``getTileBlock``, but if the block is not allocated, try creating it. Like ``getTileBlock``, but if the block is not allocated, try creating it.
* ``dfhack.maps.getTileType(coords)``, or ``getTileType(x,y,z)``
Returns the tile type at the given coordinates, or *nil* if invalid.
* ``dfhack.maps.getTileFlags(coords)``, or ``getTileFlags(x,y,z)``
Returns designation and occupancy references for the given coordinates, or *nil, nil* if invalid.
* ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)`` * ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)``
Returns the biome info struct for the given global map region. Returns the biome info struct for the given global map region.
@ -1070,6 +1134,22 @@ Maps module
take into account anything that depends on the actual units, like take into account anything that depends on the actual units, like
burrows, or the presence of invaders. burrows, or the presence of invaders.
* ``dfhack.maps.hasTileAssignment(tilemask)``
Checks if the tile_bitmask object is not *nil* and contains any set bits; returns *true* or *false*.
* ``dfhack.maps.getTileAssignment(tilemask,x,y)``
Checks if the tile_bitmask object is not *nil* and has the relevant bit set; returns *true* or *false*.
* ``dfhack.maps.setTileAssignment(tilemask,x,y,enable)``
Sets the relevant bit in the tile_bitmask object to the *enable* argument.
* ``dfhack.maps.resetTileAssignment(tilemask[,enable])``
Sets all bits in the mask to the *enable* argument.
Burrows module Burrows module
-------------- --------------
@ -1463,6 +1543,14 @@ Supported callbacks and fields are:
If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key. If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key.
* ``function screen:onGetSelectedUnit()``
* ``function screen:onGetSelectedItem()``
* ``function screen:onGetSelectedJob()``
* ``function screen:onGetSelectedBuilding()``
Implement these to provide a return value for the matching
``dfhack.gui.getSelected...`` function.
Internal API Internal API
------------ ------------
@ -1501,6 +1589,18 @@ and are only documented here for completeness:
If destination overlaps a completely invalid memory region, or another error If destination overlaps a completely invalid memory region, or another error
occurs, returns false. occurs, returns false.
* ``dfhack.internal.patchBytes(write_table[, verify_table])``
The first argument must be a lua table, which is interpreted as a mapping from
memory addresses to byte values that should be stored there. The second argument
may be a similar table of values that need to be checked before writing anything.
The function takes care to either apply all of ``write_table``, or none of it.
An empty ``write_table`` with a nonempty ``verify_table`` can be used to reasonably
safely check if the memory contains certain values.
Returns *true* if successful, or *nil, error_msg, address* if not.
* ``dfhack.internal.memmove(dest,src,count)`` * ``dfhack.internal.memmove(dest,src,count)``
Wraps the standard memmove function. Accepts both numbers and refs as pointers. Wraps the standard memmove function. Accepts both numbers and refs as pointers.
@ -1676,6 +1776,18 @@ environment by the mandatory init file dfhack.lua:
Returns a table with x, y and z as fields. Returns a table with x, y and z as fields.
* ``same_xyz(a,b)``
Checks if ``a`` and ``b`` have the same x, y and z fields.
* ``get_path_xyz(path,i)``
Returns ``path.x[i], path.y[i], path.z[i]``.
* ``pos2xy(obj)``, ``xy2pos(x,y)``, ``same_xy(a,b)``, ``get_path_xy(a,b)``
Same as above, but for 2D coordinates.
* ``safe_index(obj,index...)`` * ``safe_index(obj,index...)``
Walks a sequence of dereferences, which may be represented by numbers or strings. Walks a sequence of dereferences, which may be represented by numbers or strings.
@ -1789,6 +1901,14 @@ utils
(For an explanation of ``new=true``, see table assignment in the wrapper section) (For an explanation of ``new=true``, see table assignment in the wrapper section)
* ``utils.erase_sorted_key(vector,key,field,cmpfun)``
Removes the item with the given key from the list. Returns: *did_erase, vector[idx], idx*.
* ``utils.erase_sorted(vector,item,field,cmpfun)``
Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``.
* ``utils.prompt_yes_no(prompt, default)`` * ``utils.prompt_yes_no(prompt, default)``
Presents a yes/no prompt to the user. If ``default`` is not *nil*, Presents a yes/no prompt to the user. If ``default`` is not *nil*,
@ -1819,6 +1939,86 @@ function:
argument specifies the indentation step size in spaces. For argument specifies the indentation step size in spaces. For
the other arguments see the original documentation link above. the other arguments see the original documentation link above.
class
=====
Implements a trivial single-inheritance class system.
* ``Foo = defclass(Foo[, ParentClass])``
Defines or updates class Foo. The ``Foo = defclass(Foo)`` syntax
is needed so that when the module or script is reloaded, the
class identity will be preserved through the preservation of
global variable values.
The ``defclass`` function is defined as a stub in the global
namespace, and using it will auto-load the class module.
* ``Class.super``
This class field is set by defclass to the parent class, and
allows a readable ``Class.super.method(self, ...)`` syntax for
calling superclass methods.
* ``Class.ATTRS { foo = xxx, bar = yyy }``
Declares certain instance fields to be attributes, i.e. auto-initialized
from fields in the table used as the constructor argument. If omitted,
they are initialized with the default values specified in this declaration.
If the default value should be *nil*, use ``ATTRS { foo = DEFAULT_NIL }``.
* ``new_obj = Class{ foo = arg, bar = arg, ... }``
Calling the class as a function creates and initializes a new instance.
Initialization happens in this order:
1. An empty instance table is created, and its metatable set.
2. The ``preinit`` method is called via ``invoke_before`` (see below)
with the table used as argument to the class. This method is intended
for validating and tweaking that argument table.
3. Declared ATTRS are initialized from the argument table or their default values.
4. The ``init`` method is called via ``invoke_after`` with the argument table.
This is the main constructor method.
5. The ``postinit`` method is called via ``invoke_after`` with the argument table.
Place code that should be called after the object is fully constructed here.
Predefined instance methods:
* ``instance:assign{ foo = xxx }``
Assigns all values in the input table to the matching instance fields.
* ``instance:callback(method_name, [args...])``
Returns a closure that invokes the specified method of the class,
properly passing in self, and optionally a number of initial arguments too.
The arguments given to the closure are appended to these.
* ``instance:invoke_before(method_name, args...)``
Navigates the inheritance chain of the instance starting from the most specific
class, and invokes the specified method with the arguments if it is defined in
that specific class. Equivalent to the following definition in every class::
function Class:invoke_before(method, ...)
if rawget(Class, method) then
rawget(Class, method)(self, ...)
end
Class.super.invoke_before(method, ...)
end
* ``instance:invoke_after(method_name, args...)``
Like invoke_before, only the method is called after the recursive call to super,
i.e. invocations happen in the parent to child order.
These two methods are inspired by the Common Lisp before and after methods, and
are intended for implementing similar protocols for certain things. The class
library itself uses them for constructors.
To avoid confusion, these methods cannot be redefined.
======= =======
Plugins Plugins

46
NEWS

@ -1,4 +1,30 @@
DFHack v0.34.11-r2 (UNRELEASED) DFHack future
Internals:
- support for displaying active keybindings properly.
- support for reusable widgets in lua screen library.
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
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.
- gui/assign-rack: works together with a binary patch to fix weapon racks.
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.
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.
DFHack v0.34.11-r2
Internals: Internals:
- full support for Mac OS X. - full support for Mac OS X.
@ -19,6 +45,10 @@ DFHack v0.34.11-r2 (UNRELEASED)
- liquids: can paint permaflow, i.e. what makes rivers power water wheels. - liquids: can paint permaflow, i.e. what makes rivers power water wheels.
- prospect: pre-embark prospector accounts for caves & magma sea in its estimate. - prospect: pre-embark prospector accounts for caves & magma sea in its estimate.
- rename: supports renaming stockpiles, workshops, traps, siege engines. - rename: supports renaming stockpiles, workshops, traps, siege engines.
- fastdwarf: now has an additional option to make dwarves teleport to their destination.
New commands:
- misery: multiplies every negative thought gained (2x by default).
- digtype: designates every tile of the same type of vein on the map for 'digging' (any dig designation).
New tweaks: New tweaks:
- tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus. - tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus.
- tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does. - tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does.
@ -27,11 +57,24 @@ DFHack v0.34.11-r2 (UNRELEASED)
- tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster. - tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster.
- tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc. - tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc.
- tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode. - tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode.
- tweak fast-trade: Shift-Enter for selecting items quckly in Trade and Move to Depot screens.
- tweak military-stable-assign: Stop rightmost list of military->Positions from jumping to top.
- tweak military-color-assigned: In same list, color already assigned units in brown & green.
New scripts: New scripts:
- fixnaked: removes thoughts about nakedness. - fixnaked: removes thoughts about nakedness.
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up. - setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
- siren: wakes up units, stops breaks and parties - but causes bad thoughts.
- fix/population-cap: run after every migrant wave to prevent exceeding the cap. - fix/population-cap: run after every migrant wave to prevent exceeding the cap.
- fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp.
- fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill.
- deathcause: shows the circumstances of death for a given body.
- digfort: designate areas to dig from a csv file.
- drainaquifer: remove aquifers from the map.
- growcrops: cheat to make farm crops instantly grow.
- magmasource: continuously spawn magma from any map tile.
- removebadthoughts: delete all negative thoughts from your dwarves.
- slayrace: instakill all units of a given race, optionally with magma.
- superdwarf: per-creature fastdwarf.
New GUI scripts: New GUI scripts:
- gui/mechanisms: browse mechanism links of the current building. - gui/mechanisms: browse mechanism links of the current building.
- gui/room-list: browse other rooms owned by the unit when assigning one. - gui/room-list: browse other rooms owned by the unit when assigning one.
@ -39,6 +82,7 @@ DFHack v0.34.11-r2 (UNRELEASED)
- gui/rename: renaming stockpiles, workshops and units via an in-game dialog. - gui/rename: renaming stockpiles, workshops and units via an in-game dialog.
- gui/power-meter: front-end for the Power Meter plugin. - gui/power-meter: front-end for the Power Meter plugin.
- gui/siege-engine: front-end for the Siege Engine plugin. - gui/siege-engine: front-end for the Siege Engine plugin.
- gui/choose-weapons: auto-choose matching weapons in the military equip screen.
Autolabor plugin: Autolabor plugin:
- can set nonidle hauler percentage. - can set nonidle hauler percentage.
- broker excluded from all labors when needed at depot. - broker excluded from all labors when needed at depot.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit d0b2d0750dc2d529a152eba4f3f519f69ff7eab0 Subproject commit 178a4916da838e46e46e769e566e47fff6eff8f8

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

@ -18,7 +18,7 @@ keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
# gui/rename script # gui/rename script
keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-N gui/rename
keybinding add Ctrl-Shift-P "gui/rename unit-profession" keybinding add Alt-Shift-P "gui/rename unit-profession"
############################## ##############################
# Generic adv mode bindings # # Generic adv mode bindings #
@ -45,8 +45,15 @@ keybinding add Shift-R "job-material RHYOLITE"
keybinding add Shift-I "job-material CINNABAR" keybinding add Shift-I "job-material CINNABAR"
keybinding add Shift-B "job-material COBALTITE" keybinding add Shift-B "job-material COBALTITE"
keybinding add Shift-O "job-material OBSIDIAN" keybinding add Shift-O "job-material OBSIDIAN"
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
keybinding add Alt-Shift-N "sort-units name" "sort-items description"
keybinding add Alt-Shift-R "sort-units arrival"
keybinding add Alt-Shift-T "sort-units profession" "sort-items type material"
keybinding add Alt-Shift-Q "sort-units squad_position" "sort-items quality"
# browse linked mechanisms # browse linked mechanisms
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
@ -62,6 +69,21 @@ keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
# siege engine control # siege engine control
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
# military weapon auto-select
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
# 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 #
############################ ############################
@ -90,3 +112,12 @@ tweak fix-dimensions
# make reactions requiring containers usable in advmode - the issue is # make reactions requiring containers usable in advmode - the issue is
# that the screen asks for those reagents to be selected directly # that the screen asks for those reagents to be selected directly
tweak advmode-contained tweak advmode-contained
# support Shift-Enter in Trade and Move Goods to Depot screens for faster
# selection; it selects the current item or stack and scrolls down one line
tweak fast-trade
# stop the right list in military->positions from resetting to top all the time
tweak military-stable-assign
# in same list, color units already assigned to squads in brown & green
tweak military-color-assigned

@ -1,5 +1,18 @@
#!/bin/bash #!/bin/bash
rst2html README.rst > Readme.html # regenerate documentation after editing the .rst files. Requires python and docutils.
rst2html COMPILE.rst > Compile.html
rst2html DEVEL.rst > Devel.html cd `dirname $0`
rst2html LUA_API.rst > Lua\ API.html
function process() {
if [ "$1" -nt "$2" ]; then
rst2html --no-generator --no-datestamp "$1" "$2"
else
echo "$2 - up to date."
fi
}
process Readme.rst Readme.html
process Compile.rst Compile.html
process Lua\ API.rst Lua\ API.html
process Contributors.rst Contributors.html

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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -126,8 +126,34 @@ struct Core::Private
void Core::cheap_tokenise(string const& input, vector<string> &output) void Core::cheap_tokenise(string const& input, vector<string> &output)
{ {
string *cur = NULL; string *cur = NULL;
size_t i = 0;
for (size_t i = 0; i < input.size(); i++) { // Check the first non-space character
while (i < input.size() && isspace(input[i])) i++;
// Special verbatim argument mode?
if (i < input.size() && input[i] == ':')
{
// Read the command
std::string cmd;
i++;
while (i < input.size() && !isspace(input[i]))
cmd.push_back(input[i++]);
if (!cmd.empty())
output.push_back(cmd);
// Find the argument
while (i < input.size() && isspace(input[i])) i++;
if (i < input.size())
output.push_back(input.substr(i));
return;
}
// Otherwise, parse in the regular quoted mode
for (; i < input.size(); i++)
{
unsigned char c = input[i]; unsigned char c = input[i];
if (isspace(c)) { if (isspace(c)) {
cur = NULL; cur = NULL;
@ -348,6 +374,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)
{ {
@ -601,8 +629,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")
@ -795,6 +822,8 @@ std::string Core::getHackPath()
#endif #endif
} }
void init_screen_module(Core *);
bool Core::Init() bool Core::Init()
{ {
if(started) if(started)
@ -865,6 +894,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);
@ -1096,7 +1126,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)
@ -1114,7 +1144,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);
} }
} }
@ -1585,15 +1615,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++;
@ -1616,23 +1658,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;
}
return true;
} }
if (ok) bool MemoryPatcher::write(void *target, const void *src, size_t size)
memmove(target, src, count); {
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);
} }
/******************************************************************************* /*******************************************************************************
@ -1652,7 +1716,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);

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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);
@ -760,6 +794,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedJob), WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedItem),
WRAPM(Gui, getSelectedBuilding),
WRAPM(Gui, showAnnouncement), WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showPopupAnnouncement),
@ -780,6 +815,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
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 }
@ -827,6 +864,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isDwarf), WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen), WRAPM(Units, isCitizen),
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getNominalSkill),
WRAPM(Units, getEffectiveSkill), WRAPM(Units, getEffectiveSkill),
WRAPM(Units, computeMovementSpeed), WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
@ -904,7 +942,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),
@ -935,6 +978,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),
@ -942,6 +1001,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 }
}; };
@ -966,6 +1029,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);
@ -983,6 +1065,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 }
@ -1144,6 +1228,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 }
}; };
@ -1452,6 +1537,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,6 +1703,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getVTable", internal_getVTable }, { "getVTable", internal_getVTable },
{ "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 },

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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);
@ -1814,6 +1817,8 @@ void DFHack::Lua::Core::onUpdate(color_ostream &out)
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
run_timers(out, State, frame_timers, frame[1], ++frame_idx); run_timers(out, State, frame_timers, frame[1], ++frame_idx);
if (world)
run_timers(out, State, tick_timers, frame[1], world->frame_counter); run_timers(out, State, tick_timers, frame[1], world->frame_counter);
} }

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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;

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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()
{ {

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -166,12 +166,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*));
} }
/* /*
@ -247,8 +247,9 @@ void VMethodInterposeLinkBase::set_chain(void *chain)
addr_to_method_pointer_(chain_mptr, chain); addr_to_method_pointer_(chain_mptr, chain);
} }
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority)
: host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method),
chain_mptr(chain_mptr), priority(priority),
applied(false), saved_chain(NULL), next(NULL), prev(NULL) applied(false), saved_chain(NULL), next(NULL), prev(NULL)
{ {
if (vmethod_idx < 0 || interpose_method == NULL) if (vmethod_idx < 0 || interpose_method == NULL)
@ -280,9 +281,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++)
{ {
@ -297,19 +299,34 @@ 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)
{ {
if (from == host) if (from == host)
@ -327,7 +344,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);
@ -349,15 +368,28 @@ bool VMethodInterposeLinkBase::apply(bool enable)
return false; return false;
// Retrieve the current vtable entry // Retrieve the current vtable entry
void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
VMethodInterposeLinkBase *next_link = NULL;
while (old_link && old_link->host == host && old_link->priority > priority)
{
next_link = old_link;
old_link = old_link->prev;
}
void *old_ptr = next_link ? next_link->saved_chain : host->get_vmethod_ptr(vmethod_idx);
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 (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) if (next_link)
{
next_link->set_chain(interpose_method);
}
else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method))
{ {
set_chain(NULL); set_chain(NULL);
return false; return false;
@ -365,8 +397,13 @@ bool VMethodInterposeLinkBase::apply(bool enable)
// Push the current link into the home host // Push the current link into the home host
applied = true; applied = true;
host->interpose_list[vmethod_idx] = this;
prev = old_link; prev = old_link;
next = next_link;
if (next_link)
next_link->prev = this;
else
host->interpose_list[vmethod_idx] = this;
child_hosts.clear(); child_hosts.clear();
child_next.clear(); child_next.clear();
@ -374,13 +411,22 @@ bool VMethodInterposeLinkBase::apply(bool enable)
if (old_link && old_link->host == host) if (old_link && old_link->host == host)
{ {
// If the old link is home, just push into the plain chain // If the old link is home, just push into the plain chain
assert(old_link->next == NULL); assert(old_link->next == next_link);
old_link->next = this; old_link->next = this;
// Child links belong to the topmost local entry // Child links belong to the topmost local entry
child_hosts.swap(old_link->child_hosts); child_hosts.swap(old_link->child_hosts);
child_next.swap(old_link->child_next); child_next.swap(old_link->child_next);
} }
else if (next_link)
{
if (old_link)
{
assert(old_link->child_next.count(next_link));
old_link->child_next.erase(next_link);
old_link->child_next.insert(this);
}
}
else else
{ {
// If creating a new local chain, find children with same vmethod // If creating a new local chain, find children with same vmethod
@ -401,6 +447,8 @@ bool VMethodInterposeLinkBase::apply(bool enable)
} }
} }
assert (!next_link || (child_next.empty() && child_hosts.empty()));
// Chain subclass hooks // Chain subclass hooks
for (auto it = child_next.begin(); it != child_next.end(); ++it) for (auto it = child_next.begin(); it != child_next.end(); ++it)
{ {
@ -415,7 +463,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;
} }
@ -452,9 +500,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)
{ {
@ -471,7 +521,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);
} }

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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;

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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,
@ -707,6 +708,9 @@ namespace DFHack {
// Global object pointers // Global object pointers
#include "df/global_objects.h" #include "df/global_objects.h"
#define DF_GLOBAL_VALUE(name,defval) (df::global::name ? *df::global::name : defval)
#define DF_GLOBAL_FIELD(name,fname,defval) (df::global::name ? df::global::name->fname : defval)
// A couple of headers that have to be included at once // A couple of headers that have to be included at once
#include "df/coord2d.h" #include "df/coord2d.h"
#include "df/coord.h" #include "df/coord.h"

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -36,6 +36,7 @@ distribution.
namespace DFHack { namespace DFHack {
class function_identity_base; class function_identity_base;
struct MaterialInfo;
namespace Units { namespace Units {
struct NoblePosition; struct NoblePosition;
@ -283,6 +284,7 @@ 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);
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);
} }

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -315,5 +315,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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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.

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -65,6 +65,7 @@ namespace DFHack
RPCService *owner, const char *name, int flags) RPCService *owner, const char *name, int flags)
: RPCFunctionBase(in, out), name(name), flags(flags), owner(owner), id(-1) : RPCFunctionBase(in, out), name(name), flags(flags), owner(owner), id(-1)
{} {}
virtual ~ServerFunctionBase() {}
RPCService *owner; RPCService *owner;
int16_t id; int16_t id;

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -87,7 +87,8 @@ namespace DFHack
with code defined by DFHack, while retaining ability to with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are plugins, and multiple hooks for the same vmethod are
automatically chained in undefined order. automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage: Usage:
@ -105,6 +106,8 @@ namespace DFHack
}; };
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() { void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply()) if (!INTERPOSE_HOOK(my_hack, foo).apply())
@ -121,9 +124,11 @@ namespace DFHack
static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \ static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \
rtype interpose_fn_##name args rtype interpose_fn_##name args
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \ #define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \
DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \ DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name); class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority);
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0)
#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain) #define INTERPOSE_NEXT(name) (this->*interpose_##name.chain)
#define INTERPOSE_HOOK(class, name) (class::interpose_##name) #define INTERPOSE_HOOK(class, name) (class::interpose_##name)
@ -140,6 +145,7 @@ namespace DFHack
int vmethod_idx; int vmethod_idx;
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 below
int priority;
bool applied; bool applied;
void *saved_chain; // Previous pointer to the code void *saved_chain; // Previous pointer to the code
@ -153,9 +159,9 @@ namespace DFHack
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); VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority);
~VMethodInterposeLinkBase(); ~VMethodInterposeLinkBase();
bool is_applied() { return applied; } bool is_applied() { return applied; }
@ -171,12 +177,13 @@ namespace DFHack
operator Ptr () { return chain; } operator Ptr () { return chain; }
template<class Ptr2> template<class Ptr2>
VMethodInterposeLink(Ptr target, Ptr2 src) VMethodInterposeLink(Ptr target, Ptr2 src, int priority)
: VMethodInterposeLinkBase( : VMethodInterposeLinkBase(
&Base::_identity, &Base::_identity,
vmethod_pointer_to_idx(target), vmethod_pointer_to_idx(target),
method_pointer_to_addr(src), method_pointer_to_addr(src),
&chain &chain,
priority
) )
{ src = target; /* check compatibility */ } { src = target; /* check compatibility */ }
}; };

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mr<EFBFBD>zek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mr<EFBFBD>zek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -91,6 +91,10 @@ namespace DFHack
DFHACK_EXPORT bool any_item_hotkey(df::viewscreen *top); DFHACK_EXPORT bool any_item_hotkey(df::viewscreen *top);
DFHACK_EXPORT df::item *getSelectedItem(color_ostream &out, bool quiet = false); DFHACK_EXPORT df::item *getSelectedItem(color_ostream &out, bool quiet = false);
// A building is selected via 'q', 't' or 'i' (civzone)
DFHACK_EXPORT bool any_building_hotkey(df::viewscreen *top);
DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false);
// Show a plain announcement, or a titan-style popup message // Show a plain announcement, or a titan-style popup message
DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true); DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true); DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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);

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -32,6 +32,7 @@ distribution.
#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
{ {
@ -69,6 +70,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);

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -151,6 +151,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

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -33,6 +33,14 @@ distribution.
#include "df/graphic.h" #include "df/graphic.h"
#include "df/viewscreen.h" #include "df/viewscreen.h"
namespace df
{
struct job;
struct item;
struct unit;
struct building;
}
/** /**
* \defgroup grp_screen utilities for painting to the screen * \defgroup grp_screen utilities for painting to the screen
* @ingroup grp_screen * @ingroup grp_screen
@ -120,6 +128,9 @@ 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);
} }
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {
@ -134,6 +145,7 @@ namespace DFHack
virtual ~dfhack_viewscreen(); virtual ~dfhack_viewscreen();
static bool is_instance(df::viewscreen *screen); static bool is_instance(df::viewscreen *screen);
static dfhack_viewscreen *try_cast(df::viewscreen *screen);
virtual void logic(); virtual void logic();
virtual void render(); virtual void render();
@ -146,6 +158,10 @@ namespace DFHack
virtual std::string getFocusString() = 0; virtual std::string getFocusString() = 0;
virtual void onShow() {}; virtual void onShow() {};
virtual void onDismiss() {}; virtual void onDismiss() {};
virtual df::unit *getSelectedUnit() { return NULL; }
virtual df::item *getSelectedItem() { return NULL; }
virtual df::job *getSelectedJob() { return NULL; }
virtual df::building *getSelectedBuilding() { return NULL; }
}; };
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
@ -178,5 +194,10 @@ namespace DFHack
virtual void onShow(); virtual void onShow();
virtual void onDismiss(); virtual void onDismiss();
virtual df::unit *getSelectedUnit();
virtual df::item *getSelectedItem();
virtual df::job *getSelectedJob();
virtual df::building *getSelectedBuilding();
}; };
} }

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -233,6 +233,7 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit);
DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); 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 getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -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,17 @@ 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; }
int raw_id() const { return id; }
const std::string &key() { return key_value; } 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]; }
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 +91,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();
private: DFHACK_EXPORT void ClearPersistentCache();
struct Private;
Private *d;
bool BuildPersistentCache(); DFHACK_EXPORT df::tile_bitmask *getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create = false);
}; DFHACK_EXPORT bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block);
}
} }
#endif #endif

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any

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

@ -48,17 +48,75 @@ 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 gap <= 0 then
return 0
end
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
l = inset or 0
t,r,b = l,l,l
end
return l,r,t,b
end
function inset_frame(rect, inset, gap)
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)
gap = gap or 0
local l,t,r,b = parse_inset(inset)
local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b)
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
return rect, body
end
function blink_visible(delay)
return math.floor(dfhack.getTickCount()/delay) % 2 == 0
end
function to_pen(default, pen, bg, bold)
if pen == nil then if pen == nil then
return default or {} return default or {}
elseif type(pen) ~= 'table' then elseif type(pen) ~= 'table' then
@ -68,37 +126,68 @@ local function to_pen(default, pen, bg, bold)
end end
end end
---------------------------- function getKeyDisplay(code)
-- Clipped painter object -- if type(code) == 'string' then
---------------------------- code = df.interface_key[code]
end
return dscreen.getKeyDisplay(code)
end
Painter = defclass(Painter, nil) -----------------------------------
-- Clipped view rectangle object --
-----------------------------------
function Painter:init(args) 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 rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize())
local crect = args.clip_rect or rect local crect = args.clip_rect or rect
self:assign{ self:assign{
x = rect.x1, y = rect.y1,
x1 = rect.x1, clip_x1 = crect.x1, x1 = rect.x1, clip_x1 = crect.x1,
y1 = rect.y1, clip_y1 = crect.y1, y1 = rect.y1, clip_y1 = crect.y1,
x2 = rect.x2, clip_x2 = crect.x2, x2 = rect.x2, clip_x2 = crect.x2,
y2 = rect.y2, clip_y2 = crect.y2, y2 = rect.y2, clip_y2 = crect.y2,
width = rect.x2-rect.x1+1, width = rect.x2-rect.x1+1,
height = rect.y2-rect.y1+1, height = rect.y2-rect.y1+1,
cur_pen = to_pen(nil, args.pen or COLOR_GREY)
} }
end 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
function Painter.new(rect, pen) function ViewRect:isDefunct()
return Painter{ rect = rect, pen = pen } return (self.clip_x1 > self.clip_x2 or self.clip_y1 > self.clip_y2)
end end
function Painter:isValidPos() function ViewRect:inClipGlobalXY(x,y)
return self.x >= self.clip_x1 and self.x <= self.clip_x2 return x >= self.clip_x1 and x <= self.clip_x2
and self.y >= self.clip_y1 and self.y <= self.clip_y2 and y >= self.clip_y1 and y <= self.clip_y2
end end
function Painter:viewport(x,y,w,h) 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 +202,57 @@ 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(nil, args.pen or COLOR_GREY)
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
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
@ -210,16 +339,150 @@ function Painter:string(text,pen,...)
return self:advance(#text, nil) return self:advance(#text, nil)
end end
function Painter:key(code,pen,bg,...)
return self:string(
getKeyDisplay(code),
pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ...
)
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 +499,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 +513,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 +544,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
------------------------ ------------------------
@ -353,68 +610,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)
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

@ -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
function MessageBox:onRenderBody(dc)
if #self.text > 0 then
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 end
function MessageBox:onRenderFrame(dc,rect)
MessageBox.super.onRenderFrame(self,dc,rect)
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
if keys._STRING == 0 then
self.input = string.sub(self.input, 1, #self.input-1)
else else
self.input = self.input .. string.char(keys._STRING) self:inputToSubviews(keys)
end
end end
end end
@ -171,97 +149,72 @@ 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
} }
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 = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false)
local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true)
local list_widget = widgets.List
if self.with_filter then
list_widget = widgets.FilteredList
end
self:addviews{
list_widget{
view_id = 'list',
selected = info.selected,
choices = info.choices,
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,
frame = { l = 0, r = 0 },
}
}
end end
function ListBox:getWantedFrameSize() function ListBox:getWantedFrameSize()
local mw, mh = ListBox.super.getWantedFrameSize(self) local mw, mh = InputBox.super.getWantedFrameSize(self)
return mw, mh+#self.choices local list = self.subviews.list
end list.frame.t = mh+1
return math.max(mw, list:getContentWidth()), mh+1+math.min(20,list:getContentHeight())
function ListBox:onRenderBody(dc)
ListBox.super.onRenderBody(self, dc)
dc:newline(1)
if self.selection>dc.height-3 then
self.page_top=self.selection-(dc.height-3)
elseif self.selection<self.page_top and self.selection >0 then
self.page_top=self.selection-1
end
for i,entry in ipairs(self.choices) do
if type(entry)=="table" then
entry=entry[1]
end
if i>self.page_top then
if i == self.selection then
dc:pen(self.select_pen or COLOR_LIGHTCYAN)
else
dc:pen(self.text_pen or COLOR_GREY)
end
dc:string(entry)
dc:newline(1)
end
end
end
function ListBox:moveCursor(delta)
local newsel=self.selection+delta
if #self.choices ~=0 then
if newsel<1 or newsel>#self.choices then
newsel=newsel % #self.choices
end
end
self.selection=newsel
end end
function ListBox:onInput(keys) function ListBox:onInput(keys)
if keys.SELECT then if keys.LEAVESCREEN then
self:dismiss()
local choice=self.choices[self.selection]
if self.on_input then
self.on_input(self.selection,choice)
end
if choice and choice[2] then
choice[2](choice,self.selection) -- maybe reverse the arguments?
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
@ -246,15 +259,21 @@ function Viewport:scrollByKey(key)
self.y1 + dy, self.y1 + dy,
self.z + dz self.z + dz
) )
else
local hk_pos = get_hotkey_target(key)
if hk_pos then
return self:centerOn(hk_pos)
else else
return self 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
if not HOTKEY_KEYS[code] or get_hotkey_target(code) then
self:sendInputToParent(code) 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()
end
return code 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 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,329 @@
-- 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',
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(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 = {
{ text = self.none_caption, mat_type = -1, mat_index = -1 },
}
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 = { {
icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1
} }
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,707 @@
-- 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,
}
function Panel:init(args)
self:addviews(args.subviews)
end
function Panel:onRenderBody(dc)
if self.on_render then self.on_render(dc) 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
x = x + #text
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
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,
row_height = 1,
scroll_keys = STANDARDSCROLL,
icon_width = DEFAULT_NIL,
}
function List:init(info)
self.page_top = 1
self.page_size = 1
self:setChoices(info.choices, info.selected)
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
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
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
if iw and obj.icon then
local icon = getval(obj.icon)
if icon then
dc:seek(0, y)
if type(icon) == 'table' 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
end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
if obj.key then
local keystr = gui.getKeyDisplay(obj.key)
dc:seek(dc.width-2-#keystr,y):pen(self.text_pen)
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
end
end
end
function List:submit()
if self.on_submit and #self.choices > 0 then
self.on_submit(self:getSelected())
end
end
function List:onInput(keys)
if self.on_submit and keys.SELECT then
self:submit()
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)
function FilteredList:init(info)
self.edit = EditField{
text_pen = info.cursor_pen,
frame = { l = info.icon_width, t = 0 },
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,
row_height = info.row_height,
scroll_keys = info.scroll_keys,
icon_width = info.icon_width,
}
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
self.not_found = Label{
visible = false,
text = info.not_found_label or 'No matches',
text_pen = COLOR_LIGHTRED,
frame = { l = info.icon_width, t = 2 },
}
self:addviews{ self.edit, self.list, self.not_found }
self:setChoices(info.choices, info.selected)
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: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

@ -378,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
@ -394,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
@ -361,6 +416,27 @@ function insert_or_update(vector,item,field,cmp)
return added,cur,pos return added,cur,pos
end end
-- Binary search and erase
function erase_sorted_key(vector,key,field,cmp)
local cur,found,pos = binsearch(vector,key,field,cmp)
if found then
if df.isvalid(vector) then
vector:erase(pos)
else
table.remove(vector, pos)
end
end
return found,cur,pos
end
function erase_sorted(vector,item,field,cmp)
local key = item
if field and item then
key = item[field]
end
return erase_sorted_key(vector,key,field,cmp)
end
-- Calls a method with a string temporary -- Calls a method with a string temporary
function call_with_string(obj,methodname,...) function call_with_string(obj,methodname,...)
return dfhack.with_temp_object( return dfhack.with_temp_object(

@ -1,6 +1,6 @@
/* /*
https://github.com/peterix/dfhack https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any warranty. In no event will the authors be held liable for any
@ -762,7 +762,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;

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