Merge branch 'master' into diggingInvaders

Also make edgeCost.cpp compile because I stopped midsentence for some reason.

Conflicts:
	library/modules/Maps.cpp
develop
expwnent 2013-05-31 16:28:05 -04:00
commit f8261348ff
121 changed files with 20045 additions and 2762 deletions

6
.gitignore vendored

@ -58,5 +58,9 @@ build/CPack*Config.cmake
/cmakeall.bat
# swap files for vim
# vim files
*.swp
.vimrc
# ctags file
tags

3
.gitmodules vendored

@ -1,6 +1,9 @@
[submodule "plugins/stonesense"]
path = plugins/stonesense
url = git://github.com/peterix/stonesense.git
[submodule "plugins/isoworld"]
path = plugins/isoworld
url = git://github.com/peterix/isoworld.git
[submodule "plugins/df2mc"]
path = plugins/df2mc
url = git://github.com/peterix/DF2MC.git

@ -58,14 +58,10 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif()
# set up versioning.
set(DF_VERSION_MAJOR "0")
set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
set(DF_VERSION "0.34.11")
SET(DFHACK_RELEASE "r3" 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}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
## where to install things (after the build is done, classic 'make install' or package structure)
@ -113,7 +109,8 @@ IF(UNIX)
SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic")
ELSEIF(MSVC)
# for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od")
ENDIF()
# use shared libraries for protobuf

@ -3,13 +3,13 @@
<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/" />
<meta name="generator" content="Docutils 0.10: http://docutils.sourceforge.net/" />
<title>Building DFHACK</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
:Id: $Id: html4css1.css 7514 2012-09-14 14:27:12Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
@ -77,7 +77,7 @@ div.tip p.admonition-title {
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 {
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
@ -253,13 +253,14 @@ 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
}
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
@ -334,21 +335,24 @@ ul.auto-toc {
<li><a class="reference internal" href="#build" id="id7">Build</a></li>
</ul>
</li>
<li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a></li>
<li><a class="reference internal" href="#windows" id="id9">Windows</a><ul>
<li><a class="reference internal" href="#id1" id="id10">How to get the code</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>
<li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a><ul>
<li><a class="reference internal" href="#snow-leopard-changes" id="id9">Snow Leopard Changes</a></li>
</ul>
</li>
<li><a class="reference internal" href="#windows" id="id10">Windows</a><ul>
<li><a class="reference internal" href="#id1" id="id11">How to get the code</a></li>
<li><a class="reference internal" href="#id2" id="id12">Dependencies</a></li>
<li><a class="reference internal" href="#id3" id="id13">Build</a></li>
</ul>
</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="id14">Using the library as a developer</a><ul>
<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="id16">Remote access interface</a></li>
<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="id18">Coding style</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="id20">Memory research</a></li>
<li><a class="reference internal" href="#build-types" id="id14">Build types</a></li>
<li><a class="reference internal" href="#using-the-library-as-a-developer" id="id15">Using the library as a developer</a><ul>
<li><a class="reference internal" href="#df-data-structure-definitions" id="id16">DF data structure definitions</a></li>
<li><a class="reference internal" href="#remote-access-interface" id="id17">Remote access interface</a></li>
<li><a class="reference internal" href="#contributing-to-dfhack" id="id18">Contributing to DFHack</a><ul>
<li><a class="reference internal" href="#coding-style" id="id19">Coding style</a></li>
<li><a class="reference internal" href="#how-to-get-new-code-into-dfhack" id="id20">How to get new code into DFHack</a></li>
<li><a class="reference internal" href="#memory-research" id="id21">Memory research</a></li>
</ul>
</li>
</ul>
@ -406,6 +410,7 @@ program.</p>
</div>
<div class="section" id="mac-os-x">
<h1><a class="toc-backref" href="#id8">Mac OS X</a></h1>
<p>If you are building on 10.6, please read the subsection below titled &quot;Snow Leopard Changes&quot; FIRST.</p>
<ol class="arabic">
<li><p class="first">Download and unpack a copy of the latest DF</p>
</li>
@ -458,12 +463,34 @@ make install
</pre>
</li>
</ol>
<div class="section" id="snow-leopard-changes">
<h2><a class="toc-backref" href="#id9">Snow Leopard Changes</a></h2>
<ol class="arabic">
<li><dl class="first docutils">
<dt>Add a step 6.2a (before Install XML::LibXSLT)::</dt>
<dd><p class="first last">In a separate Terminal window or tab, run:
<tt class="docutils literal">sudo ln <span class="pre">-s</span> /usr/include/libxml2/libxml /usr/include/libxml</tt></p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>Add a step 7a (before building)::</dt>
<dd><dl class="first last docutils">
<dt>In &lt;dfhack directory&gt;/library/LuaTypes.cpp, change line 467 to</dt>
<dd><p class="first last"><tt class="docutils literal">int len = <span class="pre">strlen((char*)ptr);</span></tt></p>
</dd>
</dl>
</dd>
</dl>
</li>
</ol>
</div>
</div>
<div class="section" id="windows">
<h1><a class="toc-backref" href="#id9">Windows</a></h1>
<h1><a class="toc-backref" href="#id10">Windows</a></h1>
<p>On Windows, DFHack replaces the SDL library distributed with DF.</p>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id10">How to get the code</a></h2>
<h2><a class="toc-backref" href="#id11">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.
You will need some sort of Windows port of git, or a GUI. Some examples:</p>
<blockquote>
@ -484,7 +511,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>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id11">Dependencies</a></h2>
<h2><a class="toc-backref" href="#id12">Dependencies</a></h2>
<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>
<p>It has the usual installer wizard. Make sure you let it add its binary folder
@ -501,7 +528,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>
</div>
<div class="section" id="id3">
<h2><a class="toc-backref" href="#id12">Build</a></h2>
<h2><a class="toc-backref" href="#id13">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>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>
@ -523,7 +550,7 @@ So pick either Release or RelWithDebInfo build and build the INSTALL target.</p>
</div>
</div>
<div class="section" id="build-types">
<h1><a class="toc-backref" href="#id13">Build types</a></h1>
<h1><a class="toc-backref" href="#id14">Build types</a></h1>
<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>
<pre class="literal-block">
@ -535,7 +562,7 @@ cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
'RelWithDebInfo'. 'Debug' is not available on Windows.</p>
</div>
<div class="section" id="using-the-library-as-a-developer">
<h1><a class="toc-backref" href="#id14">Using the library as a developer</a></h1>
<h1><a class="toc-backref" href="#id15">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.
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>
@ -553,29 +580,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
the dfhack repository is welcome and the right thing to do :)</p>
<div class="section" id="df-data-structure-definitions">
<h2><a class="toc-backref" href="#id15">DF data structure definitions</a></h2>
<h2><a class="toc-backref" href="#id16">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>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>
</div>
<div class="section" id="remote-access-interface">
<h2><a class="toc-backref" href="#id16">Remote access interface</a></h2>
<h2><a class="toc-backref" href="#id17">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>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>
</div>
<div class="section" id="contributing-to-dfhack">
<h2><a class="toc-backref" href="#id17">Contributing to DFHack</a></h2>
<h2><a class="toc-backref" href="#id18">Contributing to DFHack</a></h2>
<p>Several things should be kept in mind when contributing to DFHack.</p>
<div class="section" id="coding-style">
<h3><a class="toc-backref" href="#id18">Coding style</a></h3>
<h3><a class="toc-backref" href="#id19">Coding style</a></h3>
<p>DFhack uses ANSI formatting and four spaces as indentation. Line
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
I'll make <em>you</em> fix it ;)</p>
</div>
<div class="section" id="how-to-get-new-code-into-dfhack">
<h3><a class="toc-backref" href="#id19">How to get new code into DFHack</a></h3>
<h3><a class="toc-backref" href="#id20">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
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>
@ -585,7 +612,7 @@ this is also a good place to dump new ideas and/or bugs that need
fixing.</p>
</div>
<div class="section" id="memory-research">
<h3><a class="toc-backref" href="#id20">Memory research</a></h3>
<h3><a class="toc-backref" href="#id21">Memory research</a></h3>
<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
to look at machine code without getting crazy :)</p>

@ -63,10 +63,29 @@ extra options.
You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui
program.
Fixing the libstdc++ version bug
================================
When compiling dfhack yourself, it builds against your system libc.
When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which
is usually way older, and incompatible with your dfhack. This manifests with
the error message::
./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version
`GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so)
To fix this, simply remove the libstdc++ shipped with DF, it will fall back
to your system lib and everything will work fine::
cd /path/to/DF/
rm libs/libstdc++.so.6
========
Mac OS X
========
If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST.
1. Download and unpack a copy of the latest DF
2. Install Xcode from Mac App Store
3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools.
@ -106,6 +125,18 @@ Mac OS X
make
make install
Snow Leopard Changes
====================
1. Add a step 6.2a (before Install XML::LibXSLT)::
In a separate Terminal window or tab, run:
``sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml``
2. Add a step 7a (before building)::
In <dfhack directory>/library/LuaTypes.cpp, change line 467 to
``int len = strlen((char*)ptr);``
=======
Windows
=======

@ -402,10 +402,16 @@ ul.auto-toc {
<li><a class="reference internal" href="#plugins" id="id52">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id53">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id54">sort</a></li>
<li><a class="reference internal" href="#eventful" id="id55">Eventful</a><ul>
<li><a class="reference internal" href="#list-of-events" id="id56">List of events</a></li>
<li><a class="reference internal" href="#functions" id="id57">Functions</a></li>
<li><a class="reference internal" href="#examples" id="id58">Examples</a></li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id55">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id56">Save init script</a></li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id59">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id60">Save init script</a></li>
</ul>
</li>
</ul>
@ -3038,9 +3044,93 @@ set is the same as used by the command line.</p>
<p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p>
</div>
<div class="section" id="eventful">
<h2><a class="toc-backref" href="#id55">Eventful</a></h2>
<p>This plugin exports some events to lua thus allowing to run lua functions
on DF world events.</p>
<div class="section" id="list-of-events">
<h3><a class="toc-backref" href="#id56">List of events</a></h3>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)</tt></p>
<p>Auto activates if detects reactions starting with <tt class="docutils literal">LUA_HOOK_</tt>. Is called when reaction finishes.</p>
</li>
<li><p class="first"><tt class="docutils literal">onItemContaminateWound(item,unit,wound,number1,number2)</tt></p>
<p>Is called when item tries to contaminate wound (e.g. stuck in).</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjItemCheckMovement(projectile)</tt></p>
<p>Is called when projectile moves.</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjItemCheckImpact(projectile,somebool)</tt></p>
<p>Is called when projectile hits something.</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjUnitCheckMovement(projectile)</tt></p>
<p>Is called when projectile moves.</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjUnitCheckImpact(projectile,somebool)</tt></p>
<p>Is called when projectile hits something.</p>
</li>
<li><p class="first"><tt class="docutils literal">onWorkshopFillSidebarMenu(workshop,callnative)</tt></p>
<p>Is called when viewing a workshop in 'q' mode, to populate reactions, useful for custom viewscreens for shops.</p>
</li>
<li><p class="first"><tt class="docutils literal">postWorkshopFillSidebarMenu(workshop)</tt></p>
<p>Is called after calling (or not) native fillSidebarMenu(). Useful for job button
tweaking (e.g. adding custom reactions)</p>
</li>
</ol>
</div>
<div class="section" id="functions">
<h3><a class="toc-backref" href="#id57">Functions</a></h3>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">registerReaction(reaction_name,callback)</tt></p>
<p>Simplified way of using onReactionComplete; the callback is function (same params as event).</p>
</li>
<li><p class="first"><tt class="docutils literal">removeNative(shop_name)</tt></p>
<p>Removes native choice list from the building.</p>
</li>
<li><p class="first"><tt class="docutils literal">addReactionToShop(reaction_name,shop_name)</tt></p>
<p>Add a custom reaction to the building.</p>
</li>
</ol>
</div>
<div class="section" id="examples">
<h3><a class="toc-backref" href="#id58">Examples</a></h3>
<p>Spawn dragon breath on each item attempt to contaminate wound:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.onItemContaminateWound.one=function(item,unit,un_wound,x,y)
local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000)
end
</pre>
<p>Reaction complete example:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native)
local pos=copyall(unit.pos)
-- spawn dragonbreath after 100 ticks
dfhack.timeout(100,&quot;ticks&quot;,function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end)
--do not call real item creation code
call_native.value=false
end
</pre>
<p>Grenade example:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.onProjItemCheckImpact.one=function(projectile)
-- you can check if projectile.item e.g. has correct material
dfhack.maps.spawnFlow(projectile.cur_pos,6,0,0,50000)
end
</pre>
<p>Integrated tannery:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.addReactionToShop(&quot;TAN_A_HIDE&quot;,&quot;LEATHERWORKS&quot;)
</pre>
</div>
</div>
</div>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id55">Scripts</a></h1>
<h1><a class="toc-backref" href="#id59">Scripts</a></h1>
<p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans
@ -3071,7 +3161,7 @@ The <tt class="docutils literal">name</tt> argument should be the name stem, as
</ul>
<p>Note that this function lets errors propagate to the caller.</p>
<div class="section" id="save-init-script">
<h2><a class="toc-backref" href="#id56">Save init script</a></h2>
<h2><a class="toc-backref" href="#id60">Save init script</a></h2>
<p>If a save directory contains a file called <tt class="docutils literal">raw/init.lua</tt>, it is
automatically loaded and executed every time the save is loaded. It
can also define the following functions to be called by dfhack:</p>

@ -2958,6 +2958,96 @@ sort
Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.
Eventful
========
This plugin exports some events to lua thus allowing to run lua functions
on DF world events.
List of events
--------------
1. ``onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)``
Auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes.
2. ``onItemContaminateWound(item,unit,wound,number1,number2)``
Is called when item tries to contaminate wound (e.g. stuck in).
3. ``onProjItemCheckMovement(projectile)``
Is called when projectile moves.
4. ``onProjItemCheckImpact(projectile,somebool)``
Is called when projectile hits something.
5. ``onProjUnitCheckMovement(projectile)``
Is called when projectile moves.
6. ``onProjUnitCheckImpact(projectile,somebool)``
Is called when projectile hits something.
7. ``onWorkshopFillSidebarMenu(workshop,callnative)``
Is called when viewing a workshop in 'q' mode, to populate reactions, useful for custom viewscreens for shops.
8. ``postWorkshopFillSidebarMenu(workshop)``
Is called after calling (or not) native fillSidebarMenu(). Useful for job button
tweaking (e.g. adding custom reactions)
Functions
---------
1. ``registerReaction(reaction_name,callback)``
Simplified way of using onReactionComplete; the callback is function (same params as event).
2. ``removeNative(shop_name)``
Removes native choice list from the building.
3. ``addReactionToShop(reaction_name,shop_name)``
Add a custom reaction to the building.
Examples
--------
Spawn dragon breath on each item attempt to contaminate wound::
b=require "plugins.eventful"
b.onItemContaminateWound.one=function(item,unit,un_wound,x,y)
local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000)
end
Reaction complete example::
b=require "plugins.eventful"
b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native)
local pos=copyall(unit.pos)
-- spawn dragonbreath after 100 ticks
dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end)
--do not call real item creation code
call_native.value=false
end
Grenade example::
b=require "plugins.eventful"
b.onProjItemCheckImpact.one=function(projectile)
-- you can check if projectile.item e.g. has correct material
dfhack.maps.spawnFlow(projectile.cur_pos,6,0,0,50000)
end
Integrated tannery::
b=require "plugins.eventful"
b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS")
=======
Scripts

54
NEWS

@ -1,11 +1,53 @@
DFHack future
New commands:
- digSmart - automatically dig out specified veins as they are revealed
- restrictliquid - Restrict traffic on every visible square with liquid.
- restrictice - Restrict traffic on squares above visible ice.
- treefarm - automatically chop trees and dig obsidian
New scripts:
- masspit: designate caged creatures in a zone for pitting
- locate_ore: scan the map for unmined ore veins
- multicmd: run a sequence of dfhack commands, separated by ';'
- autobutcher: A GUI front-end for the autobutcher plugin.
- invasionNow: trigger an invasion, or many
Misc improvements:
- exterminate: renamed from slayrace, add help message, add butcher mode
- ruby: add df.dfhack_run "somecommand"
- magmasource: rename to source, allow water/magma sources/drains
- autoSyndrome:
disable by default
reorganized special tags
minimized error spam
reset policies: if the target already has an instance of the syndrome you can skip, add another instance, reset the timer, or add the full duration to the time remaining
- syndromeTrigger: replaces and extends trueTransformation. Can trigger things when syndromes are added for any reason.
- fastdwarf: fixed bug involving fastdwarf and teledwarf being on at the same time
- workNow: can optionally look for jobs when jobs are completed
New plugins:
- buildingplan: Place furniture before it's built
- outsideOnly: make raw-specified buildings impossible to build inside
- resume: A plugin to help display and resume suspended constructions conveniently
- dwarfmonitor: Records dwarf activity to measure fort efficiency
- mousequery: Look and poke at the map elements with the mouse.
- autotrade: Automatically send items in marked stockpiles to trade depot, when trading is possible.
- stocks: An improved stocks display screen.
Internals:
- EventManager: fixed job completion detection
- Once: easy way to make sure something happens once per run of DF, such as an error message
DFHack v0.34.11-r3
Internals:
- support for displaying active keybindings properly.
- support for reusable widgets in lua screen library.
- Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins
don't have to monitor the same things redundantly.
- Now works with OSX 10.6.8
Notable bugfixes:
- autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires.
- Stonesense is now fixed on OSX
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.
@ -26,6 +68,7 @@ DFHack future
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
- soundsense-season: writes the correct season to gamelog.txt on world load.
- create-items: spawn items
- fix/cloth-stockpile: fixes bug 5739; needs to be run after savegame load every time.
New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
@ -33,6 +76,7 @@ DFHack future
- gui/assign-rack: works together with a binary patch to fix weapon racks.
- gui/gm-editor: an universal editor for lots of dfhack things.
- gui/companion-order: a adventure mode command interface for your companions.
- gui/advfort: a way to do jobs with your adventurer (e.g. build fort).
New binary patches (for use with binpatch):
- armorstand-capacity: doubles the capacity of armor stands.
- custom-reagent-size: lets custom reactions use small amounts of inputs.
@ -59,6 +103,16 @@ DFHack future
saving you from having to trawl through long lists of materials each time you place one.
Dfusion plugin:
Reworked to make use of lua modules, now all the scripts can be used from other scripts.
New Eventful plugin:
A collection of lua events, that will allow new ways to interact with df world.
Auto syndrome plugin:
A way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws.
Infinite sky plugin:
Create new z-levels automatically or on request.
True transformation plugin:
A better way of doing permanent transformations that allows later transformations.
Work now plugin:
Makes the game assign jobs every time you pause.
DFHack v0.34.11-r2

File diff suppressed because it is too large Load Diff

@ -16,12 +16,12 @@ access DF memory and allow for easier development of new tools.
==============
Getting DFHack
==============
The project is currently hosted on github_, for both source and
binaries at http://github.com/peterix/dfhack
The project is currently hosted on github_
at http://github.com/peterix/dfhack
.. _github: http://www.github.com/
Releases can be downloaded from here: https://github.com/peterix/dfhack/downloads
Releases can be downloaded from here: http://dethware.org/dfhack/download
All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng
@ -449,6 +449,26 @@ Options:
:bees: turn colonies into honey bee colonies
createitem
----------
Allows creating new items of arbitrary types and made of arbitrary materials.
Any items created are spawned at the feet of the selected unit.
Specify the item and material information as you would indicate them in custom reaction raws, with the following differences:
* Separate the item and material with a space rather than a colon
* If the item has no subtype, omit the :NONE
* If the item is REMAINS, FISH, FISH_RAW, VERMIN, PET, or EGG, specify a CREATURE:CASTE pair instead of a material token.
Corpses, body parts, and prepared meals cannot be created using this tool.
Examples:
``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2``
Create 2 pairs of steel gauntlets.
``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD``
Create tower-cap logs.
``createitem FISH FISH_SHAD:MALE 5``
Create a stack of 5 cleaned shad, ready to eat.
deramp (by zilpin)
------------------
Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation.
@ -469,6 +489,16 @@ Options:
:show X: Marks the selected map feature as discovered.
:hide X: Marks the selected map feature as undiscovered.
infiniteSky
-----------
Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once.
Examples:
``infiniteSky n``
Raise the sky by n z-levels.
``infiniteSky enable/disable``
Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward.
liquids
-------
Allows adding magma, water and obsidian to the game. It replaces the normal
@ -741,6 +771,112 @@ showmood
--------
Shows all items needed for the currently active strange mood.
Mod Interaction
===============
This section describes plugins that interact with information in the raw files to add new features that cannot be achieved by only changing raw files.
autoSyndrome
------------
This plugin replaces "boiling rock" syndromes. Without this plugin, it is possible to add a syndrome to a unit by making the unit perform a custom reaction. First, add the syndrome to a rock which boils at room temperature. Make sure that the syndrome is spread by inhaling. Then, add a custom reaction which creates that rock. When the reaction is performed, the rock will be created, then boil. Hopefully, the dwarf will inhale the gas and become afflicted with the syndrome. This has disadvantages.
1. The creating unit might not inhale the gas. This makes it difficult to balance gameplay, as it is hard to measure this probability.
2. A different unit might inhale the gas. Pets or children might inhale the gas, which may be undesired.
To fix this problem, you can use autoSyndrome. The plugin monitors when custom reactions are completed within dwarf mode. If certain conditions are met, then the syndrome is immediately applied. The conditions are described below in priority order. If multiple products are created by the reaction, each one is considered independently in order. If a rock has multiple syndromes, each one is considered independently. If the conditions are all met, then the appropriate target will be instantly afficted with the appropriate syndrome, and the syndrome will behave just like any other.
1. The recently completed reaction must be a custom reaction, not a built-in one.
2. The product must be an inorganic boulder. Its boiling temperature is ignored.
3. The syndrome must have ``[SYN_CLASS:\AUTO_SYNDROME]``.
4. If the syndrome has ``[SYN_CLASS:\ALLOW_MULTIPLE_TARGETS]`` then an unbounded number of units can be targetted by the syndrome. If absent, at most one will be affected, and the worker will be considered first.
5. If the syndrome has ``[SYN_CLASS:\ALLOW_NONWORKER_TARGETS]`` then units that are in the building might be targetted. If absent, only the worker will be targetted. Even if present, the worker will be considered first.
6. If the syndrome has ``[SYN_CLASS:\PRESERVE_ROCK]`` then the stone or stones created will not be destroyed. If absent, they will be. Leaving this out ensures that gasses from boiling rocks will not sidestep the plugin, affecting nearby units using existing gameplay mechanics (because said gasses will never get a chance to be created).
7. If there are no ``SYN_IMMUNE_CREATURE``, ``SYN_AFFECTED_CREATURE``, ``SYN_IMMUNE_CLASS``, or ``SYN_AFFECTED_CLASS`` then any creature can be targetted, if it meets the above restrictions.
8. If the target creature is specified as ``SYN_IMMUNE_CREATURE`` in the syndrome tags, then it will not be affected.
9. If it is specified as ``SYN_AFFECTED_CREATURE`` then it will be affected.
10. If it has ``SYN_IMMUNE_CLASS`` it will not be affected.
11. It it has ``SYN_AFFECTED_CLASS`` it will be affected.
Note that tags like ``[SYN_INHALED]`` are ignored.
The plugin will work for transformations, but doesn't seem to properly apply CE_BLEEDING, for example. Further testing is required.
If the reaction is run twice, by default, a second instance of the syndrome is added. This behavior can be customized. With ``[SYN_CLASS:\RESET_POLICY DoNothing]``, units already afflicted with the syndrome will not be considered for syndrome application. With ``[SYN_CLASS:\RESET_POLICY ResetDuration]`` the existing syndrome timer is reset. With ``[SYN_CLASS:\RESET_POLICY AddDuration]`` the duration of the longest effect in the syndrome is added to the remaining duration of the existing syndrome. The tag ``[SYN_CLASS:\RESET_POLICY NewInstance]`` re-establishes the default behavior. If more than one such tag is present, the last one takes priority.
It is also possible to directly trigger dfhack plugins and scripts using autoSyndrome. If a syndrome has ``[SYN_CLASS:\COMMAND]`` then all following ``SYN_CLASS`` tags will be used to create a console command. The command will behave exactly as if the user had typed it in to the dfhack console. For example
``[SYN_CLASS:\COMMAND]``
``[SYN_CLASS:prospect]``
``[SYN_CLASS:all]``
would run the command "prospect all" whenever the given rock is created. The ``\AUTO_SYNDROME`` tag IS required for commands to execute. Note that since all ``SYN_CLASS`` tags after the ``\COMMAND`` tag are interpreted as part of the command, tags like ``\WORKER_ONLY`` must be placed before ``\COMMAND``, or not at all in order to work.
There are also certain "special" arguments that can be passed.
1. ``\LOCATION``: pass the x, y, and z coordinates of the work tile of the building which completed the job as separate arguments.
2. ``\WORKER_ID``: pass the unit id of the unit that finished the job as an argument.
3. ``\REACTION_INDEX``: pass the id of the completed reaction as an argument.
A note on spaces: when a plugin command executes in dfhack, it always has a list of arguments. Arguments are strings which tell the plugin what the user wants it to do. When the user types in a command, arguments will be separated by whitespace. However, if autoSyndrome is given a tag like ``[SYN_CLASS:123 abcde]`` after a ``[SYN_CLASS:\COMMAND]`` tag, this will still be treated as ONE argument. This may or may not cause problems, depending on the command in question. To be safe, never include spaces in as an argument to a command.
For example, suppose a reaction creates a rock which has a syndrome with the ``SYN_CLASS`` tags ``\AUTO_SYNDROME``, ``\COMMAND``, ``printArgs``, ``id_comes_next``, ``\WORKER_ID``, ``location_comes_next``, ``\LOCATION`` in that order. Suppose the reaction is done at ``(35,96,112)`` by unit number 15. This would be equivalent to typing ``printArgs id_comes_next 15 location_comes_next 35 96 112`` into the DFHack console and pressing enter.
Other syndrome classes that occur before ``\COMMAND`` (or in absence of any ``\COMMAND`` synclass) are ignored.
It is not currently possible to execute more than one command per syndrome. Instead, use multiple syndromes to achieve the same effect. Note that it is possible to have multiple syndromes on the same stone.
Again, note that plugins AND scripts can be executed this way, and arguments will be passed according to the same rules.
outsideOnly
-----------
This plugin makes it so that buildings whose names begin with ``OUTSIDE_ONLY`` cannot be built inside. If the player attempts to do so, the building will automatically be deconstructed.
syndromeTrigger
---------------
This plugin allows DFHack commands to be executed whenever a unit becomes afflicted with a syndrome. This can happen due to a boiling rock, an interaction, autoSyndrome, etc. Regardless of the cause, if the appropriate ``SYN_CLASS`` tags are present, the command will execute.
The syntax is very similar to autoSyndrome. If the syndrome has the ``\COMMAND`` tag, every subsequent ``SYN_CLASS`` tag will be used to create a console command. The following tags are "special":
1. ``\LOCATION``: this will be replaced by three arguments, one for each coordinate of the location of the unit.
2. ``\UNIT_ID``: this will be replaced by the identifier of the unit afllicted with the syndrome.
3. ``\SYNDROME_ID``: this will be replaced by the identifier of the syndrome in question.
If there is a ``[SYN_CLASS:\AUTO_SYNDROME]`` tag, then the command, if any, will NOT be executed by syndromeTrigger, because it should already have been executed by autoSyndrome.
True Transformation
...................
The syndromeTrigger plugin also allows true, permanent transformations. In vanilla DF, if syndrome A transforms dwarves into goblins permanently, and syndrome B transforms goblins into dragons permanently, then syndrome B would NOT properly transform goblins that had been transformed from dwarves. True transformations can be achieved with this plugin.
True transformations work differently. First, the unit transforms into a temporary, distinct, intermediate form. While transformed, this plugin overwrites their "original" unit type with the desired type. When the transformation wears off, they will turn "back" into the new unit type. Once truly transformed, units will function as if they had always been the new unit type. Equipment may be dropped on transformation, but relationships and experience should be maintained.
Suppose you want to transform dwarves into goblins. First, make a syndrome that turns dwarves into ducks for 1 tick (start:0:end:1). It should work with ``END:1``, but if it doesn't, try ``END:5``. You MUST use ``START:0``. Setting the end time very high will make the intermediate form take longer, and should have no other influence on the behavior of this plugin. The intermediate form must NOT be the same as the original form, and it must NOT be the same as the final form, or the game will crash. Add the following tags:
``[SYN_CLASS:\PERMANENT]``
``[SYN_CLASS:GOBLIN]``
``[SYN_CLASS:MALE]``
Note that you must use the "official" (usually allcaps) name of the target creature/caste, not necessarily the name used in game. For example, you would use ``BIRD_DUCK``, ``MALE``, instead of ``drake``.
It is perfectly fine to use syndromeTrigger along with autoSyndrome. This means that you can, for example, trigger a true transformation using a reaction. It is also possible to trigger a true transformation using an interaction, or another plugin that adds syndromes, so long as that other plugin does not interfere with the tags required for this one to work properly.
Designations
============
@ -927,6 +1063,14 @@ Example:
'alltraffic N' - Set traffic to 'normal' for all tiles.
restrictliquid
--------------
Restrict traffic on all visible tiles with liquid.
restrictice
-----------
Restrict traffic on all tiles on top of visible ice.
getplants
---------
This tool allows plant gathering and tree cutting by RAW ID. Specify the types
@ -1418,7 +1562,6 @@ Maintain 10-100 locally-made crafts of exceptional quality.
workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management
============================
@ -1693,6 +1836,8 @@ also tries to have dwarves specialize in specific skills.
Warning: autolabor will override any manual changes you make to labors
while it is enabled.
To prevent particular dwarves from being managed by autolabor, put them in any burrow.
For detailed usage information, see 'help autolabor'.
@ -1773,6 +1918,12 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
Diagnoses and fixes issues with nonexistant 'items occupying site', usually
caused by autodump bugs or other hacking mishaps.
* fix/cloth-stockpile
Fixes erratic behavior of cloth stockpiles by scanning material objects
in memory and patching up some invalid reference fields. Needs to be run
every time a save game is loaded; putting ``fix/cloth-stockpile enable``
in ``dfhack.init`` makes it run automatically.
gui/*
=====
@ -1821,7 +1972,7 @@ use in your farming plots.
With a seed type, the script will grow 100 of these seeds, ready to be
harvested. You can change the number with a 2nd argument.
For exemple, to grow 40 plump helmet spawn:
For example, to grow 40 plump helmet spawn:
::
growcrops plump 40
@ -1846,8 +1997,8 @@ Internals: the thoughts are set to be very old, so that the game remove them
quickly after you unpause.
slayrace
========
exterminate
===========
Kills any unit of a given race.
With no argument, lists the available races and count eligible targets.
@ -1864,42 +2015,58 @@ such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the
targets until they die (Warning: do not call on magma-safe creatures. Also,
using this mode for birds is not recommanded.)
using this mode on birds is not recommanded.)
Will target any unit on a revealed tile of the map, including ambushers.
Will target any unit on a revealed tile of the map, including ambushers,
but ignore caged/chained creatures.
Ex::
slayrace gob
exterminate gob
To kill a single creature, select the unit with the 'v' cursor and::
slayrace him
exterminate him
To purify all elves on the map with fire (may have side-effects)::
slayrace elve magma
exterminate elve magma
magmasource
===========
Create an infinite magma source on a tile.
source
======
Create an infinite magma or water source or drain on a tile.
This script registers a map tile as a magma source, and every 12 game ticks
that tile receives 1 new unit of flowing magma.
This script registers a map tile as a liquid source, and every 12 game ticks
that tile receives or remove 1 new unit of flow based on the configuration.
Place the game cursor where you want to create the source (must be a
flow-passable tile, and not too high in the sky) and call::
magmasource here
source add [magma|water] [0-7]
The number argument is the target liquid level (0 = drain, 7 = source).
To add more than 1 unit everytime, call the command again on the same spot.
To delete one source, place the cursor over its tile and use ``delete``.
To remove all existing sources, call ``source clear``.
The ``list`` argument shows all existing sources.
To add more than 1 unit everytime, call the command again.
Ex::
To delete one source, place the cursor over its tile and use ``delete-here``.
To remove all placed sources, call ``magmasource stop``.
source add water - water source
source add magma 7 - magma source
source add water 0 - water drain
With no argument, this command shows an help message and list existing sources.
masspit
=======
Designate all creatures in cages on top of a pit/pond activity zone for pitting.
Works best with an animal stockpile on top of the zone.
Works with a zone number as argument (eg ``Activity Zone #6`` -> ``masspit 6``)
or with the game cursor on top of the area.
digfort
=======
@ -2023,7 +2190,7 @@ Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Exemples::
Examples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
@ -2032,6 +2199,40 @@ Exemples::
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
locate-ore
==========
Scan the map for metal ores.
Finds and designate for digging one tile of a specific metal ore.
Only works for native metal ores, does not handle reaction stuff (eg STEEL).
When invoked with the ``list`` argument, lists metal ores available on the map.
Examples::
locate-ore list
locate-ore hematite
locate-ore iron
soundsense-season
=================
It is a well known issue that Soundsense cannot detect the correct
current season when a savegame is loaded and has to play random
season music until a season switch occurs.
This script registers a hook that prints the appropriate string
to gamelog.txt on every map load to fix this. For best results
call the script from ``dfhack.init``.
multicmd
========
Run multiple dfhack commands. The argument is split around the
character ; and all parts are run sequencially as independent
dfhack commands. Useful for hotkeys.
Example::
multicmd locate-ore iron ; digv
=======================
In-game interface tools
=======================
@ -2105,7 +2306,9 @@ directly to the main dwarf mode screen.
Search
======
The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens.
The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows
(unit list), Rooms, Announcements, Job List and Unit List screens.
.. image:: images/search.png
@ -2125,8 +2328,9 @@ Leaving any screen automatically clears the filter.
In the Trade screen, the actual trade will always only act on items that
are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, pressing the 't'
key while search is active clears the search instead of executing the trade.
Value numbers displayed by the screen. Because of this, the 't' key is
blocked while search is active, so you have to reset the filters first.
Pressing Alt-C will clear both search strings.
In the stockpile screen the option only appears if the cursor is in the
rightmost list:
@ -2182,8 +2386,25 @@ To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mo
.. image:: images/liquids.png
While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes.
This script is a gui front-end to the liquids plugin and works similar to it,
allowing you to add or remove water & magma, and create obsidian walls & floors.
Note that there is **no undo support**, and that bugs in this plugin have been
known to create pathfinding problems and heat traps.
The ``b`` key changes how the affected area is selected. The default *Rectangle*
mode works by selecting two corners like any ordinary designation. The ``p``
key chooses between adding water, magma, obsidian walls & floors, or just
tweaking flags.
When painting liquids, it is possible to select the desired level with ``+-``,
and choose between setting it exactly, only increasing or only decreasing
with ``s``.
In addition, ``f`` allows disabling or enabling the flowing water computations
for an area, and ``r`` operates on the "permanent flow" property that makes
rivers power water wheels even when full and technically not flowing.
After setting up the desired operations using the described keys, use ``Enter`` to apply them.
gui/mechanisms
@ -2411,6 +2632,55 @@ the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.
gui/advfort
=============
This script allows to perform jobs in adventure mode. For more complete help
press '?' while script is running. It's most confortable to use this as a
keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:
* -a or --nodfassign - uses different method to assign items.
* -i or --inventory - checks inventory for possible items to use in the job.
* -c or --cheat - relaxes item requirements for buildings (e.g. walls from bones).
implies -a
* job - selects that job (e.g. Dig or FellTree)
gui/companion-order
=======================
A script to issue orders for companions. Select companions with lower case chars, issue orders with upper
case. Must be in look or talk mode to issue command on tile.
* move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you.
* equip - try to equip items on the ground.
* pick-up - try to take items into hand (also wield)
* unequip - remove and drop equipment
* unwield - drop held items
* wait - temporarely remove from party
* follow - rejoin the party after "wait"
* leave - remove from party (can be rejoined by talking)
gui/gm-editor
=============
There are three ways to open this editor:
* using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)
* using gui/gm-editor <lua command> - executes lua command and opens editor on
it's results (e.g. gui/gm-editor "df.global.world.items.all" shows all items)
* using gui/gm-editor dialog - shows an in game dialog to input lua command. Works
the same as version above.
This editor allows to change and modify almost anything in df. Press '?' for an
in-game help.
=============
Behavior Mods
=============
@ -2604,3 +2874,4 @@ be bought from caravans. :)
To be really useful this needs patches from bug 808, ``tweak fix-dimensions``
and ``tweak advmode-contained``.

@ -42,6 +42,13 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force"
# Context-specific bindings #
#############################
# Stocks plugin
keybinding add Ctrl-Shift-Z@dwarfmode/Default "stocks show"
# Workflow
keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some "gui/workflow"
keybinding add Ctrl-I "gui/workflow status"
# q->stockpile; p - copy & paste stockpiles
keybinding add Alt-P copystock
@ -93,6 +100,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status"
# autobutcher front-end
keybinding add Shift-B@pet/List/Unit "gui/autobutcher"
# assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
@ -137,6 +147,9 @@ tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
# enable autoSyndrome
autoSyndrome enable
###########
# Scripts #
###########
@ -144,25 +157,26 @@ tweak military-training
# write the correct season to gamelog on world load
soundsense-season
# patch the material objects in memory to fix cloth stockpiles
fix/cloth-stockpile enable
#######################################################
# Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
#######################################################
# Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall
binpatch apply deconstruct-teleport
binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items
#binpatch apply hospital-overstocking
binpatch apply hospital-overstocking
# Bug 808 - custom reactions completely using up all of their reagents
#binpatch apply custom-reagent-size
binpatch apply custom-reagent-size
# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo
#binpatch apply training-ammo
binpatch apply training-ammo
# Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity
binpatch apply weaponrack-unassign
binpatch apply armorstand-capacity

@ -125,6 +125,7 @@ include/modules/Translation.h
include/modules/Vermin.h
include/modules/World.h
include/modules/Graphic.h
include/modules/Once.h
)
SET( MODULE_SOURCES
@ -147,6 +148,7 @@ modules/Vermin.cpp
modules/World.cpp
modules/Graphic.cpp
modules/Windows.cpp
modules/Once.cpp
)
IF(WIN32)

@ -373,7 +373,8 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
if (possible.size() == 1)
{
completed = possible[0];
fprintf(stderr, "Autocompleted %s to %s\n", first.c_str(), completed.c_str());
//fprintf(stderr, "Autocompleted %s to %s\n", , );
con.printerr("%s is not recognized. Did you mean %s?\n", first.c_str(), completed.c_str());
return true;
}
@ -382,7 +383,8 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
std::string out;
for (size_t i = 0; i < possible.size(); i++)
out += " " + possible[i];
con.print("Possible completions:%s\n", out.c_str());
con.printerr("%s is not recognized. Possible completions:%s\n", first.c_str(), out.c_str());
return true;
}
return false;
@ -717,7 +719,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
else if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
res = runRubyScript(con, plug_mgr, first, parts);
else if (try_autocomplete(con, first, completed))
return runCommand(con, completed, parts);
return CR_NOT_IMPLEMENTED;// runCommand(con, completed, parts);
else
con.printerr("%s is not a recognized command.\n", first.c_str());
}

@ -65,7 +65,7 @@ DFhackCExport int egg_prerender(void)
}
// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise
DFhackCExport int egg_sdl_event(SDL_Event* event)
DFhackCExport int egg_sdl_event(SDL::Event* event)
{
// if the event is valid, intercept
if( event != 0 )

@ -1314,7 +1314,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, enableBlockUpdates),
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canPathBetween),
WRAPM(Maps, canWalkBetween),
WRAPM(Maps, spawnFlow),
WRAPN(hasTileAssignment, hasTileAssignment),
WRAPN(getTileAssignment, getTileAssignment),

@ -212,9 +212,10 @@ bool Plugin::load(color_ostream &con)
}
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version");
if(!plug_name || !plug_version)
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
if(!plug_name || !plug_version || !plug_self)
{
con.printerr("Plugin %s has no name or version.\n", filename.c_str());
con.printerr("Plugin %s has no name, version or self pointer.\n", filename.c_str());
ClosePlugin(plug);
RefAutolock lock(access);
state = PS_BROKEN;
@ -229,6 +230,7 @@ bool Plugin::load(color_ostream &con)
state = PS_BROKEN;
return false;
}
*plug_self = this;
RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init)

@ -312,12 +312,12 @@ void* Process::memAlloc(const int length)
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
}
int Process::memDealloc(const void *ptr, const int length)
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(const void *ptr, const int length, const int prot)
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;

@ -39,15 +39,54 @@ using namespace DFHack;
/*
* Code for accessing method pointers directly. Very compiler-specific.
*
* Pointers to methods in C++ are conceptually similar to pointers to
* functions, but with some complications. Specifically, the target of
* such pointer can be either:
*
* - An ordinary non-virtual method, in which case the pointer behaves
* not much differently from a simple function pointer.
* - A virtual method, in which case calling the pointer must emulate
* an ordinary call to that method, i.e. fetch the real code address
* from the vtable at the appropriate index.
*
* This means that pointers to virtual methods actually have to encode
* the relevant vtable index value in some way. Also, since these two
* types of pointers cannot be distinguished by data type and differ
* only in value, any sane compiler would ensure that any non-virtual
* method that can potentially be called via a pointer uses the same
* parameter passing rules as an equivalent virtual method, so that
* the same parameter passing code would work with both types of pointer.
*
* This means that with a few small low-level compiler-specific wrappers
* to access the data inside such pointers it is possible to:
*
* - Convert a non-virtual method pointer into a code address that
* can be directly put into a vtable.
* - Convert a pointer taken out of a vtable into a fake non-virtual
* method pointer that can be used to easily call the original
* vmethod body.
* - Extract a vtable index out of a virtual method pointer.
*
* Taken together, these features allow delegating all the difficult
* and fragile tasks like passing parameters and calculating the
* vtable index to the C++ compiler.
*/
#if defined(_MSC_VER)
// MSVC may use up to 3 different representations
// based on context, but adding the /vmg /vmm options
// forces it to stick to this one. It can accomodate
// multiple, but not virtual inheritance.
struct MSVC_MPTR {
void *method;
intptr_t this_shift;
};
// Debug builds sometimes use additional thunks that
// just jump to the real one, presumably to attach some
// additional debug info.
static uint32_t *follow_jmp(void *ptr)
{
uint8_t *p = (uint8_t*)ptr;
@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr)
{
switch (*p)
{
case 0xE9:
case 0xE9: // jmp near rel32
p += 5 + *(int32_t*)(p+1);
break;
case 0xEB:
case 0xEB: // jmp short rel8
p += 2 + *(int8_t*)(p+1);
break;
default:
@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
#elif defined(__GXX_ABI_VERSION)
// GCC seems to always use this structure - possibly unless
// virtual inheritance is involved, but that's irrelevant.
struct GCC_MPTR {
intptr_t method;
intptr_t method; // Code pointer or tagged vtable offset
intptr_t this_shift;
};
@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v
{
if (vmethod_idx < 0 || interpose_method == NULL)
{
/*
* A failure here almost certainly means a problem in one
* of the pointer-to-method access wrappers above:
*
* - vmethod_idx comes from vmethod_pointer_to_idx_
* - interpose_method comes from method_pointer_to_addr_
*/
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
vmethod_idx, unsigned(interpose_method));
fflush(stderr);

@ -247,7 +247,8 @@ namespace DFHack
/// You have to have this in every plugin you write - just once. Ideally on top of the main file.
#define DFHACK_PLUGIN(plugin_name) \
DFhackDataExport const char * version = DFHACK_VERSION;\
DFhackDataExport const char * name = plugin_name;
DFhackDataExport const char * name = plugin_name;\
DFhackDataExport Plugin *plugin_self = NULL;
#define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =

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

@ -49,10 +49,10 @@ namespace DFHack
{
namespace Job {
// Duplicate the job structure. It is not linked into any DF lists.
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false);
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepEverything=false);
// Delete a cloned structure.
DFHACK_EXPORT void deleteJobStruct(df::job *job);
DFHACK_EXPORT void deleteJobStruct(df::job *job, bool keptEverything=false);
DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx);
DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job);

@ -307,7 +307,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
/// remove a block event from the block by address
extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which );
DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2);
}
}

@ -0,0 +1,11 @@
#pragma once
#include "Export.h"
#include <string>
namespace DFHack {
namespace Once {
DFHACK_EXPORT bool alreadyDone(std::string);
DFHACK_EXPORT bool doOnce(std::string);
}
}

@ -1,5 +1,7 @@
-- Simple binary patch with IDA dif file support.
local _ENV = mkmodule('binpatch')
local function load_patch(name)
local filename = name
if not string.match(filename, '[./\\]') then

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

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

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

@ -249,31 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos)
if (!occ || !occ->bits.building)
return NULL;
auto a = locationToBuilding.find(pos);
if ( a == locationToBuilding.end() ) {
cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <<pos.z << "." << endl;
exit(1);
}
int32_t id = (*a).second;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id);
if ( index == -1 ) {
cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <<pos.z << "." << endl;
exit(1);
}
df::building* building = df::global::world->buildings.all[index];
if (!building->isSettingOccupancy())
return NULL;
if (building->room.extents && building->isExtentShaped())
// Try cache lookup in case it works:
auto cached = locationToBuilding.find(pos);
if (cached != locationToBuilding.end())
{
auto etile = getExtentTile(building->room, pos);
if (!etile || !*etile)
return NULL;
auto building = df::building::find(cached->second);
if (building && building->z == pos.z &&
building->isSettingOccupancy() &&
containsTile(building, pos, false))
{
return building;
}
}
return building;
/*
//old method: brute-force
// The authentic method, i.e. how the game generally does this:
auto &vec = df::building::get_vector();
for (size_t i = 0; i < vec.size(); i++)
{
@ -298,7 +288,6 @@ df::building *Buildings::findAtTile(df::coord pos)
}
return NULL;
*/
}
bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec, df::coord pos)
@ -646,7 +635,7 @@ bool Buildings::containsTile(df::building *bld, df::coord2d tile, bool room)
}
else
{
if (tile.x < bld->x1 || tile.x > bld->x2 || tile.y < bld->y1 || tile.y >= bld->y2)
if (tile.x < bld->x1 || tile.x > bld->x2 || tile.y < bld->y1 || tile.y > bld->y2)
return false;
}
@ -1125,18 +1114,21 @@ void Buildings::clearBuildings(color_ostream& out) {
locationToBuilding.clear();
}
void Buildings::updateBuildings(color_ostream& out, void* ptr) {
//out.print("Updating buildings, %s %d\n", __FILE__, __LINE__);
void Buildings::updateBuildings(color_ostream& out, void* ptr)
{
int32_t id = (int32_t)ptr;
if ( corner1.find(id) == corner1.end() ) {
//new building: mark stuff
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id);
if ( index == -1 ) {
out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id);
exit(1);
}
df::building* building = df::global::world->buildings.all[index];
auto building = df::building::find(id);
if (building)
{
// Already cached -> weird, so bail out
if (corner1.count(id))
return;
// Civzones cannot be cached because they can
// overlap each other and normal buildings.
if (!building->isSettingOccupancy())
return;
df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z);
df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z);
@ -1146,18 +1138,24 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) {
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,building->z);
locationToBuilding[pt] = id;
if (containsTile(building, pt, false))
locationToBuilding[pt] = id;
}
}
} else {
}
else if (corner1.count(id))
{
//existing building: destroy it
df::coord p1 = corner1[id];
df::coord p2 = corner2[id];
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,p1.z);
locationToBuilding.erase(pt);
auto cur = locationToBuilding.find(pt);
if (cur != locationToBuilding.end() && cur->second == id)
locationToBuilding.erase(cur);
}
}

@ -8,6 +8,9 @@
#include "df/building.h"
#include "df/construction.h"
#include "df/general_ref.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/job.h"
@ -32,13 +35,13 @@ using namespace EventManager;
**/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
multimap<uint32_t, EventHandler> tickQueue;
static multimap<uint32_t, EventHandler> tickQueue;
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[EventType::EVENT_MAX];
static multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
static uint32_t eventLastTick[EventType::EVENT_MAX];
const uint32_t ticksPerYear = 403200;
static const uint32_t ticksPerYear = 403200;
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
@ -56,6 +59,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug
if ( absolute ) {
tick = 0;
}
handler.freq = 1; //to make manageEvents work more nicely
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
@ -111,6 +115,19 @@ static void manageConstructionEvent(color_ostream& out);
static void manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(color_ostream& out);
static void (*eventManager[])(color_ostream&) = {
manageTickEvent,
manageJobInitiatedEvent,
manageJobCompletedEvent,
manageUnitDeathEvent,
manageItemCreationEvent,
manageBuildingEvent,
manageConstructionEvent,
manageSyndromeEvent,
manageInvasionEvent,
};
//tick event
static uint32_t lastTick = 0;
@ -150,7 +167,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
lastTick = 0;
lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
Job::deleteJobStruct((*i).second, true);
}
prevJobs.clear();
tickQueue.clear();
@ -186,59 +203,32 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) {
return;
}
CoreSuspender suspender;
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( tick <= lastTick )
return;
lastTick = tick;
/*if ( tick - lastTick > 1 ) {
out.print("EventManager missed tick: %d, %d, (%d)\n", lastTick, tick, tick - lastTick);
}*/
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
int32_t min = 1000000000;
if ( handlers[a].empty() )
continue;
int32_t eventFrequency = 1000000000;
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < min )
min = bob.freq;
if ( bob.freq < eventFrequency )
eventFrequency = bob.freq;
}
eventFrequency[a] = min;
if ( tick - eventLastTick[a] < eventFrequency )
continue;
eventManager[a](out);
eventLastTick[a] = tick;
}
manageTickEvent(out);
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
}
return;
lastTick = tick;
}
static void manageTickEvent(color_ostream& out) {
@ -255,9 +245,6 @@ static void manageTickEvent(color_ostream& out) {
}
static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1;
return;
@ -281,10 +268,23 @@ static void manageJobInitiatedEvent(color_ostream& out) {
lastJobId = *df::global::job_next_id - 1;
}
static void manageJobCompletedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
return;
//helper function for manageJobCompletedEvent
static int32_t getWorkerID(df::job* job) {
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue;
return ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
}
return -1;
}
/*
TODO: consider checking item creation / experience gain just in case
*/
static void manageJobCompletedEvent(color_ostream& out) {
uint32_t tick0 = eventLastTick[EventType::JOB_COMPLETED];
uint32_t tick1 = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
@ -293,12 +293,97 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue;
nowJobs[link->item->id] = link->item;
}
#if 0
//testing info on job initiation/completion
//newly allocated jobs
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
if ( prevJobs.find((*j).first) != prevJobs.end() )
continue;
df::job& job1 = *(*j).second;
out.print("new job\n"
" location : 0x%X\n"
" id : %d\n"
" type : %d %s\n"
" working : %d\n"
" completion_timer : %d\n"
" workerID : %d\n"
" time : %d -> %d\n"
"\n", job1.list_link->item, job1.id, job1.job_type, ENUM_ATTR(job_type, caption, job1.job_type), job1.flags.bits.working, job1.completion_timer, getWorkerID(&job1), tick0, tick1);
}
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
if ( nowJobs.find((*i).first) != nowJobs.end() )
df::job& job0 = *(*i).second;
auto j = nowJobs.find((*i).first);
if ( j == nowJobs.end() ) {
out.print("job deallocated\n"
" location : 0x%X\n"
" id : %d\n"
" type : %d %s\n"
" working : %d\n"
" completion_timer : %d\n"
" workerID : %d\n"
" time : %d -> %d\n"
,job0.list_link == NULL ? 0 : job0.list_link->item, job0.id, job0.job_type, ENUM_ATTR(job_type, caption, job0.job_type), job0.flags.bits.working, job0.completion_timer, getWorkerID(&job0), tick0, tick1);
continue;
//recently finished or cancelled job!
}
df::job& job1 = *(*j).second;
if ( job0.flags.bits.working == job1.flags.bits.working &&
(job0.completion_timer == job1.completion_timer || (job1.completion_timer > 0 && job0.completion_timer-1 == job1.completion_timer)) &&
getWorkerID(&job0) == getWorkerID(&job1) )
continue;
out.print("job change\n"
" location : 0x%X -> 0x%X\n"
" id : %d -> %d\n"
" type : %d -> %d\n"
" type : %s -> %s\n"
" working : %d -> %d\n"
" completion timer : %d -> %d\n"
" workerID : %d -> %d\n"
" time : %d -> %d\n"
"\n",
job0.list_link->item, job1.list_link->item,
job0.id, job1.id,
job0.job_type, job1.job_type,
ENUM_ATTR(job_type, caption, job0.job_type), ENUM_ATTR(job_type, caption, job1.job_type),
job0.flags.bits.working, job1.flags.bits.working,
job0.completion_timer, job1.completion_timer,
getWorkerID(&job0), getWorkerID(&job1),
tick0, tick1
);
}
#endif
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
//if it happened within a tick, must have been cancelled by the user or a plugin: not completed
if ( tick1 <= tick0 )
continue;
if ( nowJobs.find((*i).first) != nowJobs.end() ) {
//could have just finished if it's a repeat job
df::job& job0 = *(*i).second;
if ( !job0.flags.bits.repeat )
continue;
df::job& job1 = *nowJobs[(*i).first];
if ( job0.completion_timer != 0 )
continue;
if ( job1.completion_timer != -1 )
continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)&job0);
}
continue;
}
//recently finished or cancelled job
df::job& job0 = *(*i).second;
if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue;
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second);
}
@ -306,7 +391,7 @@ static void manageJobCompletedEvent(color_ostream& out) {
//erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
Job::deleteJobStruct((*i).second, true);
}
prevJobs.clear();
@ -320,18 +405,9 @@ static void manageJobCompletedEvent(color_ostream& out) {
df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob;
}
/*//get rid of old pointers to deallocated jobs
for ( size_t a = 0; a < toDelete.size(); a++ ) {
prevJobs.erase(a);
}*/
}
static void manageUnitDeathEvent(color_ostream& out) {
if ( handlers[EventType::UNIT_DEATH].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
@ -351,10 +427,6 @@ static void manageUnitDeathEvent(color_ostream& out) {
}
static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) {
return;
}
@ -387,9 +459,6 @@ static void manageBuildingEvent(color_ostream& out) {
* TODO: could be faster
* consider looking at jobs: building creation / destruction
**/
if ( handlers[EventType::BUILDING].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
@ -431,9 +500,6 @@ static void manageBuildingEvent(color_ostream& out) {
}
static void manageConstructionEvent(color_ostream& out) {
if ( handlers[EventType::CONSTRUCTION].empty() )
return;
unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*,EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
@ -462,9 +528,6 @@ static void manageConstructionEvent(color_ostream& out) {
}
static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
df::unit* unit = *a;
@ -486,9 +549,6 @@ static void manageSyndromeEvent(color_ostream& out) {
}
static void manageInvasionEvent(color_ostream& out) {
if ( handlers[EventType::INVASION].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion )

@ -55,53 +55,68 @@ using namespace std;
using namespace DFHack;
using namespace df::enums;
df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData)
df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepEverything)
{
CHECK_NULL_POINTER(job);
df::job *pnew = new df::job(*job);
// Clean out transient fields
pnew->flags.whole = 0;
pnew->flags.bits.repeat = job->flags.bits.repeat;
pnew->flags.bits.suspend = job->flags.bits.suspend;
if ( !keepEverything ) {
// Clean out transient fields
pnew->flags.whole = 0;
pnew->flags.bits.repeat = job->flags.bits.repeat;
pnew->flags.bits.suspend = job->flags.bits.suspend;
pnew->completion_timer = -1;
}
pnew->list_link = NULL;
pnew->completion_timer = -1;
pnew->items.clear();
pnew->specific_refs.clear();
// Clone refs
for (int i = pnew->general_refs.size()-1; i >= 0; i--)
{
df::general_ref *ref = pnew->general_refs[i];
if (!keepWorkerData && virtual_cast<df::general_ref_unit_workerst>(ref))
vector_erase_at(pnew->general_refs, i);
else
pnew->general_refs[i] = ref->clone();
//pnew->items.clear();
//pnew->specific_refs.clear();
pnew->general_refs.clear();
//pnew->job_items.clear();
if ( keepEverything ) {
for ( int a = 0; a < pnew->items.size(); a++ )
pnew->items[a] = new df::job_item_ref(*pnew->items[a]);
for ( int a = 0; a < pnew->specific_refs.size(); a++ )
pnew->specific_refs[a] = new df::specific_ref(*pnew->specific_refs[a]);
} else {
pnew->items.clear();
pnew->specific_refs.clear();
}
// Clone items
for (int i = pnew->job_items.size()-1; i >= 0; i--)
pnew->job_items[i] = new df::job_item(*pnew->job_items[i]);
for ( int a = 0; a < pnew->job_items.size(); a++ )
pnew->job_items[a] = new df::job_item(*pnew->job_items[a]);
for ( int a = 0; a < job->general_refs.size(); a++ )
if ( keepEverything || job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
pnew->general_refs.push_back(job->general_refs[a]->clone());
return pnew;
}
void DFHack::Job::deleteJobStruct(df::job *job)
void DFHack::Job::deleteJobStruct(df::job *job, bool keptEverything)
{
if (!job)
return;
// Only allow free-floating job structs
assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
for (int i = job->general_refs.size()-1; i >= 0; i--)
delete job->general_refs[i];
for (int i = job->job_items.size()-1; i >= 0; i--)
delete job->job_items[i];
if ( !keptEverything )
assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
else
assert(!job->list_link);
if ( keptEverything ) {
for ( int a = 0; a < job->items.size(); a++ )
delete job->items[a];
for ( int a = 0; a < job->specific_refs.size(); a++ )
delete job->specific_refs[a];
}
for ( int a = 0; a < job->job_items.size(); a++ )
delete job->job_items[a];
for ( int a = 0; a < job->general_refs.size(); a++ )
delete job->general_refs[a];
delete job;
}

@ -525,7 +525,7 @@ bool Maps::ReadGeology(vector<vector<int16_t> > *layer_mats, vector<df::coord2d>
return true;
}
bool Maps::canPathBetween(df::coord pos1, df::coord pos2)
bool Maps::canWalkBetween(df::coord pos1, df::coord pos2)
{
auto block1 = getTileBlock(pos1);
auto block2 = getTileBlock(pos2);

@ -766,7 +766,7 @@ bool Materials::ReadCreatureTypesEx (void)
for(size_t k = 0; k < sizecolormod;k++)
{
// color mod [0] -> color list
auto & indexes = colorings[k]->color_indexes;
auto & indexes = colorings[k]->pattern_index;
size_t sizecolorlist = indexes.size();
caste.ColorModifier[k].colorlist.resize(sizecolorlist);
for(size_t l = 0; l < sizecolorlist; l++)

@ -0,0 +1,16 @@
#include "modules/Once.h"
#include <unordered_set>
using namespace std;
static unordered_set<string> thingsDone;
bool DFHack::Once::alreadyDone(string bob) {
return thingsDone.find(bob) != thingsDone.end();
}
bool DFHack::Once::doOnce(string bob) {
return thingsDone.insert(bob).second;
}

@ -1 +1 @@
Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be
Subproject commit 20ecaa0393df1ea111861d67c789aaaa56a37c58

@ -11,5 +11,4 @@ else
export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs
export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs
fi
export DYLD_FORCE_FLAT_NAMESPACE=1
cd "${PWD}"; ./dwarfort.exe

@ -36,7 +36,7 @@ old_tty_settings=$(stty -g)
# Now run
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
case "$1" in
-g | --gdb)

@ -3,6 +3,6 @@
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
exec hack/dfhack-run "$@"

@ -1,7 +1,7 @@
#!/bin/sh
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
./libs/Dwarf_Fortress $* # Go, go, go! :)

@ -0,0 +1,8 @@
#!/bin/sh
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
./isoworld/isoworld "$@"

@ -1,11 +1,7 @@
INCLUDE(Plugins.cmake)
# Dfusion plugin
IF(UNIX)
OPTION(BUILD_DFUSION "Build DFusion." OFF)
ELSE()
OPTION(BUILD_DFUSION "Build DFusion." ON)
ENDIF()
OPTION(BUILD_DFUSION "Build DFusion." ON)
if(BUILD_DFUSION)
add_subdirectory (Dfusion)
endif()
@ -15,6 +11,20 @@ if(BUILD_STONESENSE)
add_subdirectory (stonesense)
endif()
OPTION(BUILD_ISOWORLD "Build isoworld (needs a checkout first)." OFF)
if(BUILD_ISOWORLD)
add_subdirectory (isoworld)
IF(UNIX)
if (APPLE)
#TODO: add an OSX runner script
else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/runisoworld
DESTINATION .)
endif()
ENDIF()
endif()
OPTION(BUILD_DEV_PLUGINS "Build developer plugins." OFF)
if(BUILD_DEV_PLUGINS)
add_subdirectory (devel)
@ -65,6 +75,7 @@ ADD_CUSTOM_COMMAND(
${PROJECT_PROTOS}
DEPENDS protoc-bin ${PROJECT_PROTOS}
)
add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS})
SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
@ -114,7 +125,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(tweak tweak.cpp)
DFHACK_PLUGIN(feature feature.cpp)
DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(zone zone.cpp)
DFHACK_PLUGIN(zone zone.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(catsplosion catsplosion.cpp)
DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(forceequip forceequip.cpp)
@ -127,13 +138,28 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(steam-engine steam-engine.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(reactionhooks reactionhooks.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(add-spatter add-spatter.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(workNow workNow.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp)
DFHACK_PLUGIN(syndromeTrigger syndromeTrigger.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
DFHACK_PLUGIN(digSmart digSmart.cpp)
DFHACK_PLUGIN(createitem createitem.cpp)
DFHACK_PLUGIN(outsideOnly outsideOnly.cpp)
DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote)
DFHACK_PLUGIN(buildingplan buildingplan.cpp)
DFHACK_PLUGIN(resume resume.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp)
DFHACK_PLUGIN(mousequery mousequery.cpp)
DFHACK_PLUGIN(autotrade autotrade.cpp)
DFHACK_PLUGIN(stocks stocks.cpp)
DFHACK_PLUGIN(treefarm treefarm.cpp)
endif()
# this is the skeleton plugin. If you want to make your own, make a copy and then change it

@ -65,14 +65,22 @@ MACRO(DFHACK_PLUGIN)
)
CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS})
CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS})
SET(PLUGIN_PROTOCPP)
FOREACH(pbuf ${PLUGIN_PROTOBUFS})
SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc)
SET(PLUGIN_PROTOCPP ${PLUGIN_PROTOCPP} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc)
ENDFOREACH()
# Tell CMake the source won't be available until build time.
SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES GENERATED 1)
ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES})
IDE_FOLDER(${PLUGIN_NAME} "Plugins")
# Make sure the source is generated before the executable builds.
ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto)
LIST(LENGTH PLUGIN_PROTOBUFS NUM_PROTO)
IF(NUM_PROTO)
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite ${PLUGIN_LINK_LIBRARIES})

@ -0,0 +1,481 @@
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/Job.h"
#include "modules/Maps.h"
#include "modules/Once.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/caste_raw.h"
#include "df/creature_interaction_effect.h"
#include "df/creature_raw.h"
#include "df/general_ref.h"
#include "df/general_ref_building_holderst.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/item_boulderst.h"
#include "df/job.h"
#include "df/job_type.h"
#include "df/reaction.h"
#include "df/reaction_product.h"
#include "df/reaction_product_type.h"
#include "df/reaction_product_itemst.h"
#include "df/syndrome.h"
#include "df/unit_syndrome.h"
#include "df/ui.h"
#include "df/unit.h"
#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using namespace DFHack;
static bool enabled = false;
namespace ResetPolicy {
typedef enum {DoNothing, ResetDuration, AddDuration, NewInstance} ResetPolicy;
}
DFHACK_PLUGIN("autoSyndrome");
command_result autoSyndrome(color_ostream& out, vector<string>& parameters);
void processJob(color_ostream& out, void* jobPtr);
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome, ResetPolicy::ResetPolicy policy);
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false,
"autoSyndrome:\n"
" autoSyndrome 0 //disable\n"
" autoSyndrome 1 //enable\n"
" autoSyndrome disable //disable\n"
" autoSyndrome enable //enable\n"
"\n"
"autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the unit that finished that job the syndrome specified in the raw files. See Readme.rst for full details.\n"
));
//EventManager::EventHandler handle(processJob, 5);
//EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream& out) {
return CR_OK;
}
/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) {
return CR_OK;
}*/
command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
bool wasEnabled = enabled;
if ( parameters.size() == 1 ) {
if ( parameters[0] == "enable" ) {
enabled = true;
} else if ( parameters[0] == "disable" ) {
enabled = false;
} else {
int32_t a = atoi(parameters[0].c_str());
if ( a < 0 || a > 1 )
return CR_WRONG_USAGE;
enabled = (bool)a;
}
}
out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled");
if ( enabled == wasEnabled )
return CR_OK;
EventManager::unregisterAll(plugin_self);
if ( enabled ) {
EventManager::EventHandler handle(processJob, 0);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
}
return CR_OK;
}
bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit, ResetPolicy::ResetPolicy policy) {
df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race];
df::caste_raw* caste = creature->caste[unit->caste];
std::string& creature_name = creature->creature_id;
std::string& creature_caste = caste->caste_id;
//check that the syndrome applies to that guy
/*
* If there is no affected class or affected creature, then anybody who isn't immune is fair game.
*
* Otherwise, it works like this:
* add all the affected class creatures
* add all the affected creatures
* remove all the immune class creatures
* remove all the immune creatures
* you're affected if and only if you're in the remaining list after all of that
**/
bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0;
for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) {
if ( applies )
break;
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) {
applies = true;
break;
}
}
}
for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) {
if ( !applies )
break;
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) {
applies = false;
return false;
}
}
}
if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) {
if ( DFHack::Once::doOnce("autoSyndrome: different affected creature/caste sizes.") ) {
out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__);
}
return false;
}
for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) {
if ( creature_name != *syndrome->syn_affected_creature[c] )
continue;
if ( *syndrome->syn_affected_caste[c] == "ALL" ||
*syndrome->syn_affected_caste[c] == creature_caste ) {
applies = true;
break;
}
}
for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) {
if ( creature_name != *syndrome->syn_immune_creature[c] )
continue;
if ( *syndrome->syn_immune_caste[c] == "ALL" ||
*syndrome->syn_immune_caste[c] == creature_caste ) {
applies = false;
return false;
}
}
if ( !applies ) {
return false;
}
if ( giveSyndrome(out, workerId, syndrome, policy) < 0 )
return false;
return true;
}
void processJob(color_ostream& out, void* jobPtr) {
CoreSuspender suspender;
df::job* job = (df::job*)jobPtr;
if ( job == NULL ) {
if ( DFHack::Once::doOnce("autoSyndrome_processJob_null job") )
out.print("Error %s line %d: null job.\n", __FILE__, __LINE__);
return;
}
if ( job->completion_timer > 0 )
return;
if ( job->job_type != df::job_type::CustomReaction )
return;
df::reaction* reaction = NULL;
for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) {
df::reaction* candidate = df::global::world->raws.reactions[a];
if ( candidate->code != job->reaction_name )
continue;
reaction = candidate;
break;
}
if ( reaction == NULL ) {
if ( DFHack::Once::doOnce("autoSyndrome processJob couldNotFind") )
out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() );
return;
}
int32_t workerId = -1;
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue;
if ( workerId != -1 ) {
if ( DFHack::Once::doOnce("autoSyndrome processJob two workers same job") )
out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__);
}
workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
if (workerId == -1) {
if ( DFHack::Once::doOnce("autoSyndrome processJob invalid worker") )
out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
continue;
}
}
df::unit* worker = df::unit::find(workerId);
if ( worker == NULL ) {
//out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
//this probably means that it finished before EventManager could get a copy of the job while the job was running
//TODO: consider printing a warning once
return;
}
//find the building that made it
int32_t buildingId = -1;
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER )
continue;
if ( buildingId != -1 ) {
if ( DFHack::Once::doOnce("autoSyndrome processJob two buildings same job") )
out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__);
}
buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id;
if (buildingId == -1) {
if ( DFHack::Once::doOnce("autoSyndrome processJob invalid building") )
out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__);
continue;
}
}
df::building* building = df::building::find(buildingId);
if ( building == NULL ) {
if ( DFHack::Once::doOnce("autoSyndrome processJob couldn't find building") )
out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId);
return;
}
//find all of the products it makes. Look for a stone.
for ( size_t a = 0; a < reaction->products.size(); a++ ) {
bool appliedSomething = false;
df::reaction_product_type type = reaction->products[a]->getType();
//out.print("type = %d\n", (int32_t)type);
if ( type != df::enums::reaction_product_type::item )
continue;
df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a];
//out.print("item_type = %d\n", (int32_t)bob->item_type);
if ( bob->item_type != df::enums::item_type::BOULDER )
continue;
if ( bob->mat_index < 0 )
continue;
//for now don't worry about subtype
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index];
//maybe add each syndrome to the guy who did the job, or someone in the building, and maybe execute a command
for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) {
df::syndrome* syndrome = inorganic->material.syndrome[b];
bool workerOnly = true;
bool allowMultipleTargets = false;
bool foundCommand = false;
bool destroyRock = true;
bool foundAutoSyndrome = false;
ResetPolicy::ResetPolicy policy = ResetPolicy::NewInstance;
string commandStr;
vector<string> args;
for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) {
std::string& clazz = *syndrome->syn_class[c];
//special syn_classes
if ( clazz == "\\AUTO_SYNDROME" ) {
foundAutoSyndrome = true;
continue;
} else if ( clazz == "\\ALLOW_NONWORKER_TARGETS" ) {
workerOnly = false;
continue;
} else if ( clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
allowMultipleTargets = true;
continue;
} else if ( clazz == "\\PRESERVE_ROCK" ) {
destroyRock = false;
continue;
} else if ( clazz == "\\RESET_POLICY DoNothing" ) {
policy = ResetPolicy::DoNothing;
continue;
} else if ( clazz == "\\RESET_POLICY ResetDuration" ) {
policy = ResetPolicy::ResetDuration;
continue;
} else if ( clazz == "\\RESET_POLICY AddDuration" ) {
policy = ResetPolicy::AddDuration;
continue;
} else if ( clazz == "\\RESET_POLICY NewInstance" ) {
policy = ResetPolicy::NewInstance;
continue;
}
//special arguments for a DFHack console command
if ( foundCommand ) {
if ( commandStr == "" ) {
commandStr = clazz;
} else {
stringstream bob;
if ( clazz == "\\LOCATION" ) {
bob << job->pos.x;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << job->pos.y;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << job->pos.z;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( clazz == "\\WORKER_ID" ) {
bob << workerId;
args.push_back(bob.str());
} else if ( clazz == "\\REACTION_INDEX" ) {
bob << reaction->index;
args.push_back(bob.str());
} else {
args.push_back(clazz);
}
}
} else if ( clazz == "\\COMMAND" ) {
foundCommand = true;
}
}
if ( !foundAutoSyndrome ) {
continue;
}
if ( commandStr != "" ) {
Core::getInstance().runCommand(out, commandStr, args);
}
if ( destroyRock ) {
//find the rock and kill it before it can boil and cause problems and ugliness
for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) {
df::item* item = df::global::world->items.all[c];
if ( item->pos.z != building->z )
continue;
if ( item->pos.x < building->x1 || item->pos.x > building->x2 )
continue;
if ( item->pos.y < building->y1 || item->pos.y > building->y2 )
continue;
if ( item->getType() != df::enums::item_type::BOULDER )
continue;
//make sure it's the right type of boulder
df::item_boulderst* boulder = (df::item_boulderst*)item;
if ( boulder->mat_index != bob->mat_index )
continue;
boulder->flags.bits.hidden = true;
boulder->flags.bits.forbid = true;
boulder->flags.bits.garbage_collect = true;
}
}
if ( maybeApply(out, syndrome, workerId, worker, policy) ) {
appliedSomething = true;
}
if ( workerOnly )
continue;
if ( appliedSomething && !allowMultipleTargets )
continue;
//now try applying it to everybody inside the building
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit == worker )
continue; //we already tried giving it to him, so no doubling up
if ( unit->pos.z != building->z )
continue;
if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 )
continue;
if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 )
continue;
if ( maybeApply(out, syndrome, unit->id, unit, policy) ) {
appliedSomething = true;
if ( !allowMultipleTargets )
break;
}
}
}
}
return;
}
/*
* Heavily based on https://gist.github.com/4061959/
**/
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome, ResetPolicy::ResetPolicy policy) {
df::unit* unit = df::unit::find(workerId);
if ( !unit ) {
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome couldn't find unit") )
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
return -1;
}
if ( policy != ResetPolicy::NewInstance ) {
//figure out if already there
for ( size_t a = 0; a < unit->syndromes.active.size(); a++ ) {
df::unit_syndrome* unitSyndrome = unit->syndromes.active[a];
if ( unitSyndrome->type != syndrome->id )
continue;
int32_t most = 0;
switch(policy) {
case ResetPolicy::DoNothing:
return -1;
case ResetPolicy::ResetDuration:
for ( size_t b = 0; b < unitSyndrome->symptoms.size(); b++ ) {
unitSyndrome->symptoms[b]->ticks = 0; //might cause crashes with transformations
}
unitSyndrome->ticks = 0;
break;
case ResetPolicy::AddDuration:
if ( unitSyndrome->symptoms.size() != syndrome->ce.size() ) {
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome incorrect symptom count") )
out.print("%s, line %d. Incorrect symptom count %d != %d\n", __FILE__, __LINE__, unitSyndrome->symptoms.size(), syndrome->ce.size());
break;
}
for ( size_t b = 0; b < unitSyndrome->symptoms.size(); b++ ) {
if ( syndrome->ce[b]->end == -1 )
continue;
unitSyndrome->symptoms[b]->ticks -= syndrome->ce[b]->end;
if ( syndrome->ce[b]->end > most )
most = syndrome->ce[b]->end;
}
unitSyndrome->ticks -= most;
break;
default:
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome invalid reset policy") )
out.print("%s, line %d: invalid reset policy %d.\n", __FILE__, __LINE__, policy);
return -1;
}
return 0;
}
}
df::unit_syndrome* unitSyndrome = new df::unit_syndrome();
unitSyndrome->type = syndrome->id;
unitSyndrome->year = DFHack::World::ReadCurrentYear();
unitSyndrome->year_time = DFHack::World::ReadCurrentTick();
unitSyndrome->ticks = 0;
unitSyndrome->unk1 = 0;
unitSyndrome->flags = 0; //TODO: typecast?
for ( size_t a = 0; a < syndrome->ce.size(); a++ ) {
df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms();
symptom->unk1 = 0;
symptom->unk2 = 0;
symptom->ticks = 0;
symptom->flags = 2; //TODO: ???
unitSyndrome->symptoms.push_back(symptom);
}
unit->syndromes.active.push_back(unitSyndrome);
return 0;
}

@ -679,6 +679,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" also tries to have dwarves specialize in specific skills.\n"
" Warning: autolabor will override any manual changes you make to labors\n"
" while it is enabled.\n"
" To prevent particular dwarves from being managed by autolabor, put them\n"
" in any burrow.\n"
"Examples:\n"
" autolabor MINE 2\n"
" Keep at least 2 dwarves with mining enabled.\n"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,619 @@
#include "uicommon.h"
#include "modules/Gui.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "df/building_def.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h"
#include "modules/Items.h"
#include "df/building_tradedepotst.h"
#include "df/general_ref_building_holderst.h"
#include "df/job.h"
#include "df/job_item_ref.h"
#include "modules/Job.h"
#include "df/ui.h"
#include "df/caravan_state.h"
#include "df/mandate.h"
#include "modules/Maps.h"
#include "modules/World.h"
using df::global::world;
using df::global::cursor;
using df::global::ui;
using df::building_stockpilest;
DFHACK_PLUGIN("autotrade");
#define PLUGIN_VERSION 0.2
/*
* Stockpile Access
*/
static building_stockpilest *get_selected_stockpile()
{
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) ||
ui->main.mode != ui_sidebar_mode::QueryBuilding)
{
return nullptr;
}
return virtual_cast<building_stockpilest>(world->selected_building);
}
static bool can_trade()
{
if (df::global::ui->caravans.size() == 0)
return false;
for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++)
{
auto caravan = *it;
auto trade_state = caravan->trade_state;
auto time_remaining = caravan->time_remaining;
if ((trade_state != 1 && trade_state != 2) || time_remaining == 0)
return false;
}
return true;
}
class StockpileInfo {
public:
StockpileInfo(df::building_stockpilest *sp_) : sp(sp_)
{
readBuilding();
}
StockpileInfo(PersistentDataItem &config)
{
this->config = config;
id = config.ival(1);
}
bool inStockpile(df::item *i)
{
df::item *container = Items::getContainer(i);
if (container)
return inStockpile(container);
if (i->pos.z != z) return false;
if (i->pos.x < x1 || i->pos.x >= x2 ||
i->pos.y < y1 || i->pos.y >= y2) return false;
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
return sp->room.extents[e] == 1;
}
bool isValid()
{
auto found = df::building::find(id);
return found && found == sp && found->getType() == building_type::Stockpile;
}
bool load()
{
auto found = df::building::find(id);
if (!found || found->getType() != building_type::Stockpile)
return false;
sp = virtual_cast<df::building_stockpilest>(found);
if (!sp)
return false;
readBuilding();
return true;
}
int32_t getId()
{
return id;
}
bool matches(df::building_stockpilest* sp)
{
return this->sp == sp;
}
void save()
{
config = DFHack::World::AddPersistentData("autotrade/stockpiles");
config.ival(1) = id;
}
void remove()
{
DFHack::World::DeletePersistentData(config);
}
private:
PersistentDataItem config;
df::building_stockpilest* sp;
int x1, x2, y1, y2, z;
int32_t id;
void readBuilding()
{
id = sp->id;
z = sp->z;
x1 = sp->room.x;
x2 = sp->room.x + sp->room.width;
y1 = sp->room.y;
y2 = sp->room.y + sp->room.height;
}
};
/*
* Depot Access
*/
class TradeDepotInfo
{
public:
TradeDepotInfo() : depot(0)
{
}
bool findDepot()
{
if (isValid())
return true;
reset();
for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++)
{
auto bld = *bld_it;
if (!isUsableDepot(bld))
continue;
depot = bld;
id = depot->id;
break;
}
return depot;
}
bool assignItem(df::item *item)
{
auto href = df::allocate<df::general_ref_building_holderst>();
if (!href)
return false;
auto job = new df::job();
df::coord tpos(depot->centerx, depot->centery, depot->z);
job->pos = tpos;
job->job_type = job_type::BringItemToDepot;
// job <-> item link
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled))
{
delete job;
delete href;
return false;
}
// job <-> building link
href->building_id = id;
depot->jobs.push_back(job);
job->general_refs.push_back(href);
// add to job list
Job::linkIntoWorld(job);
return true;
}
void reset()
{
depot = 0;
}
private:
int32_t id;
df::building *depot;
bool isUsableDepot(df::building* bld)
{
if (bld->getType() != building_type::TradeDepot)
return false;
if (bld->getBuildStage() < bld->getMaxBuildStage())
return false;
if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding)
return false;
return true;
}
bool isValid()
{
if (!depot)
return false;
auto found = df::building::find(id);
return found && found == depot && isUsableDepot(found);
}
};
static TradeDepotInfo depot_info;
/*
* Item Manipulation
*/
static bool check_mandates(df::item *item)
{
for (auto it = world->mandates.begin(); it != world->mandates.end(); it++)
{
auto mandate = *it;
if (mandate->mode != 0)
continue;
if (item->getType() != mandate->item_type ||
(mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype))
continue;
if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type)
continue;
if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index)
continue;
return false;
}
return true;
}
static bool is_valid_item(df::item *item)
{
for (size_t i = 0; i < item->general_refs.size(); i++)
{
df::general_ref *ref = item->general_refs[i];
switch (ref->getType())
{
case general_ref_type::CONTAINED_IN_ITEM:
return false;
case general_ref_type::UNIT_HOLDER:
return false;
case general_ref_type::BUILDING_HOLDER:
return false;
default:
break;
}
}
for (size_t i = 0; i < item->specific_refs.size(); i++)
{
df::specific_ref *ref = item->specific_refs[i];
if (ref->type == specific_ref_type::JOB)
{
// Ignore any items assigned to a job
return false;
}
}
if (!check_mandates(item))
return false;
return true;
}
static void mark_all_in_stockpiles(vector<StockpileInfo> &stockpiles, bool announce)
{
if (!depot_info.findDepot())
{
if (announce)
Gui::showAnnouncement("Cannot trade, no valid depot available", COLOR_RED, true);
return;
}
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact);
F(spider_web); F(owned); F(in_job);
#undef F
size_t marked_count = 0;
size_t error_count = 0;
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
if (!is_valid_item(item))
continue;
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
{
if (!it->inStockpile(item))
continue;
// In case of container, check contained items for mandates
bool mandates_ok = true;
vector<df::item*> contained_items;
Items::getContainedItems(item, &contained_items);
for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++)
{
if (!check_mandates(*cit))
{
mandates_ok = false;
break;
}
}
if (!mandates_ok)
continue;
if (depot_info.assignItem(item))
{
++marked_count;
}
else
{
if (++error_count < 5)
{
Gui::showZoomAnnouncement(df::announcement_type::CANCEL_JOB, item->pos,
"Cannot trade item from stockpile " + int_to_string(it->getId()), COLOR_RED, true);
}
}
}
}
if (marked_count)
Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items for trade", COLOR_GREEN, false);
else if (announce)
Gui::showAnnouncement("No more items to mark", COLOR_RED, true);
if (error_count >= 5)
{
Gui::showAnnouncement(int_to_string(error_count) + " items were not marked", COLOR_RED, true);
}
}
/*
* Stockpile Monitoring
*/
class StockpileMonitor
{
public:
bool isMonitored(df::building_stockpilest *sp)
{
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
{
if (it->matches(sp))
return true;
}
return false;
}
void add(df::building_stockpilest *sp)
{
auto pile = StockpileInfo(sp);
if (pile.isValid())
{
monitored_stockpiles.push_back(StockpileInfo(sp));
monitored_stockpiles.back().save();
}
}
void remove(df::building_stockpilest *sp)
{
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
{
if (it->matches(sp))
{
it->remove();
monitored_stockpiles.erase(it);
break;
}
}
}
void doCycle()
{
if (!can_trade())
return;
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();)
{
if (!it->isValid())
{
it = monitored_stockpiles.erase(it);
continue;
}
++it;
}
mark_all_in_stockpiles(monitored_stockpiles, false);
}
void reset()
{
monitored_stockpiles.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "autotrade/stockpiles");
for (auto i = items.begin(); i != items.end(); i++)
{
auto pile = StockpileInfo(*i);
if (pile.load())
monitored_stockpiles.push_back(StockpileInfo(pile));
else
pile.remove();
}
}
private:
vector<StockpileInfo> monitored_stockpiles;
};
static StockpileMonitor monitor;
#define DELTA_TICKS 600
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if(!Maps::IsValid())
return CR_OK;
static decltype(world->frame_counter) last_frame_count = 0;
if (DFHack::World::ReadPauseState())
return CR_OK;
if (world->frame_counter - last_frame_count < DELTA_TICKS)
return CR_OK;
last_frame_count = world->frame_counter;
monitor.doCycle();
return CR_OK;
}
/*
* Interface
*/
struct trade_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool handleInput(set<df::interface_key> *input)
{
building_stockpilest *sp = get_selected_stockpile();
if (!sp)
return false;
if (input->count(interface_key::CUSTOM_M))
{
if (!can_trade())
return false;
vector<StockpileInfo> wrapper;
wrapper.push_back(StockpileInfo(sp));
mark_all_in_stockpiles(wrapper, true);
return true;
}
else if (input->count(interface_key::CUSTOM_U))
{
if (monitor.isMonitored(sp))
monitor.remove(sp);
else
monitor.add(sp);
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handleInput(input))
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
building_stockpilest *sp = get_selected_stockpile();
if (!sp)
return;
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = 23;
if (can_trade())
OutputHotkeyString(x, y, "Mark all for trade", "m", true, left_margin);
OutputToggleString(x, y, "Auto trade", "u", monitor.isMonitored(sp), true, left_margin);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, render);
static command_result autotrade_cmd(color_ostream &out, vector <string> & parameters)
{
if (!parameters.empty())
{
if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
{
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event)
{
case DFHack::SC_MAP_LOADED:
depot_info.reset();
monitor.reset();
break;
case DFHack::SC_MAP_UNLOADED:
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(trade_hook, feed).apply() || !INTERPOSE_HOOK(trade_hook, render).apply())
out.printerr("Could not insert autotrade hooks!\n");
commands.push_back(
PluginCommand(
"autotrade", "Automatically send items in marked stockpiles to trade depot, when trading is possible.",
autotrade_cmd, false, ""));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,264 @@
// Create arbitrary items
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "MiscUtils.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Materials.h"
#include "DataDefs.h"
#include "df/game_type.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/historical_entity.h"
#include "df/world_site.h"
#include "df/item.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/reaction_reagent.h"
#include "df/reaction_product_itemst.h"
using namespace std;
using namespace DFHack;
using df::global::world;
using df::global::ui;
using df::global::gametype;
DFHACK_PLUGIN("createitem");
command_result df_createitem (color_ostream &out, vector <string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &commands)
{
commands.push_back(PluginCommand("createitem", "Create arbitrary item at the selected unit's feet.", df_createitem));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_item = false)
{
vector<df::item *> out_items;
vector<df::reaction_reagent *> in_reag;
vector<df::item *> in_items;
bool is_gloves = (prod->item_type == df::item_type::GLOVES);
bool is_shoes = (prod->item_type == df::item_type::SHOES);
prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
df::historical_entity::find(unit->civ_id),
((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL);
if (!out_items.size())
return false;
// if we asked to make shoes and we got twice as many as we asked, then we're okay
// otherwise, make a second set because shoes are normally made in pairs
if (is_shoes && out_items.size() == prod->count * 2)
is_shoes = false;
for (size_t i = 0; i < out_items.size(); i++)
{
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
if (is_gloves)
{
// if the reaction creates gloves without handedness, then create 2 sets (left and right)
if (out_items[i]->getGloveHandedness() > 0)
is_gloves = false;
else
out_items[i]->setGloveHandedness(second_item ? 2 : 1);
}
}
if ((is_gloves || is_shoes) && !second_item)
return makeItem(prod, unit, true);
return true;
}
command_result df_createitem (color_ostream &out, vector <string> & parameters)
{
string item_str, material_str;
df::item_type item_type = df::item_type::NONE;
int16_t item_subtype = -1;
int16_t mat_type = -1;
int32_t mat_index = -1;
int count = 1;
if ((parameters.size() < 2) || (parameters.size() > 3))
{
out.print("Syntax: createitem <item> <material> [count]\n"
" <item> - Item token for what you wish to create, as specified in custom\n"
" reactions. If the item has no subtype, omit the :NONE.\n"
" <material> - The material you want the item to be made of, as specified\n"
" in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n"
" PET, and EGG, replace this with a creature ID and caste.\n"
" [count] - How many of the item you wish to create.\n"
);
return CR_WRONG_USAGE;
}
item_str = parameters[0];
material_str = parameters[1];
if (parameters.size() == 3)
{
stringstream ss(parameters[2]);
ss >> count;
if (count < 1)
{
out.printerr("You cannot produce less than one item!\n");
return CR_FAILURE;
}
}
ItemTypeInfo item;
MaterialInfo material;
vector<string> tokens;
if (!item.find(item_str))
{
out.printerr("Unrecognized item type!\n");
return CR_FAILURE;
}
item_type = item.type;
item_subtype = item.subtype;
switch (item.type)
{
case df::item_type::INSTRUMENT:
case df::item_type::TOY:
case df::item_type::WEAPON:
case df::item_type::ARMOR:
case df::item_type::SHOES:
case df::item_type::SHIELD:
case df::item_type::HELM:
case df::item_type::GLOVES:
case df::item_type::AMMO:
case df::item_type::PANTS:
case df::item_type::SIEGEAMMO:
case df::item_type::TRAPCOMP:
case df::item_type::TOOL:
if (item_subtype == -1)
{
out.printerr("You must specify a subtype!\n");
return CR_FAILURE;
}
default:
if (!material.find(material_str))
{
out.printerr("Unrecognized material!\n");
return CR_FAILURE;
}
mat_type = material.type;
mat_index = material.index;
break;
case df::item_type::REMAINS:
case df::item_type::FISH:
case df::item_type::FISH_RAW:
case df::item_type::VERMIN:
case df::item_type::PET:
case df::item_type::EGG:
split_string(&tokens, material_str, ":");
if (tokens.size() != 2)
{
out.printerr("You must specify a creature ID and caste for this item type!\n");
return CR_FAILURE;
}
for (size_t i = 0; i < world->raws.creatures.all.size(); i++)
{
df::creature_raw *creature = world->raws.creatures.all[i];
if (creature->creature_id == tokens[0])
{
for (size_t j = 0; j < creature->caste.size(); j++)
{
df::caste_raw *caste = creature->caste[j];
if (creature->caste[j]->caste_id == tokens[1])
{
mat_type = i;
mat_index = j;
break;
}
}
if (mat_type == -1)
{
out.printerr("The creature you specified has no such caste!\n");
return CR_FAILURE;
}
}
}
if (mat_type == -1)
{
out.printerr("Unrecognized creature ID!\n");
return CR_FAILURE;
}
break;
case df::item_type::CORPSE:
case df::item_type::CORPSEPIECE:
case df::item_type::FOOD:
out.printerr("Cannot create that type of item!\n");
return CR_FAILURE;
break;
}
CoreSuspender suspend;
df::unit *unit = Gui::getSelectedUnit(out, true);
if (!unit)
{
out.printerr("No unit selected!\n");
return CR_FAILURE;
}
if (!Maps::IsValid())
{
out.printerr("Map is not available.\n");
return CR_FAILURE;
}
df::map_block *block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z);
if (block == NULL)
{
out.printerr("Unit does not reside in a valid map block, somehow?\n");
return CR_FAILURE;
}
df::reaction_product_itemst *prod = df::allocate<df::reaction_product_itemst>();
prod->item_type = item_type;
prod->item_subtype = item_subtype;
prod->mat_type = mat_type;
prod->mat_index = mat_index;
prod->probability = 100;
prod->count = count;
switch (item_type)
{
case df::item_type::BAR:
case df::item_type::POWDER_MISC:
case df::item_type::LIQUID_MISC:
case df::item_type::DRINK:
prod->product_dimension = 150;
break;
case df::item_type::THREAD:
prod->product_dimension = 15000;
break;
case df::item_type::CLOTH:
prod->product_dimension = 10000;
break;
default:
prod->product_dimension = 1;
break;
}
bool result = makeItem(prod, unit);
delete prod;
if (!result)
{
out.printerr("Failed to create item!\n");
return CR_FAILURE;
}
return CR_OK;
}

@ -18,7 +18,10 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(autolabor2 autolabor2.cpp)
DFHACK_PLUGIN(eventExample eventExample.cpp)
DFHACK_PLUGIN(printArgs printArgs.cpp)
DFHACK_PLUGIN(onceExample onceExample.cpp)
IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF()

File diff suppressed because it is too large Load Diff

@ -7,6 +7,7 @@
#include "DataDefs.h"
#include "df/item.h"
#include "df/job.h"
#include "df/world.h"
#include <vector>
@ -34,11 +35,11 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginC
}
command_result eventExample(color_ostream& out, vector<string>& parameters) {
EventManager::EventHandler initiateHandler(jobInitiated, 10);
EventManager::EventHandler completeHandler(jobCompleted, 5);
EventManager::EventHandler initiateHandler(jobInitiated, 1);
EventManager::EventHandler completeHandler(jobCompleted, 0);
EventManager::EventHandler timeHandler(timePassed, 1);
EventManager::EventHandler deathHandler(unitDeath, 500);
EventManager::EventHandler itemHandler(itemCreate, 1000);
EventManager::EventHandler itemHandler(itemCreate, 1);
EventManager::EventHandler buildingHandler(building, 500);
EventManager::EventHandler constructionHandler(construction, 100);
EventManager::EventHandler syndromeHandler(syndrome, 1);
@ -62,8 +63,17 @@ command_result eventExample(color_ostream& out, vector<string>& parameters) {
return CR_OK;
}
void jobInitiated(color_ostream& out, void* job) {
out.print("Job initiated! 0x%X\n", job);
//static int timerCount=0;
//static int timerDenom=0;
void jobInitiated(color_ostream& out, void* job_) {
out.print("Job initiated! 0x%X\n", job_);
/*
df::job* job = (df::job*)job_;
out.print(" completion_timer = %d\n", job->completion_timer);
if ( job->completion_timer != -1 ) timerCount++;
timerDenom++;
out.print(" frac = %d / %d\n", timerCount, timerDenom);
*/
}
void jobCompleted(color_ostream& out, void* job) {

@ -0,0 +1,35 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Once.h"
using namespace DFHack;
using namespace df::enums;
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("onceExample");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"onceExample", "Test the doOnce command.",
onceExample, false,
" This command tests the doOnce command..\n"
));
return CR_OK;
}
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters)
{
out.print("Already done = %d.\n", DFHack::Once::alreadyDone("onceExample_1"));
if ( DFHack::Once::doOnce("onceExample_1") ) {
out.print("Printing this message once!\n");
}
return CR_OK;
}

@ -0,0 +1,32 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include <iostream>
using namespace DFHack;
using namespace std;
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("printArgs");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"printArgs", "Print the arguments given.",
printArgs, false
));
return CR_OK;
}
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters)
{
for ( size_t a = 0; a < parameters.size(); a++ ) {
out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl;
}
return CR_OK;
}

@ -80,7 +80,7 @@ df::coord prev;
command_result stepBetween (color_ostream &out, std::vector <std::string> & parameters)
{
df::coord bob = Gui::getCursorPos();
out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canPathBetween(prev,bob));
out.print("(%d,%d,%d), (%d,%d,%d): canStepBetween = %d, canWalkBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob));
prev = bob;
return CR_OK;

@ -55,7 +55,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" Also follows the stone between z-levels with stairs, like 'digl x' would.\n"
));
commands.push_back(PluginCommand("digexp","Select or designate an exploratory pattern. Use 'digexp ?' for help.",digexp));
commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow) with given radius.",digcircle));
commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow) with given diameter.",digcircle));
//commands.push_back(PluginCommand("digauto","Mark a tile for continuous digging.",autodig));
commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey,
"For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated.\n"
@ -120,7 +120,7 @@ bool dig (MapExtras::MapCache & MCache,
if(tileMaterial(tt) == tiletype_material::CONSTRUCTION && !des.bits.hidden)
return false;
df::tiletype_shape ts = tileShape(tt);
if (ts == tiletype_shape::EMPTY)
if (ts == tiletype_shape::EMPTY && !des.bits.hidden)
return false;
if(!des.bits.hidden)
{
@ -293,7 +293,7 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
"\n"
"After you have set the options, the command called with no options\n"
"repeats with the last selected parameters:\n"
"'digcircle filled 3' = Dig a filled circle with radius = 3.\n"
"'digcircle filled 3' = Dig a filled circle with diameter = 3.\n"
"'digcircle' = Do it again.\n"
);
return CR_OK;

@ -0,0 +1,199 @@
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/MapCache.h"
#include "modules/Maps.h"
#include "df/coord.h"
#include "df/global_objects.h"
#include "df/job.h"
#include "df/map_block.h"
#include "df/tile_dig_designation.h"
#include "df/world.h"
#include <set>
#include <string>
#include <vector>
using namespace DFHack;
using namespace std;
command_result digSmart (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("digSmart");
void onDig(color_ostream& out, void* ptr);
void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set<df::coord>& jobLocations);
EventManager::EventHandler digHandler(onDig, 0);
bool enabled = false;
bool digAll = false;
set<string> autodigMaterials;
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"digSmart", "Automatically dig out veins as you discover them.",
digSmart, false,
"Example:\n"
" digSmart 0\n"
" disable plugin\n"
" digSmart 1\n"
" enable plugin\n"
" digSmart 0 MICROCLINE COAL_BITUMINOUS 1\n"
" disable plugin and remove microcline and bituminous coal from being monitored, then re-enable plugin"
" digSmart 1 MICROCLINE 0 COAL_BITUMINOUS 1\n"
" don't monitor microcline, do monitor COAL_BITUMINOUS, then enable plugin\n"
" digSmart CLEAR\n"
" remove all inorganics from monitoring\n"
" digSmart digAll1\n"
" enable digAll mode: dig any vein, regardless of the monitor list\n"
" digSmart digAll0\n"
" disable digAll mode\n"
"\n"
"Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your dfhack.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n"
));
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, digHandler, plugin_self);
return CR_OK;
}
void onDig(color_ostream& out, void* ptr) {
CoreSuspender bob;
df::job* job = (df::job*)ptr;
if ( job->completion_timer > 0 )
return;
if ( job->job_type != df::enums::job_type::Dig &&
job->job_type != df::enums::job_type::CarveUpwardStaircase &&
job->job_type != df::enums::job_type::CarveDownwardStaircase &&
job->job_type != df::enums::job_type::CarveUpDownStaircase &&
job->job_type != df::enums::job_type::CarveRamp &&
job->job_type != df::enums::job_type::DigChannel )
return;
set<df::coord> jobLocations;
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->job_type != df::enums::job_type::Dig &&
link->item->job_type != df::enums::job_type::CarveUpwardStaircase &&
link->item->job_type != df::enums::job_type::CarveDownwardStaircase &&
link->item->job_type != df::enums::job_type::CarveUpDownStaircase &&
link->item->job_type != df::enums::job_type::CarveRamp &&
link->item->job_type != df::enums::job_type::DigChannel )
continue;
jobLocations.insert(link->item->pos);
}
MapExtras::MapCache cache;
df::coord pos = job->pos;
for ( int16_t a = -1; a <= 1; a++ ) {
for ( int16_t b = -1; b <= 1; b++ ) {
maybeExplore(out, cache, df::coord(pos.x+a,pos.y+b,pos.z), jobLocations);
}
}
cache.trash();
}
void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set<df::coord>& jobLocations) {
if ( !Maps::isValidTilePos(pt) ) {
return;
}
df::map_block* block = Maps::getTileBlock(pt);
if (!block)
return;
if ( block->designation[pt.x&0xF][pt.y&0xF].bits.hidden )
return;
df::tiletype type = block->tiletype[pt.x&0xF][pt.y&0xF];
if ( ENUM_ATTR(tiletype, material, type) != df::enums::tiletype_material::MINERAL )
return;
if ( ENUM_ATTR(tiletype, shape, type) != df::enums::tiletype_shape::WALL )
return;
if ( block->designation[pt.x&0xF][pt.y&0xF].bits.dig != df::enums::tile_dig_designation::No )
return;
uint32_t xMax,yMax,zMax;
Maps::getSize(xMax,yMax,zMax);
if ( pt.x == 0 || pt.y == 0 || pt.x+1 == xMax*16 || pt.y+1 == yMax*16 )
return;
if ( jobLocations.find(pt) != jobLocations.end() ) {
return;
}
int16_t mat = cache.veinMaterialAt(pt);
if ( mat == -1 )
return;
if ( !digAll ) {
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[mat];
if ( autodigMaterials.find(inorganic->id) == autodigMaterials.end() ) {
return;
}
}
block->designation[pt.x&0xF][pt.y&0xF].bits.dig = df::enums::tile_dig_designation::Default;
block->flags.bits.designated = true;
// *df::global::process_dig = true;
// *df::global::process_jobs = true;
}
command_result digSmart (color_ostream &out, std::vector <std::string> & parameters)
{
bool adding = true;
set<string> toAdd, toRemove;
for ( size_t a = 0; a < parameters.size(); a++ ) {
int32_t i = (int32_t)strtol(parameters[a].c_str(), NULL, 0);
if ( i == 0 && parameters[a] == "0" ) {
EventManager::unregisterAll(plugin_self);
adding = false;
continue;
} else if ( i == 1 ) {
EventManager::unregisterAll(plugin_self);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, digHandler, plugin_self);
adding = true;
continue;
}
if ( parameters[a] == "CLEAR" )
autodigMaterials.clear();
if ( parameters[a] == "digAll0" ) {
digAll = false;
continue;
}
if ( parameters[a] == "digAll1" ) {
digAll = true;
continue;
}
for ( size_t b = 0; b < df::global::world->raws.inorganics.size(); b++ ) {
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[b];
if ( parameters[a] == inorganic->id ) {
if ( adding )
toAdd.insert(parameters[a]);
else
toRemove.insert(parameters[a]);
goto loop;
}
}
out.print("Could not find material \"%s\".\n", parameters[a].c_str());
return CR_WRONG_USAGE;
loop: continue;
}
autodigMaterials.insert(toAdd.begin(), toAdd.end());
for ( auto a = toRemove.begin(); a != toRemove.end(); a++ )
autodigMaterials.erase(*a);
return CR_OK;
}

@ -154,7 +154,9 @@ int64_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2) {
//if you're moving down, and you're on a bridge, and that bridge is lowered, then you can't do it
if ( building1 && building1->getType() == df::building_type::Bridge ) {
df::building_bridgest* bridge = (df::building_bridgest*)building2;
if ( bridge->gate_flags.bits.closed
if ( bridge->gate_flags.bits.closed ) {
return -1;
}
}
bool forbidden = false;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,327 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <string.h>
#include <VTableInterpose.h>
#include "df/building_workshopst.h"
#include "df/unit.h"
#include "df/item.h"
#include "df/item_actual.h"
#include "df/unit_wound.h"
#include "df/world.h"
#include "df/reaction.h"
#include "df/reaction_reagent_itemst.h"
#include "df/reaction_product_itemst.h"
#include "df/proj_itemst.h"
#include "df/proj_unitst.h"
#include "MiscUtils.h"
#include "LuaTools.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
typedef df::reaction_product_itemst item_product;
DFHACK_PLUGIN("eventful");
struct ReagentSource {
int idx;
df::reaction_reagent *reagent;
ReagentSource() : idx(-1), reagent(NULL) {}
};
struct MaterialSource : ReagentSource {
bool product;
std::string product_name;
int mat_type, mat_index;
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
};
struct ProductInfo {
df::reaction *react;
item_product *product;
MaterialSource material;
bool isValid() {
return true;//due to mat_type being -1 = any
}
};
struct ReactionInfo {
df::reaction *react;
std::vector<ProductInfo> products;
};
static std::map<std::string, ReactionInfo> reactions;
static std::map<df::reaction_product*, ProductInfo*> products;
static ReactionInfo *find_reaction(const std::string &name)
{
auto it = reactions.find(name);
return (it != reactions.end()) ? &it->second : NULL;
}
static bool is_lua_hook(const std::string &name)
{
return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0;
}
/*
* Hooks
*/
static void handle_fillsidebar(color_ostream &out,df::building_workshopst*,bool *call_native){};
static void handle_postfillsidebar(color_ostream &out,df::building_workshopst*){};
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
, std::vector<df::item*> *out_items,bool *call_native){};
static void handle_contaminate_wound(color_ostream &out,df::item_actual*,df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2){};
static void handle_projitem_ci(color_ostream &out,df::proj_itemst*,bool){};
static void handle_projitem_cm(color_ostream &out,df::proj_itemst*){};
static void handle_projunit_ci(color_ostream &out,df::proj_unitst*,bool){};
static void handle_projunit_cm(color_ostream &out,df::proj_unitst*){};
DEFINE_LUA_EVENT_2(onWorkshopFillSidebarMenu, handle_fillsidebar, df::building_workshopst*,bool* );
DEFINE_LUA_EVENT_1(postWorkshopFillSidebarMenu, handle_postfillsidebar, df::building_workshopst*);
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t );
//projectiles
DEFINE_LUA_EVENT_2(onProjItemCheckImpact, handle_projitem_ci, df::proj_itemst*,bool );
DEFINE_LUA_EVENT_1(onProjItemCheckMovement, handle_projitem_cm, df::proj_itemst*);
DEFINE_LUA_EVENT_2(onProjUnitCheckImpact, handle_projunit_ci, df::proj_unitst*,bool );
DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* );
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu),
DFHACK_LUA_EVENT(postWorkshopFillSidebarMenu),
DFHACK_LUA_EVENT(onReactionComplete),
DFHACK_LUA_EVENT(onItemContaminateWound),
DFHACK_LUA_EVENT(onProjItemCheckImpact),
DFHACK_LUA_EVENT(onProjItemCheckMovement),
DFHACK_LUA_EVENT(onProjUnitCheckImpact),
DFHACK_LUA_EVENT(onProjUnitCheckMovement),
DFHACK_LUA_END
};
struct workshop_hook : df::building_workshopst{
typedef df::building_workshopst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void,fillSidebarMenu,())
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onWorkshopFillSidebarMenu(out,this,&call_native);
if(call_native)
INTERPOSE_NEXT(fillSidebarMenu)();
postWorkshopFillSidebarMenu(out,this);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, fillSidebarMenu);
struct product_hook : item_product {
typedef item_product interpose_base;
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items,
int32_t quantity, df::job_skill skill,
df::historical_entity *entity, df::world_site *site)
) {
if (auto product = products[this])
{
df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
if(!call_native)
return;
}
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
struct item_hooks :df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, contaminateWound,(df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2))
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onItemContaminateWound(out,this,unit,wound,a1,a2);
INTERPOSE_NEXT(contaminateWound)(unit,wound,a1,a2);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(item_hooks, contaminateWound);
struct proj_item_hook: df::proj_itemst{
typedef df::proj_itemst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode))
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjItemCheckImpact(out,this,mode);
return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not?
}
DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,())
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjItemCheckMovement(out,this);
return INTERPOSE_NEXT(checkMovement)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkImpact);
IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkMovement);
struct proj_unit_hook: df::proj_unitst{
typedef df::proj_unitst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode))
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjUnitCheckImpact(out,this,mode);
return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not?
}
DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,())
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjUnitCheckMovement(out,this);
return INTERPOSE_NEXT(checkMovement)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkImpact);
IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkMovement);
/*
* Scan raws for matching reactions.
*/
static void parse_product(
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) {
info.react = react;
info.product = prod;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
}
static bool find_reactions(color_ostream &out)
{
reactions.clear();
auto &rlist = world->raws.reactions;
for (size_t i = 0; i < rlist.size(); i++)
{
if (!is_lua_hook(rlist[i]->code))
continue;
reactions[rlist[i]->code].react = rlist[i];
}
for (auto it = reactions.begin(); it != reactions.end(); ++it)
{
auto &prod = it->second.react->products;
auto &out_prod = it->second.products;
for (size_t i = 0; i < prod.size(); i++)
{
auto itprod = strict_virtual_cast<item_product>(prod[i]);
if (!itprod) continue;
out_prod.push_back(ProductInfo());
parse_product(out, out_prod.back(), it->second.react, itprod);
}
for (size_t i = 0; i < prod.size(); i++)
{
if (out_prod[i].isValid())
products[out_prod[i].product] = &out_prod[i];
}
}
return !products.empty();
}
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(workshop_hook,fillSidebarMenu).apply(enable);
INTERPOSE_HOOK(item_hooks,contaminateWound).apply(enable);
INTERPOSE_HOOK(proj_unit_hook,checkImpact).apply(enable);
INTERPOSE_HOOK(proj_unit_hook,checkMovement).apply(enable);
INTERPOSE_HOOK(proj_item_hook,checkImpact).apply(enable);
INTERPOSE_HOOK(proj_item_hook,checkMovement).apply(enable);
}
static void world_specific_hooks(color_ostream &out,bool enable)
{
if(enable && find_reactions(out))
{
out.print("Detected reaction hooks - enabling plugin.\n");
INTERPOSE_HOOK(product_hook, produce).apply(true);
}
else
{
INTERPOSE_HOOK(product_hook, produce).apply(false);
reactions.clear();
products.clear();
}
}
void disable_all_hooks(color_ostream &out)
{
world_specific_hooks(out,false);
enable_hooks(false);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_WORLD_LOADED:
world_specific_hooks(out,true);
break;
case SC_WORLD_UNLOADED:
world_specific_hooks(out,false);
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_WORLD_LOADED);
enable_hooks(true);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
disable_all_hooks(out);
return CR_OK;
}

@ -49,7 +49,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (!Units::isCitizen(unit))
continue;
if (enable_fastdwarf)
if (enable_fastdwarf && !enable_teledwarf )
{
if (unit->counters.job_counter > 0)
unit->counters.job_counter = 0;
@ -59,8 +59,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (enable_teledwarf) do
{
// don't do anything if the dwarf isn't going anywhere
if (!unit->path.dest.isValid())
if (!unit->pos.isValid() || !unit->path.dest.isValid() || unit->pos == unit->path.dest) {
if ( enable_fastdwarf && unit->counters.job_counter > 0 )
unit->counters.job_counter = 0;
break;
}
// skip dwarves that are dragging creatures or being dragged
if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1))

@ -23,6 +23,8 @@ typedef void (*checkTile)(DFCoord, MapExtras::MapCache &);
//Forward Declarations for Commands
command_result filltraffic(color_ostream &out, std::vector<std::string> & params);
command_result alltraffic(color_ostream &out, std::vector<std::string> & params);
command_result restrictLiquid(color_ostream &out, std::vector<std::string> & params);
command_result restrictIce(color_ostream &out, std::vector<std::string> & params);
//Forward Declarations for Utility Functions
command_result setAllMatching(color_ostream &out, checkTile checkProc,
@ -34,6 +36,9 @@ void allNormal(DFCoord coord, MapExtras::MapCache & map);
void allLow(DFCoord coord, MapExtras::MapCache & map);
void allRestricted(DFCoord coord, MapExtras::MapCache & map);
void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map);
void restrictIceProc(DFCoord coord, MapExtras::MapCache &map);
DFHACK_PLUGIN("filltraffic");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
@ -66,6 +71,14 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" L: Low Traffic\n"
" R: Restricted Traffic\n"
));
commands.push_back(PluginCommand(
"restrictliquids","Restrict on every visible square with liquid",
restrictLiquid, false, ""
));
commands.push_back(PluginCommand(
"restrictice","Restrict traffic on squares above visible ice",
restrictIce, false, ""
));
return CR_OK;
}
@ -265,6 +278,16 @@ command_result alltraffic(color_ostream &out, std::vector<std::string> & params)
return setAllMatching(out, proc);
}
command_result restrictLiquid(color_ostream &out, std::vector<std::string> & params)
{
return setAllMatching(out, restrictLiquidProc);
}
command_result restrictIce(color_ostream &out, std::vector<std::string> & params)
{
return setAllMatching(out, restrictIceProc);
}
//Helper function for writing new functions that check every tile on the map.
//newTraffic is the traffic designation to set.
//check takes a coordinate and the map cache as arguments, and returns true if the criteria is met.
@ -356,3 +379,33 @@ void allRestricted(DFCoord coord, MapExtras::MapCache &map)
des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des);
}
//Restrict traffic if tile is visible and liquid is present.
void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map)
{
df::tile_designation des = map.designationAt(coord);
if ((des.bits.hidden == 0) && (des.bits.flow_size != 0))
{
des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des);
}
}
//Restrict traffice if tile is above visible ice wall.
void restrictIceProc(DFCoord coord, MapExtras::MapCache &map)
{
//There is no ice below the bottom of the map.
if (coord.z == 0)
return;
DFCoord tile_below = DFCoord(coord.x, coord.y, coord.z - 1);
df::tiletype tt = map.tiletypeAt(tile_below);
df::tile_designation des = map.designationAt(tile_below);
if ((des.bits.hidden == 0) && (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID))
{
des = map.designationAt(coord);
des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des);
}
}

@ -477,7 +477,7 @@ static bool try_store_item(df::building *target, df::item *item)
df::coord tpos(target->centerx, target->centery, target->z);
df::coord ipos = Items::getPosition(item);
if (!Maps::canPathBetween(tpos, ipos))
if (!Maps::canWalkBetween(tpos, ipos))
return false;
// Check if the target has enough space left

@ -0,0 +1,182 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/World.h"
#include "df/construction.h"
#include "df/game_mode.h"
#include "df/map_block.h"
#include "df/map_block_column.h"
#include "df/world.h"
#include "df/z_level_flags.h"
#include <cstring>
#include <string>
#include <vector>
using namespace std;
using namespace DFHack;
using namespace df::enums;
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("infiniteSky");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"infiniteSky",
"Creates new sky levels on request, or as you construct up.",
infiniteSky, false,
"Usage:\n"
" infiniteSky\n"
" creates one more z-level\n"
" infiniteSky [n]\n"
" creates n more z-level(s)\n"
" infiniteSky enable\n"
" enables monitoring of constructions\n"
" infiniteSky disable\n"
" disable monitoring of constructions\n"
"\n"
"If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
/*
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_GAME_LOADED:
// initialize from the world just loaded
break;
case SC_GAME_UNLOADED:
// cleanup
break;
default:
break;
}
return CR_OK;
}
*/
static size_t constructionSize = 0;
static bool enabled = false;
void doInfiniteSky(color_ostream& out, int32_t howMany);
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if ( !enabled )
return CR_OK;
if ( !Core::getInstance().isMapLoaded() )
return CR_OK;
{
t_gamemodes mode;
if ( !World::ReadGameMode(mode) )
return CR_FAILURE;
if ( mode.g_mode != df::enums::game_mode::DWARF )
return CR_OK;
}
if ( df::global::world->constructions.size() == constructionSize )
return CR_OK;
int32_t zNow = df::global::world->map.z_count_block;
for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) {
df::construction* construct = df::global::world->constructions[a];
if ( construct->pos.z+2 < zNow )
continue;
doInfiniteSky(out, 1);
zNow = df::global::world->map.z_count_block;
///break;
}
constructionSize = df::global::world->constructions.size();
return CR_OK;
}
void doInfiniteSky(color_ostream& out, int32_t howMany) {
df::world* world = df::global::world;
CoreSuspender suspend;
int32_t x_count_block = world->map.x_count_block;
int32_t y_count_block = world->map.y_count_block;
for ( int32_t count = 0; count < howMany; count++ ) {
//change the size of the pointer stuff
int32_t z_count_block = world->map.z_count_block;
df::map_block**** block_index = world->map.block_index;
for ( int32_t a = 0; a < x_count_block; a++ ) {
for ( int32_t b = 0; b < y_count_block; b++ ) {
df::map_block** blockColumn = new df::map_block*[z_count_block+1];
memcpy(blockColumn, block_index[a][b], z_count_block*sizeof(df::map_block*));
blockColumn[z_count_block] = NULL;
delete[] block_index[a][b];
block_index[a][b] = blockColumn;
//deal with map_block_column stuff even though it'd probably be fine
df::map_block_column* column = world->map.column_index[a][b];
if ( !column ) {
out.print("%s, line %d: column is null (%d, %d).\n", __FILE__, __LINE__, a, b);
continue;
}
df::map_block_column::T_unmined_glyphs* glyphs = new df::map_block_column::T_unmined_glyphs;
glyphs->x[0] = 0;
glyphs->x[1] = 1;
glyphs->x[2] = 2;
glyphs->x[3] = 3;
glyphs->y[0] = 0;
glyphs->y[1] = 0;
glyphs->y[2] = 0;
glyphs->y[3] = 0;
glyphs->tile[0] = 'e';
glyphs->tile[1] = 'x';
glyphs->tile[2] = 'p';
glyphs->tile[3] = '^';
column->unmined_glyphs.push_back(glyphs);
}
}
df::z_level_flags* flags = new df::z_level_flags[z_count_block+1];
memcpy(flags, world->map.z_level_flags, z_count_block*sizeof(df::z_level_flags));
flags[z_count_block].whole = 0;
flags[z_count_block].bits.update = 1;
world->map.z_count_block++;
world->map.z_count++;
delete[] world->map.z_level_flags;
world->map.z_level_flags = flags;
}
}
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters)
{
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
if ( parameters.size() == 0 ) {
out.print("Construction monitoring is %s.\n", enabled ? "enabled" : "disabled");
return CR_OK;
}
if (parameters[0] == "enable") {
enabled = true;
out.print("Construction monitoring enabled.\n");
return CR_OK;
}
if (parameters[0] == "disable") {
enabled = false;
out.print("Construction monitoring disabled.\n");
constructionSize = 0;
return CR_OK;
}
int32_t howMany = 0;
howMany = atoi(parameters[0].c_str());
out.print("InfiniteSky: creating %d new z-level%s of sky.\n", howMany, howMany == 1 ? "" : "s" );
doInfiniteSky(out, howMany);
return CR_OK;
}

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

@ -0,0 +1,366 @@
// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
// some headers required for a plugin. Nothing special, just the basics.
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "df/world.h"
#include "df/map_block.h"
#include "df/builtin_mats.h"
#include "df/tile_designation.h"
//DFhack specific headers
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/Materials.h"
//Needed for writing the protobuff stuff to a file.
#include <fstream>
#include <string>
#include <iomanip>
#include "isoworldremote.pb.h"
#include "RemoteServer.h"
using namespace DFHack;
using namespace df::enums;
using namespace isoworldremote;
// Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom
command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters);
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out);
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out);
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out);
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP);
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP);
// A plugin must be able to return its name and version.
// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case
DFHACK_PLUGIN("isoworldremote");
// Mandatory init function. If you have some global state, create it here.
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
//// Fill the command list with your commands.
//commands.push_back(PluginCommand(
// "isoworldremote", "Dump north-west embark tile to text file for debug purposes.",
// isoWorldRemote, false, /* true means that the command can't be used from non-interactive user interface */
// // Extended help string. Used by CR_WRONG_USAGE and the help command:
// " This command does nothing at all.\n"
// "Example:\n"
// " isoworldremote\n"
// " Does nothing.\n"
//));
return CR_OK;
}
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
{
RPCService *svc = new RPCService();
svc->addFunction("GetEmbarkTile", GetEmbarkTile);
svc->addFunction("GetEmbarkInfo", GetEmbarkInfo);
svc->addFunction("GetRawNames", GetRawNames);
return svc;
}
// This is called right before the plugin library is removed from memory.
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
// You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash.
return CR_OK;
}
// Called to notify the plugin about important state changes.
// Invoked with DF suspended, and always before the matching plugin_onupdate.
// More event codes may be added in the future.
/*
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_GAME_LOADED:
// initialize from the world just loaded
break;
case SC_GAME_UNLOADED:
// cleanup
break;
default:
break;
}
return CR_OK;
}
*/
// Whatever you put here will be done in each game step. Don't abuse it.
// It's optional, so you can just comment it out like this if you don't need it.
/*
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
// whetever. You don't need to suspend DF execution here.
return CR_OK;
}
*/
//// A command! It sits around and looks pretty. And it's nice and friendly.
//command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters)
//{
// // It's nice to print a help message you get invalid options
// // from the user instead of just acting strange.
// // This can be achieved by adding the extended help string to the
// // PluginCommand registration as show above, and then returning
// // CR_WRONG_USAGE from the function. The same string will also
// // be used by 'help your-command'.
// if (!parameters.empty())
// return CR_WRONG_USAGE;
// // Commands are called from threads other than the DF one.
// // Suspend this thread until DF has time for us. If you
// // use CoreSuspender, it'll automatically resume DF when
// // execution leaves the current scope.
// CoreSuspender suspend;
// // Actually do something here. Yay.
// out.print("Doing a test...\n");
// MapExtras::MapCache MC;
// EmbarkTile test_tile;
// if(!gather_embark_tile(0,0, &test_tile, &MC))
// return CR_FAILURE;
// //test-write the file to check it.
// std::ofstream output_file("tile.p", std::ios_base::binary);
// output_file << test_tile.SerializeAsString();
// output_file.close();
//
// //load it again to verify.
// std::ifstream input_file("tile.p", std::ios_base::binary);
// std::string input_string( (std::istreambuf_iterator<char>(input_file) ),
// (std::istreambuf_iterator<char>() ) );
// EmbarkTile verify_tile;
// verify_tile.ParseFromString(input_string);
// //write contents to text file.
// std::ofstream debug_text("tile.txt", std::ios_base::trunc);
// debug_text << "world coords:" << verify_tile.world_x()<< "," << verify_tile.world_y()<< "," << verify_tile.world_z() << std::endl;
// for(int i = 0; i < verify_tile.tile_layer_size(); i++) {
// debug_text << "layer: " << i << std::endl;
// for(int j = 0; j < 48; j++) {
// debug_text << " ";
// for(int k = 0; k < 48; k++) {
// debug_text << verify_tile.tile_layer(i).mat_type_table(j*48+k) << ",";
// }
// debug_text << " ";
// for(int k = 0; k < 48; k++) {
// debug_text << std::setw(3) << verify_tile.tile_layer(i).mat_subtype_table(j*48+k) << ",";
// }
// debug_text << std::endl;
// }
// debug_text << std::endl;
// }
// // Give control back to DF.
// return CR_OK;
//}
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out)
{
MapExtras::MapCache MC;
gather_embark_tile(in->want_x() * 3, in->want_y() * 3, out, &MC);
MC.trash();
return CR_OK;
}
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out)
{
if(!Core::getInstance().isWorldLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!Core::getInstance().isMapLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!df::global::gamemode) {
out->set_available(false);
return CR_OK;
}
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
out->set_available(false);
return CR_OK;
}
if(!DFHack::Maps::IsValid()) {
out->set_available(false);
return CR_OK;
}
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
out->set_available(false);
return CR_OK;
}
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
out->set_available(false);
return CR_OK;
}
out->set_available(true);
out->set_current_year(*df::global::cur_year);
out->set_current_season(*df::global::cur_season);
out->set_region_x(df::global::world->map.region_x);
out->set_region_y(df::global::world->map.region_y);
out->set_region_size_x(df::global::world->map.x_count_block / 3);
out->set_region_size_y(df::global::world->map.y_count_block / 3);
return CR_OK;
}
int coord_to_index_48(int x, int y) {
return y*48+x;
}
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP) {
tile->set_is_valid(false);
tile->set_world_x(df::global::world->map.region_x + (EmbX/3));
tile->set_world_y(df::global::world->map.region_y + (EmbY/3));
tile->set_world_z(df::global::world->map.region_z + 1); //adding one because floors get shifted one downwards.
tile->set_current_year(*df::global::cur_year);
tile->set_current_season(*df::global::cur_season);
int num_valid_layers = 0;
for(int z = 0; z < MP->maxZ(); z++)
{
EmbarkTileLayer * tile_layer = tile->add_tile_layer();
num_valid_layers += gather_embark_tile_layer(EmbX, EmbY, z, tile_layer, MP);
}
if(num_valid_layers > 0)
tile->set_is_valid(true);
return 1;
}
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP)
{
for(int i = tile->mat_type_table_size(); i < 2304; i++) { //This is needed so we have a full array to work with, otherwise the size isn't updated correctly.
tile->add_mat_type_table(AIR);
tile->add_mat_subtype_table(0);
}
int num_valid_blocks = 0;
for(int yy = 0; yy < 3; yy++) {
for(int xx = 0; xx < 3; xx++) {
DFCoord current_coord, upper_coord;
current_coord.x = EmbX+xx;
current_coord.y = EmbY+yy;
current_coord.z = EmbZ;
upper_coord = current_coord;
upper_coord.z += 1;
MapExtras::Block * b = MP->BlockAt(current_coord);
MapExtras::Block * b_upper = MP->BlockAt(upper_coord);
if(b && b->getRaw()) {
for(int block_y=0; block_y<16; block_y++) {
for(int block_x=0; block_x<16; block_x++) {
df::coord2d block_coord;
block_coord.x = block_x;
block_coord.y = block_y;
df::tiletype tile_type = b->tiletypeAt(block_coord);
df::tiletype upper_tile = df::tiletype::Void;
if(b_upper && b_upper->getRaw()) {
upper_tile = b_upper->tiletypeAt(block_coord);
}
df::tile_designation designation = b->DesignationAt(block_coord);
DFHack::t_matpair actual_mat;
if(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor && (tileMaterial(tile_type) != tiletype_material::FROZEN_LIQUID) && (tileMaterial(tile_type) != tiletype_material::BROOK)) { //if the upper tile is a floor, use that material instead. Unless it's ice.
actual_mat = b_upper->staticMaterialAt(block_coord);
}
else {
actual_mat = b->staticMaterialAt(block_coord);
}
if(((tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID) || (tileMaterial(tile_type) == tiletype_material::BROOK)) && (tileShapeBasic(tileShape(tile_type)) == tiletype_shape_basic::Floor)) {
tile_type = tiletype::OpenSpace;
}
unsigned int array_index = coord_to_index_48(xx*16+block_x, yy*16+block_y);
//make a new fake material at the given index
if(tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID && !((tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor) && (tileMaterial(upper_tile) != tiletype_material::FROZEN_LIQUID))) { //Ice.
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID); //Ice is totally a liquid, shut up.
tile->set_mat_subtype_table(array_index, LiquidType::ICE);
num_valid_blocks++;
}
else if(designation.bits.flow_size && (tileShapeBasic(tileShape(upper_tile)) != tiletype_shape_basic::Floor)) { //Contains either water or lava.
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID);
if(designation.bits.liquid_type) //Magma
tile->set_mat_subtype_table(array_index, LiquidType::MAGMA);
else //water
tile->set_mat_subtype_table(array_index, LiquidType::WATER);
num_valid_blocks++;
}
else if(((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Open) ||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor)) &&
((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Floor) ||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor))) { //if the upper tile is a floor, we don't skip, otherwise we do.
if(actual_mat.mat_type == builtin_mats::INORGANIC) { //inorganic
tile->set_mat_type_table(array_index, BasicMaterial::INORGANIC);
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
}
else if(actual_mat.mat_type == 419) { //Growing plants
tile->set_mat_type_table(array_index, BasicMaterial::PLANT);
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
}
else if(actual_mat.mat_type >= 420) { //Wooden constructions. Different from growing plants.
tile->set_mat_type_table(array_index, BasicMaterial::WOOD);
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
}
else { //Unknown and unsupported stuff. Will just be drawn as grey.
tile->set_mat_type_table(array_index, BasicMaterial::OTHER);
tile->set_mat_subtype_table(array_index, actual_mat.mat_type);
}
num_valid_blocks++;
}
else {
tile->set_mat_type_table(array_index, BasicMaterial::AIR);
}
}
}
}
}
}
return (num_valid_blocks >0);
}
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out){
if(!Core::getInstance().isWorldLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!Core::getInstance().isMapLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!df::global::gamemode) {
out->set_available(false);
return CR_OK;
}
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
out->set_available(false);
return CR_OK;
}
if(!DFHack::Maps::IsValid()) {
out->set_available(false);
return CR_OK;
}
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
out->set_available(false);
return CR_OK;
}
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
out->set_available(false);
return CR_OK;
}
out->set_available(true);
for(int i = 0; i < df::global::world->raws.inorganics.size(); i++){
out->add_inorganic(df::global::world->raws.inorganics[i]->id);
}
for(int i = 0; i < df::global::world->raws.plants.all.size(); i++){
out->add_organic(df::global::world->raws.plants.all[i]->id);
}
return CR_OK;
}

@ -178,6 +178,7 @@ end
function BinaryPlugin:move_to_df()
local _,addr=df.sizeof(self.data)
markAsExecutable(addr)
return addr
end
function BinaryPlugin:print_data()
local out=""
@ -220,7 +221,7 @@ function SimpleMenu:display()
local ans
repeat
local r
r=io.stdin:read()
r=dfhack.lineedit()
if r==nil then return end
if r=='q' then return end
ans=tonumber(r)

@ -1,5 +1,6 @@
local _ENV = mkmodule('plugins.dfusion.adv_tools')
local dfu=require("plugins.dfusion")
local tools=require("plugins.dfusion.tools")
menu=dfu.SimpleMenu()
function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
if swap_soul==nil then
@ -9,7 +10,7 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
if adv.flags1.dead==false then
qerror("You are not dead (yet)!")
end
local hist_fig=getNemesis(adv).figure
local hist_fig=dfhack.units.getNemesis(adv).figure
if hist_fig==nil then
qerror("No historical figure for adventurer...")
end
@ -18,9 +19,9 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry
if df.history_event_hist_figure_diedst:is_instance(events[i]) then
--print("is instance:"..i)
if events[i].victim==hist_fig.id then
if events[i].victim_hf==hist_fig.id then
--print("Is same id:"..i)
trg_hist_fig=events[i].slayer
trg_hist_fig=events[i].slayer_hf
if trg_hist_fig then
trg_hist_fig=df.historical_figure.find(trg_hist_fig)
end
@ -38,7 +39,7 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
end
local trg_unit_final=df.unit.find(trg_unit)
tools.change_adv(trg_unit_final)
change_adv(trg_unit_final)
if swap_soul then --actually add a soul...
t_soul=adv.status.current_soul
adv.status.current_soul=df.NULL
@ -53,7 +54,7 @@ function change_adv(unit,nemesis)
nemesis=true --default value is nemesis switch too.
end
if unit==nil then
unit=getCreatureAtPointer()
unit=dfhack.gui.getSelectedUnit()--getCreatureAtPointer()
end
if unit==nil then
error("Invalid unit!")
@ -72,8 +73,8 @@ function change_adv(unit,nemesis)
other[unit_indx]=other[0]
other[0]=unit
if nemesis then --basicly copied from advtools plugin...
local nem=getNemesis(unit)
local other_nem=getNemesis(other[unit_indx])
local nem=dfhack.units.getNemesis(unit)
local other_nem=dfhack.units.getNemesis(other[unit_indx])
if other_nem then
other_nem.flags[0]=false
other_nem.flags[1]=true
@ -113,4 +114,59 @@ function log_pos()
f:close()
end
menu:add("Log adventurers position",log_pos)
function addSite(x,y,rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y,civ_id,name,sitetype)
if x==nil or y==nil then
x=(df.global.world.map.region_x+1)/16
y=(df.global.world.map.region_y+1)/16
end
if name==nil then
name=dfhack.lineedit("Site name:")or "Hacked site"
end
if sitetype==nil then
sitetype=tonumber(dfhack.lineedit("Site type (numeric):")) or 7
end
rgn_max_x=rgn_max_x or (df.global.world.map.region_x+1)%16
rgn_max_y=rgn_max_y or (df.global.world.map.region_y+1)%16
rgn_min_y=rgn_min_y or rgn_max_y
rgn_min_x=rgn_min_x or rgn_max_x
print("Region:",rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y)
--[=[
<angavrilov> global = pos*16 + rgn
<angavrilov> BUT
<angavrilov> for cities global is usually 17x17, i.e. max size
<angavrilov> while rgn designates a small bit in the middle
<angavrilov> for stuff like forts that formula holds exactly
]=]--
local wd=df.global.world.world_data
local nsite=df.world_site:new()
nsite.name.first_name=name
nsite.name.has_name=true
nsite.pos:assign{x=x,y=y}
nsite.rgn_max_x=rgn_max_x
nsite.rgn_min_x=rgn_min_x
nsite.rgn_min_y=rgn_min_y
nsite.rgn_max_y=rgn_max_y
nsite.global_max_x=nsite.pos.x*16+nsite.rgn_max_x
nsite.global_min_x=nsite.pos.x*16+nsite.rgn_min_x
nsite.global_max_y=nsite.pos.y*16+nsite.rgn_max_y
nsite.global_min_y=nsite.pos.y*16+nsite.rgn_min_y
nsite.id=wd.next_site_id
nsite.civ_id=civ_id or -1
nsite.cur_owner_id=civ_id or -1
nsite.type=sitetype --lair = 7
nsite.flags:resize(23)
--nsite.flags[4]=true
--nsite.flags[5]=true
--nsite.flags[6]=true
nsite.index=#wd.sites+1
wd.sites:insert("#",nsite)
wd.next_site_id=wd.next_site_id+1
--might not be needed...
--[[local unk130=df.world_site_unk130:new()
unk130.index=#wd.site_unk130+1
wd.site_unk130:insert("#",unk130)
--wd.next_site_unk136_id=wd.next_site_unk136_id+1--]]
return nsite
end
menu:add("Create site at current location",addSite)
return _ENV

@ -9,9 +9,18 @@ FriendshipRainbow.name="FriendshipRainbow"
-- os independant... I think...
FriendshipRainbow.ATTRS{filename="hack/lua/plugins/dfusion/friendship.o",name="FriendshipRainbow",race_data=DEFAULT_NIL}
FriendshipRainbow.class_status="valid, not installed"
function FriendshipRainbow:findall_needles(codesg,needle) -- todo move to memscan.lua
local cidx,caddr=codesg.uint8_t:find(needle)
local ret={}
while cidx~=nil do
table.insert(ret,{cidx,caddr})
cidx,caddr=codesg.uint8_t:find(needle,cidx+1)
end
return ret
end
function FriendshipRainbow:find_one(codesg,needle,crace)
dfu.concatTables(needle,dfu.dwordToTable(crace))
return codesg.uint8_t:findall(needle)
return self:findall_needles(codesg,needle)
end
function FriendshipRainbow:find_all()
local code=ms.get_code_segment()

@ -26,7 +26,7 @@ function setrace(name)
if name == nil then
print("Type new race's token name in full caps (q to quit):")
repeat
local entry=io.stdin:read()
local entry=dfhack.lineedit()
if entry=="q" then
return
end
@ -48,7 +48,7 @@ function GiveSentience(names)
ids={}
print("Type race's token name in full caps to give sentience to:")
repeat
id=io.stdin:read()
id=dfhack.lineedit()
id=RaceTable[entry]
if id~=nil then
table.insert(ids,id)

@ -0,0 +1,112 @@
local _ENV = mkmodule('plugins.eventful')
--[[
--]]
local function getShopName(btype,bsubtype,bcustom)
local typenames_shop={[df.workshop_type.Carpenters]="CARPENTERS",[df.workshop_type.Farmers]="FARMERS",
[df.workshop_type.Masons]="MASONS",[df.workshop_type.Craftsdwarfs]="CRAFTSDWARFS",
[df.workshop_type.Jewelers]="JEWELERS",[df.workshop_type.MetalsmithsForge]="METALSMITHSFORGE",
[df.workshop_type.MagmaForge]="MAGMAFORGE",[df.workshop_type.Bowyers]="BOWYERS",
[df.workshop_type.Mechanics]="MECHANICS",[df.workshop_type.Siege]="SIEGE",
[df.workshop_type.Butchers]="BUTCHERS",[df.workshop_type.Leatherworks]="LEATHERWORKS",
[df.workshop_type.Tanners]="TANNERS",[df.workshop_type.Clothiers]="CLOTHIERS",
[df.workshop_type.Fishery]="FISHERY",[df.workshop_type.Still]="STILL",
[df.workshop_type.Loom]="LOOM",[df.workshop_type.Quern]="QUERN",
[df.workshop_type.Kennels]="KENNELS",[df.workshop_type.Ashery]="ASHERY",
[df.workshop_type.Kitchen]="KITCHEN",[df.workshop_type.Dyers]="DYERS",
[df.workshop_type.Tool]="TOOL",[df.workshop_type.Millstone]="MILLSTONE",
}
local typenames_furnace={[df.furnace_type.WoodFurnace]="WOOD_FURNACE",[df.furnace_type.Smelter]="SMELTER",
[df.furnace_type.GlassFurnace]="GLASS_FURNACE",[df.furnace_type.MagmaSmelter]="MAGMA_SMELTER",
[df.furnace_type.MagmaGlassFurnace]="MAGMA_GLASS_FURNACE",[df.furnace_type.MagmaKiln]="MAGMA_KILN",
[df.furnace_type.Kiln]="KILN"}
if btype==df.building_type.Workshop then
if typenames_shop[bsubtype]~=nil then
return typenames_shop[bsubtype]
else
return nil --todo add custom (not very useful)
end
elseif btype==df.building_type.Furnace then
if typenames_furnace[bsubtype]~=nil then
return typenames_furnace[bsubtype]
else
return nil --todo add custom (not very useful)
end
end
end
_registeredStuff={}
local function unregall(state)
if state==SC_WORLD_UNLOADED then
onReactionComplete._library=nil
postWorkshopFillSidebarMenu._library=nil
dfhack.onStateChange.eventful= nil
_registeredStuff={}
end
end
local function onReact(reaction,unit,input_items,input_reagents,output_items,call_native)
if _registeredStuff.reactionCallbacks and _registeredStuff.reactionCallbacks[reaction.code] then
_registeredStuff.reactionCallbacks[reaction.code](reaction,unit,input_items,input_reagents,output_items,call_native)
end
end
local function onPostSidebar(workshop)
local shop_id=getShopName(workshop:getType(),workshop:getSubtype(),workshop:getCustomType())
if shop_id then
if _registeredStuff.shopNonNative and _registeredStuff.shopNonNative[shop_id] then
if _registeredStuff.shopNonNative[shop_id].all then
--[[for _,button in ipairs(df.global.ui_sidebar_menus.workshop_job.choices_all) do
button.is_hidden=true
end]]
df.global.ui_sidebar_menus.workshop_job.choices_visible:resize(0)
else
--todo by name
end
end
if _registeredStuff.reactionToShop and _registeredStuff.reactionToShop[shop_id] then
for _,reaction_name in ipairs(_registeredStuff.reactionToShop[shop_id]) do
local new_button=df.interface_button_building_new_jobst:new()
--new_button.hotkey_id=--todo get hotkey
new_button.is_hidden=false
new_button.building=workshop
new_button.job_type=df.job_type.CustomReaction --could be used for other stuff too i guess...
new_button.reaction_name=reaction_name
new_button.is_custom=true
local wjob=df.global.ui_sidebar_menus.workshop_job
wjob.choices_all:insert("#",new_button)
wjob.choices_visible:insert("#",new_button)
end
end
end
end
function registerReaction(reaction_name,callback)
_registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {}
_registeredStuff.reactionCallbacks[reaction_name]=callback
onReactionComplete._library=onReact
dfhack.onStateChange.eventful=unregall
end
function removeNative(shop_name,name)
_registeredStuff.shopNonNative=_registeredStuff.shopNonNative or {}
local shops=_registeredStuff.shopNonNative
shops[shop_name]=shops[shop_name] or {}
if name~=nil then
table.insert(shops[shop_name],name)
else
shops[shop_name].all=true
end
postWorkshopFillSidebarMenu._library=onPostSidebar
dfhack.onStateChange.eventful=unregall
end
function addReactionToShop(reaction_name,shop_name)
_registeredStuff.reactionToShop=_registeredStuff.reactionToShop or {}
local shops=_registeredStuff.reactionToShop
shops[shop_name]=shops[shop_name] or {}
table.insert(shops[shop_name],reaction_name)
postWorkshopFillSidebarMenu._library=onPostSidebar
dfhack.onStateChange.eventful=unregall
end
return _ENV

@ -1,13 +0,0 @@
local _ENV = mkmodule('plugins.reactionhooks')
--[[
Native events:
* onReactionComplete(burrow)
--]]
rawset_default(_ENV, dfhack.reactionhooks)
return _ENV

@ -0,0 +1,12 @@
local _ENV = mkmodule('plugins.zone')
--[[
Native functions:
* autobutcher_isEnabled()
* autowatch_isEnabled()
--]]
return _ENV

@ -0,0 +1,263 @@
#include <sstream>
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
#include "DataDefs.h"
#include "df/building.h"
#include "df/enabler.h"
#include "df/item.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/world.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
using std::set;
using std::string;
using std::ostringstream;
using namespace DFHack;
using namespace df::enums;
using df::global::enabler;
using df::global::gps;
using df::global::world;
using df::global::ui;
static int32_t last_x, last_y, last_z;
static size_t max_list_size = 100000; // Avoid iterating over huge lists
struct mousequery_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
void send_key(const df::interface_key &key)
{
set<df::interface_key> tmp;
tmp.insert(key);
//INTERPOSE_NEXT(feed)(&tmp);
this->feed(&tmp);
}
df::interface_key get_default_query_mode(const int32_t &x, const int32_t &y, const int32_t &z)
{
bool fallback_to_building_query = false;
// Check for unit under cursor
size_t count = world->units.all.size();
if (count <= max_list_size)
{
for(size_t i = 0; i < count; i++)
{
df::unit *unit = world->units.all[i];
if(unit->pos.x == x && unit->pos.y == y && unit->pos.z == z)
return df::interface_key::D_VIEWUNIT;
}
}
else
{
fallback_to_building_query = true;
}
// Check for building under cursor
count = world->buildings.all.size();
if (count <= max_list_size)
{
for(size_t i = 0; i < count; i++)
{
df::building *bld = world->buildings.all[i];
if (z == bld->z &&
x >= bld->x1 && x <= bld->x2 &&
y >= bld->y1 && y <= bld->y2)
{
df::building_type type = bld->getType();
if (type == building_type::Stockpile)
{
fallback_to_building_query = true;
break; // Check for items in stockpile first
}
// For containers use item view, fir everything else, query view
return (type == building_type::Box || type == building_type::Cabinet ||
type == building_type::Weaponrack || type == building_type::Armorstand)
? df::interface_key::D_BUILDITEM : df::interface_key::D_BUILDJOB;
}
}
}
else
{
fallback_to_building_query = true;
}
// Check for items under cursor
count = world->items.all.size();
if (count <= max_list_size)
{
for(size_t i = 0; i < count; i++)
{
df::item *item = world->items.all[i];
if (z == item->pos.z && x == item->pos.x && y == item->pos.y &&
!item->flags.bits.in_building && !item->flags.bits.hidden &&
!item->flags.bits.in_job && !item->flags.bits.in_chest &&
!item->flags.bits.in_inventory)
{
return df::interface_key::D_LOOK;
}
}
}
else
{
fallback_to_building_query = true;
}
return (fallback_to_building_query) ? df::interface_key::D_BUILDJOB : df::interface_key::D_LOOK;
}
bool handle_mouse(const set<df::interface_key> *input)
{
int32_t cx, cy, vz;
if (enabler->tracking_on)
{
if (enabler->mouse_lbut)
{
int32_t mx, my;
if (Gui::getMousePos(mx, my))
{
int32_t vx, vy;
if (Gui::getViewCoords(vx, vy, vz))
{
cx = vx + mx - 1;
cy = vy + my - 1;
using namespace df::enums::ui_sidebar_mode;
df::interface_key key = interface_key::NONE;
bool cursor_still_here = (last_x == cx && last_y == cy && last_z == vz);
switch(ui->main.mode)
{
case QueryBuilding:
if (cursor_still_here)
key = df::interface_key::D_BUILDITEM;
break;
case BuildingItems:
if (cursor_still_here)
key = df::interface_key::D_VIEWUNIT;
break;
case ViewUnits:
if (cursor_still_here)
key = df::interface_key::D_LOOK;
break;
case LookAround:
if (cursor_still_here)
key = df::interface_key::D_BUILDJOB;
break;
case Default:
break;
default:
return false;
}
enabler->mouse_lbut = 0;
// Can't check limits earlier as we must be sure we are in query or default mode we can clear the button flag
// Otherwise the feed gets stuck in a loop
uint8_t menu_width, area_map_width;
Gui::getMenuWidth(menu_width, area_map_width);
int32_t w = gps->dimx;
if (menu_width == 1) w -= 57; //Menu is open doubly wide
else if (menu_width == 2 && area_map_width == 3) w -= 33; //Just the menu is open
else if (menu_width == 2 && area_map_width == 2) w -= 26; //Just the area map is open
if (mx < 1 || mx > w || my < 1 || my > gps->dimy - 2)
return false;
while (ui->main.mode != Default)
{
send_key(df::interface_key::LEAVESCREEN);
}
if (key == interface_key::NONE)
key = get_default_query_mode(cx, cy, vz);
send_key(key);
// Force UI refresh
Gui::setCursorCoords(cx, cy, vz);
send_key(interface_key::CURSOR_DOWN_Z);
send_key(interface_key::CURSOR_UP_Z);
last_x = cx;
last_y = cy;
last_z = vz;
return true;
}
}
}
else if (enabler->mouse_rbut)
{
// Escape out of query mode
using namespace df::enums::ui_sidebar_mode;
if (ui->main.mode == QueryBuilding || ui->main.mode == BuildingItems ||
ui->main.mode == ViewUnits || ui->main.mode == LookAround)
{
while (ui->main.mode != Default)
{
enabler->mouse_rbut = 0;
send_key(df::interface_key::LEAVESCREEN);
}
}
}
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handle_mouse(input))
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(mousequery_hook, feed);
DFHACK_PLUGIN("mousequery");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(mousequery_hook, feed).apply())
out.printerr("Could not insert mousequery hooks!\n");
last_x = last_y = last_z = -1;
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
last_x = last_y = last_z = -1;
break;
default:
break;
}
return CR_OK;
}

@ -0,0 +1,63 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Buildings.h"
#include "modules/EventManager.h"
#include "modules/Maps.h"
#include "df/coord.h"
#include "df/building.h"
#include "df/building_def.h"
#include "df/map_block.h"
#include "df/tile_designation.h"
#include <string>
using namespace DFHack;
using namespace std;
DFHACK_PLUGIN("outsideOnly");
void buildingCreated(color_ostream& out, void* data);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
EventManager::EventHandler handler(buildingCreated,1);
EventManager::registerListener(EventManager::EventType::BUILDING, handler, plugin_self);
return CR_OK;
}
// This is called right before the plugin library is removed from memory.
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
void buildingCreated(color_ostream& out, void* data) {
int32_t id = (int32_t)data;
df::building* building = df::building::find(id);
if ( building == NULL )
return;
if ( building->getCustomType() < 0 )
return;
string prefix("OUTSIDE_ONLY");
df::building_def* def = df::global::world->raws.buildings.all[building->getCustomType()];
if (def->code.compare(0, prefix.size(), prefix)) {
return;
}
//now, just check if it was created inside, and if so, scuttle it
df::coord pos(building->centerx,building->centery,building->z);
df::tile_designation* des = Maps::getTileDesignation(pos);
if ( des->bits.outside )
return;
Buildings::deconstruct(building);
}

@ -0,0 +1,59 @@
package isoworldremote;
//Describes a very basic material structure for the map embark
option optimize_for = LITE_RUNTIME;
enum BasicMaterial {
AIR = 0;
OTHER = 1;
INORGANIC = 2;
LIQUID = 3;
PLANT = 4;
WOOD = 5;
};
enum LiquidType {
ICE = 0;
WATER = 1;
MAGMA = 2;
}
message EmbarkTileLayer {
repeated BasicMaterial mat_type_table = 4 [packed=true];
repeated int32 mat_subtype_table = 5 [packed=true];
}
message EmbarkTile {
required int32 world_x = 1;
required int32 world_y = 2;
required sint32 world_z = 3;
repeated EmbarkTileLayer tile_layer = 4;
optional int32 current_year = 5;
optional int32 current_season = 6;
optional bool is_valid = 7;
}
message TileRequest {
optional int32 want_x = 1;
optional int32 want_y = 2;
}
message MapRequest {
optional string save_folder = 1;
}
message MapReply {
required bool available = 1;
optional int32 region_x = 2;
optional int32 region_y = 3;
optional int32 region_size_x = 4;
optional int32 region_size_y = 5;
optional int32 current_year = 6;
optional int32 current_season = 7;
}
message RawNames {
required bool available = 1;
repeated string inorganic = 2;
repeated string organic = 3;
}

@ -1,322 +0,0 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <modules/Maps.h>
#include <modules/Job.h>
#include <modules/Items.h>
#include <modules/Units.h>
#include <TileTypes.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <string.h>
#include <VTableInterpose.h>
#include "df/item_liquid_miscst.h"
#include "df/item_constructed.h"
#include "df/builtin_mats.h"
#include "df/world.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/job_item_ref.h"
#include "df/ui.h"
#include "df/report.h"
#include "df/reaction.h"
#include "df/reaction_reagent_itemst.h"
#include "df/reaction_product_itemst.h"
#include "df/matter_state.h"
#include "df/contaminant.h"
#include "MiscUtils.h"
#include "LuaTools.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
typedef df::reaction_product_itemst item_product;
DFHACK_PLUGIN("reactionhooks");
struct ReagentSource {
int idx;
df::reaction_reagent *reagent;
ReagentSource() : idx(-1), reagent(NULL) {}
};
struct MaterialSource : ReagentSource {
bool product;
std::string product_name;
int mat_type, mat_index;
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
};
struct ProductInfo {
df::reaction *react;
item_product *product;
MaterialSource material;
bool isValid() {
return (material.mat_type >= 0 || material.reagent);
}
};
struct ReactionInfo {
df::reaction *react;
std::vector<ProductInfo> products;
};
static std::map<std::string, ReactionInfo> reactions;
static std::map<df::reaction_product*, ProductInfo*> products;
static ReactionInfo *find_reaction(const std::string &name)
{
auto it = reactions.find(name);
return (it != reactions.end()) ? &it->second : NULL;
}
static bool is_lua_hook(const std::string &name)
{
return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0;
}
static void find_material(int *type, int *index, df::item *input, MaterialSource &mat)
{
if (input && mat.reagent)
{
MaterialInfo info(input);
if (mat.product)
{
if (!info.findProduct(info, mat.product_name))
{
color_ostream_proxy out(Core::getInstance().getConsole());
out.printerr("Cannot find product '%s'\n", mat.product_name.c_str());
}
}
*type = info.type;
*index = info.index;
}
else
{
*type = mat.mat_type;
*index = mat.mat_index;
}
}
/*
* Hooks
*/
typedef std::map<int, std::vector<df::item*> > item_table;
static void index_items(item_table &table, df::job *job, ReactionInfo *info)
{
for (int i = job->items.size()-1; i >= 0; i--)
{
auto iref = job->items[i];
if (iref->job_item_idx < 0) continue;
auto iitem = job->job_items[iref->job_item_idx];
if (iitem->contains.empty())
{
table[iitem->reagent_index].push_back(iref->item);
}
else
{
std::vector<df::item*> contents;
Items::getContainedItems(iref->item, &contents);
for (int j = contents.size()-1; j >= 0; j--)
{
for (int k = iitem->contains.size()-1; k >= 0; k--)
{
int ridx = iitem->contains[k];
auto reag = info->react->reagents[ridx];
if (reag->matchesChild(contents[j], info->react, iitem->reaction_id))
table[ridx].push_back(contents[j]);
}
}
}
}
}
df::item* find_item(ReagentSource &info, item_table &table)
{
if (!info.reagent)
return NULL;
if (table[info.idx].empty())
return NULL;
return table[info.idx].back();
}
df::item* find_item(
ReagentSource &info,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items
) {
if (!info.reagent)
return NULL;
for (int i = in_items->size(); i >= 0; i--)
if ((*in_reag)[i] == info.reagent)
return (*in_items)[i];
return NULL;
}
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
, std::vector<df::item*> *out_items,bool *call_native){};
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onReactionComplete),
DFHACK_LUA_END
};
struct product_hook : item_product {
typedef item_product interpose_base;
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items,
int32_t quantity, df::job_skill skill,
df::historical_entity *entity, df::world_site *site)
) {
if (auto product = products[this])
{
df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
if(!call_native)
return;
}
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
/*
* Scan raws for matching reactions.
*/
static void parse_product(
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) {
info.react = react;
info.product = prod;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
}
static bool find_reactions(color_ostream &out)
{
reactions.clear();
auto &rlist = world->raws.reactions;
for (size_t i = 0; i < rlist.size(); i++)
{
if (!is_lua_hook(rlist[i]->code))
continue;
reactions[rlist[i]->code].react = rlist[i];
}
for (auto it = reactions.begin(); it != reactions.end(); ++it)
{
auto &prod = it->second.react->products;
auto &out_prod = it->second.products;
for (size_t i = 0; i < prod.size(); i++)
{
auto itprod = strict_virtual_cast<item_product>(prod[i]);
if (!itprod) continue;
out_prod.push_back(ProductInfo());
parse_product(out, out_prod.back(), it->second.react, itprod);
}
for (size_t i = 0; i < prod.size(); i++)
{
if (out_prod[i].isValid())
products[out_prod[i].product] = &out_prod[i];
}
}
return !products.empty();
}
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(product_hook, produce).apply(enable);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_WORLD_LOADED:
if (find_reactions(out))
{
out.print("Detected reaction hooks - enabling plugin.\n");
enable_hooks(true);
}
else
enable_hooks(false);
break;
case SC_WORLD_UNLOADED:
enable_hooks(false);
reactions.clear();
products.clear();
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_WORLD_LOADED);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
enable_hooks(false);
return CR_OK;
}

@ -0,0 +1,315 @@
#include <string>
#include <vector>
#include <algorithm>
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "MiscUtils.h"
#include "Types.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/world.h"
#include "df/building_constructionst.h"
#include "df/building.h"
#include "df/job.h"
#include "df/job_item.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/World.h"
using std::map;
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::ui;
using df::global::world;
DFHACK_PLUGIN("resume");
#define PLUGIN_VERSION 0.2
#ifndef HAVE_NULLPTR
#define nullptr 0L
#endif
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
template <class T, typename Fn>
static void for_each_(vector<T> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
{
transform(src.begin(), src.end(), back_inserter(dst), func);
}
void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0)
{
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
if (newline)
{
++y;
x = left_margin;
}
else
x += text.length();
}
df::job *get_suspended_job(df::building *bld)
{
if (bld->getBuildStage() != 0)
return nullptr;
if (bld->jobs.size() == 0)
return nullptr;
auto job = bld->jobs[0];
if (job->flags.bits.suspend)
return job;
return nullptr;
}
struct SuspendedBuilding
{
df::building *bld;
df::coord pos;
bool was_resumed;
bool is_planned;
SuspendedBuilding(df::building *bld_) : bld(bld_), was_resumed(false), is_planned(false)
{
pos = df::coord(bld->centerx, bld->centery, bld->z);
}
bool isValid()
{
return bld && Buildings::findAtTile(pos) == bld && get_suspended_job(bld);
}
};
static bool enabled = false;
static bool buildings_scanned = false;
static vector<SuspendedBuilding> suspended_buildings, resumed_buildings;
void scan_for_suspended_buildings()
{
if (buildings_scanned)
return;
for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++)
{
auto bld = *b;
auto job = get_suspended_job(bld);
if (job)
{
SuspendedBuilding sb(bld);
sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE;
auto it = find_if(resumed_buildings.begin(), resumed_buildings.end(),
[&] (SuspendedBuilding &rsb) { return rsb.bld == bld; });
sb.was_resumed = it != resumed_buildings.end();
suspended_buildings.push_back(sb);
}
}
buildings_scanned = true;
}
void show_suspended_buildings()
{
int32_t vx, vy, vz;
if (!Gui::getViewCoords(vx, vy, vz))
return;
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = vx + dims.map_x2;
int bottom_margin = vy + dims.y2;
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();)
{
if (!sb->isValid())
{
sb = suspended_buildings.erase(sb);
continue;
}
if (sb->bld->z == vz && sb->bld->centerx >= vx && sb->bld->centerx <= left_margin &&
sb->bld->centery >= vy && sb->bld->centery <= bottom_margin)
{
int x = sb->bld->centerx - vx + 1;
int y = sb->bld->centery - vy + 1;
auto color = COLOR_YELLOW;
if (sb->is_planned)
color = COLOR_GREEN;
else if (sb->was_resumed)
color = COLOR_RED;
OutputString(color, x, y, "X");
}
sb++;
}
}
void clear_scanned()
{
buildings_scanned = false;
suspended_buildings.clear();
}
void resume_suspended_buildings(color_ostream &out)
{
out << "Resuming all buildings." << endl;
for (auto isb = resumed_buildings.begin(); isb != resumed_buildings.end();)
{
if (isb->isValid())
{
isb++;
continue;
}
isb = resumed_buildings.erase(isb);
}
scan_for_suspended_buildings();
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end(); sb++)
{
if (sb->is_planned)
continue;
resumed_buildings.push_back(*sb);
sb->bld->jobs[0]->flags.bits.suspend = false;
}
clear_scanned();
out << resumed_buildings.size() << " buildings resumed" << endl;
}
//START Viewscreen Hook
struct resume_hook : public df::viewscreen_dwarfmodest
{
//START UI Methods
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (enabled && DFHack::World::ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default)
{
scan_for_suspended_buildings();
show_suspended_buildings();
}
else
{
clear_scanned();
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render);
static command_result resume_cmd(color_ostream &out, vector <string> & parameters)
{
bool show_help = false;
if (parameters.empty())
{
show_help = true;
}
else
{
auto cmd = parameters[0][0];
if (cmd == 'v')
{
out << "Resume" << endl << "Version: " << PLUGIN_VERSION << endl;
}
else if (cmd == 's')
{
enabled = true;
out << "Overlay enabled" << endl;
}
else if (cmd == 'h')
{
enabled = false;
out << "Overlay disabled" << endl;
}
else if (cmd == 'a')
{
resume_suspended_buildings(out);
}
else
{
show_help = true;
}
}
if (show_help)
return CR_WRONG_USAGE;
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(resume_hook, render).apply())
out.printerr("Could not insert resume hooks!\n");
commands.push_back(
PluginCommand(
"resume", "A plugin to help display and resume suspended constructions conveniently",
resume_cmd, false,
"resume show\n"
" Show overlay when paused:\n"
" Yellow: Suspended construction\n"
" Red: Suspended after resume attempt, possibly stuck\n"
" Green: Planned building waiting for materials\n"
"resume hide\n"
" Hide overlay\n"
"resume all\n"
" Resume all suspended building constructions\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
suspended_buildings.clear();
resumed_buildings.clear();
break;
default:
break;
}
return CR_OK;
}

@ -54,6 +54,19 @@ The help string displayed in dfhack 'ls' command is the first line of the
script, if it is a comment (ie starts with '# ').
Calling dfhack commands
-----------------------
The ruby plugin allows the calling of arbitrary dfhack commands, as if typed
directly on the dfhack prompt.
However due to locks and stuff, the dfhack command is delayed until the current
ruby command is finished, so it is restricted to interactive uses.
It is possible to call the method many times, this will queue dfhack commands
to be run in order.
df.dfhack_run "reveal"
Ruby helper functions
---------------------
@ -107,7 +120,7 @@ eg 'gob' for 'GOBLIN' or 'coal' for 'COAL_BITUMINOUS', hence the name.
df.building_construct(bld, item_list)
Allocates a new building in DF memory, define its position / dimensions, and
create a dwarf job to construct it from the given list of items.
See buildings.rb/buildbed for an exemple.
See buildings.rb/buildbed for an example.
df.each_tree(material) { |t| }
Iterates over every tree of the given material (eg 'maple').
@ -137,13 +150,17 @@ To stop being called, use:
The same mechanism is available for 'onstatechange', but the
SC_BEGIN_UNLOAD event is not propagated to the ruby handler.
Available states:
:WORLD_LOADED, :WORLD_UNLOADED, :MAP_LOADED, :MAP_UNLOADED,
:VIEWSCREEN_CHANGED, :CORE_INITIALIZED, :PAUSED, :UNPAUSED
C++ object manipulation
-----------------------
The ruby classes defined in ruby-autogen.rb are accessors to the underlying
df C++ objects in-memory. To allocate a new C++ object for use in DF, use the
RubyClass.cpp_new method (see buildings.rb for exemples), works for Compounds
RubyClass.cpp_new method (see buildings.rb for examples), works for Compounds
only.
A special Compound DFHack::StlString is available for allocating a single c++
stl::string, so that you can call vmethods that take a string pointer argument
@ -186,10 +203,10 @@ Pointer fields are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. NULL pointers yield the 'nil' value.
Exemples
Examples
--------
For more complex exemples, check the dfhack/scripts/*.rb files.
For more complex examples, check the dfhack/scripts/*.rb files.
Show info on the currently selected unit ('v' or 'k' DF menu)
p df.unit_find.flags1
@ -240,7 +257,7 @@ which differ between Windows and Linux. Linux and Macosx are the same, as they
both use gcc).
It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb)
For exemple,
For example,
<ld:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/>
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>

@ -48,6 +48,8 @@ module DFHack
raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new
bld.race = ui.race_id
subtype = ConstructionType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Construction
subtype = SiegeengineType.int(subtype) if subtype.kind_of?(::Symbol) and type == :SiegeEngine
subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop
subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace
subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone

@ -843,7 +843,7 @@ sub render_item_number {
} elsif ($subtype eq 'uint8_t') {
push @lines_rb, 'number 8, false';
} elsif ($subtype eq 'int8_t') {
push @lines_rb, 'number 8, false';
push @lines_rb, 'number 8, true';
} elsif ($subtype eq 'bool') {
push @lines_rb, 'number 8, true';
$initvalue ||= 'nil';

@ -149,7 +149,21 @@ module DFHack
def vein
# last vein wins
all_veins.reverse.find { |v|
(v.tile_bitmask.bits[@dy] & (1 << @dx)) > 0
v.tile_bitmask.bits[@dy][@dx] > 0
}
end
# return the first BlockBurrow this tile is in (nil if none)
def burrow
mapblock.block_burrows.find { |b|
b.tile_bitmask.bits[@dy][@dx] > 0
}
end
# return the array of BlockBurrow this tile is in
def all_burrows
mapblock.block_burrows.find_all { |b|
b.tile_bitmask.bits[@dy][@dx] > 0
}
end

@ -122,7 +122,8 @@ module DFHack
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _cpp_delete
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_delete }
# cannot call delete on compound members (would call free on member address)
#_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_delete }
DFHack.free(@_memaddr)
@_memaddr = nil # turn future segfaults in harmless ruby exceptions
end
@ -642,7 +643,7 @@ module DFHack
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
# possible struct size, like for StlVector. Just ignore it for now, deques are rare enough.
def inspect ; "#<StlDeque>" ; end
end
@ -676,7 +677,7 @@ module DFHack
def inspect
out = "#<DfFlagarray"
each_with_index { |e, idx|
out << " #{_indexenum.sym(idx)}" if e
out << " #{_indexenum ? _indexenum.sym(idx) : idx}" if e
}
out << '>'
end

@ -41,6 +41,7 @@ static tthread::thread *r_thread;
static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1;
static color_ostream_proxy *console_proxy;
static std::vector<std::string> *dfhack_run_queue;
DFHACK_PLUGIN("ruby")
@ -63,6 +64,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
// lock this before anything, and release when everything is done
m_mutex = new tthread::mutex();
// list of dfhack commands to run when the current ruby run is done (once locks are released)
dfhack_run_queue = new std::vector<std::string>;
r_type = RB_INIT;
// create the dedicated ruby thread
@ -84,6 +88,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
"Ruby interpreter. Eval() a ruby string.",
df_rubyeval));
commands.push_back(PluginCommand("rb",
"Ruby interpreter. Eval() a ruby string (alias for rb_eval).",
df_rubyeval));
return CR_OK;
}
@ -111,6 +119,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
// we can release m_mutex, other users will check r_thread
m_mutex->unlock();
delete m_mutex;
delete dfhack_run_queue;
// dlclose libruby
df_unloadruby();
@ -152,6 +161,8 @@ static command_result do_plugin_eval_ruby(color_ostream &out, const char *comman
// send a single ruby line to be evaluated by the ruby thread
DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command)
{
command_result ret;
// if dlopen failed
if (!r_thread)
return CR_FAILURE;
@ -160,14 +171,24 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c
// debug only!
// run ruby commands without locking the main thread
// useful when the game is frozen after a segfault
return do_plugin_eval_ruby(out, command+7);
ret = do_plugin_eval_ruby(out, command+7);
} else {
// wrap all ruby code inside a suspend block
// if we dont do that and rely on ruby code doing it, we'll deadlock in
// onupdate
CoreSuspender suspend;
return do_plugin_eval_ruby(out, command);
ret = do_plugin_eval_ruby(out, command);
}
// if any dfhack command is queued for run, do it now
while (!dfhack_run_queue->empty()) {
std::string cmd = dfhack_run_queue->at(0);
// delete before running the command, which may be ruby and cause infinite loops
dfhack_run_queue->erase(dfhack_run_queue->begin());
Core::getInstance().runCommand(out, cmd);
}
return ret;
}
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
@ -550,18 +571,10 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
// run a dfhack command, as if typed from the dfhack console
static VALUE rb_dfhack_run(VALUE self, VALUE cmd)
{
if (!r_console) // XXX
return Qnil;
std::string s;
int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0));
s.assign(rb_string_value_ptr(&cmd), strlen);
// allow the target command to suspend
// FIXME
//CoreSuspendClaimer suspend(true);
Core::getInstance().runCommand(*r_console, s);
dfhack_run_queue->push_back(s);
return Qtrue;
}
@ -1045,7 +1058,7 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
//rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);

File diff suppressed because it is too large Load Diff

@ -0,0 +1,40 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
//#include "df/world.h"
using namespace DFHack;
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("skeleton2");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"skeleton2",
"shortHelpString",
skeleton2,
false, //allow non-interactive use
"longHelpString"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters)
{
if (!parameters.empty())
return CR_WRONG_USAGE;
CoreSuspender suspend;
out.print("blah");
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd
Subproject commit 0d41614ff3dae9245e786ad667b0e463fe0dea3e

@ -0,0 +1,198 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/Once.h"
#include "df/caste_raw.h"
#include "df/creature_raw.h"
#include "df/syndrome.h"
#include "df/unit.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
#include <cstdlib>
using namespace DFHack;
using namespace std;
static bool enabled = false;
DFHACK_PLUGIN("syndromeTrigger");
void syndromeHandler(color_ostream& out, void* ptr);
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand("syndromeTrigger", "Run commands and enable true transformations, configured by the raw files.\n", &syndromeTrigger, false,
"syndromeTrigger:\n"
" syndromeTrigger 0 //disable\n"
" syndromeTrigger 1 //enable\n"
" syndromeTrigger disable //disable\n"
" syndromeTrigger enable //enable\n"
"\n"
"See Readme.rst for details.\n"
));
return CR_OK;
}
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters) {
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
bool wasEnabled = enabled;
if ( parameters.size() == 1 ) {
if ( parameters[0] == "enable" ) {
enabled = true;
} else if ( parameters[0] == "disable" ) {
enabled = false;
} else {
int32_t a = atoi(parameters[0].c_str());
if ( a < 0 || a > 1 )
return CR_WRONG_USAGE;
enabled = (bool)a;
}
}
out.print("syndromeTrigger is %s\n", enabled ? "enabled" : "disabled");
if ( enabled == wasEnabled )
return CR_OK;
EventManager::unregisterAll(plugin_self);
if ( enabled ) {
EventManager::EventHandler handle(syndromeHandler, 1);
EventManager::registerListener(EventManager::EventType::SYNDROME, handle, plugin_self);
}
return CR_OK;
}
void syndromeHandler(color_ostream& out, void* ptr) {
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
if ( !ptr ) {
if ( DFHack::Once::doOnce("syndromeTrigger_null data") ) {
out.print("%s, %d: null pointer from EventManager.\n", __FILE__, __LINE__);
}
return;
}
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
df::unit* unit = df::unit::find(data->unitId);
if (!unit) {
if ( DFHack::Once::doOnce("syndromeTrigger_no find unit" ) )
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
return;
}
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
//out.print(" syndrome type %d\n", unit_syndrome->type);
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
bool foundPermanent = false;
bool foundCommand = false;
bool foundAutoSyndrome = false;
string commandStr;
vector<string> args;
int32_t raceId = -1;
df::creature_raw* creatureRaw = NULL;
int32_t casteId = -1;
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
std::string& clazz = *syndrome->syn_class[a];
//out.print(" clazz %d = %s\n", a, clazz.c_str());
if ( foundCommand ) {
if ( commandStr == "" ) {
commandStr = clazz;
continue;
}
stringstream bob;
if ( clazz == "\\LOCATION" ) {
bob << unit->pos.x;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << unit->pos.y;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << unit->pos.z;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( clazz == "\\UNIT_ID" ) {
bob << unit->id;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( clazz == "\\SYNDROME_ID" ) {
bob << unit_syndrome->type;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else {
args.push_back(clazz);
}
continue;
}
if ( clazz == "\\AUTO_SYNDROME" ) {
foundAutoSyndrome = true;
continue;
}
if ( clazz == "\\COMMAND" ) {
foundCommand = true;
continue;
}
if ( clazz == "\\PERMANENT" ) {
foundPermanent = true;
continue;
}
if ( !foundPermanent )
continue;
if ( raceId == -1 ) {
//find the race with the name
string& name = *syndrome->syn_class[a];
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
if ( creature->creature_id != name )
continue;
raceId = b;
creatureRaw = creature;
break;
}
continue;
}
if ( raceId != -1 && casteId == -1 ) {
string& name = *syndrome->syn_class[a];
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
df::caste_raw* caste = creatureRaw->caste[b];
if ( caste->caste_id != name )
continue;
casteId = b;
break;
}
continue;
}
}
if ( !foundAutoSyndrome && commandStr != "" ) {
Core::getInstance().runCommand(out, commandStr, args);
}
if ( !foundPermanent || raceId == -1 || casteId == -1 )
return;
unit->enemy.normal_race = raceId;
unit->enemy.normal_caste = casteId;
//that's it!
}

@ -0,0 +1,145 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/Once.h"
#include "df/block_burrow.h"
#include "df/block_burrow_link.h"
#include "df/burrow.h"
#include "df/map_block.h"
#include "df/tile_bitmask.h"
#include "df/tile_dig_designation.h"
#include "df/tiletype.h"
#include "df/tiletype_shape.h"
#include "df/world.h"
//#include "df/world.h"
using namespace DFHack;
void checkFarms(color_ostream& out, void* ptr);
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters);
EventManager::EventHandler handler(&checkFarms, -1);
int32_t frequency = 1200*30;
DFHACK_PLUGIN("treefarm");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"treefarm",
"automatically manages special burrows and regularly schedules tree chopping and digging when appropriate",
treefarm,
false, //allow non-interactive use
"treefarm\n"
" enables treefarm monitoring, starting next frame\n"
"treefarm n\n"
" enables treefarm monitoring, starting next frame\n"
" sets monitoring interval to n frames\n"
" if n is less than one, disables monitoring\n"
"\n"
"Every time the plugin runs, it checks for burrows with a name containing the string \"treefarm\". For each such burrow, it checks every tile in it for fully-grown trees and for diggable walls. For each fully-grown tree it finds, it designates the tree to be chopped, and for each natural wall it finds, it designates the wall to be dug.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
void checkFarms(color_ostream& out, void* ptr) {
EventManager::unregisterAll(plugin_self);
EventManager::registerTick(handler, frequency, plugin_self);
CoreSuspender suspend;
df::world* world = df::global::world;
df::ui* ui = df::global::ui;
int32_t xOffset = world->map.region_x*3;
int32_t yOffset = world->map.region_y*3;
int32_t zOffset = world->map.region_z;
//for each burrow named treefarm or obsidianfarm, check if you can dig/chop any obsidian/trees
for ( size_t a = 0; a < df::burrow::get_vector().size(); a++ ) {
df::burrow* burrow = df::burrow::get_vector()[a];
if ( !burrow || burrow->name.find("treefarm") == std::string::npos )
continue;
if ( burrow->block_x.size() != burrow->block_y.size() || burrow->block_x.size() != burrow->block_z.size() )
continue;
for ( size_t b = 0; b < burrow->block_x.size(); b++ ) {
int32_t x=burrow->block_x[b] - xOffset;
int32_t y=burrow->block_y[b] - yOffset;
int32_t z=burrow->block_z[b] - zOffset;
df::map_block* block = world->map.block_index[x][y][z];
if ( !block )
continue;
df::block_burrow_link* link = &block->block_burrows;
df::tile_bitmask mask;
for ( ; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->id == burrow->id ) {
mask = link->item->tile_bitmask;
break;
}
}
if ( link == NULL )
continue;
for ( int32_t x = 0; x < 16; x++ ) {
for ( int32_t y = 0; y < 16; y++ ) {
if ( !mask.getassignment(x,y) )
continue;
df::tiletype type = block->tiletype[x][y];
df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, type);
if ( !block->designation[x][y].bits.hidden &&
shape != df::enums::tiletype_shape::WALL &&
shape != df::enums::tiletype_shape::TREE )
continue;
if ( shape != df::enums::tiletype_shape::TREE ) {
if ( x == 0 && (block->map_pos.x/16) == 0 )
continue;
if ( y == 0 && (block->map_pos.y/16) == 0 )
continue;
if ( x == 15 && (block->map_pos.x/16) == world->map.x_count_block-1 )
continue;
if ( y == 15 && (block->map_pos.y/16) == world->map.y_count_block-1 )
continue;
}
block->designation[x][y].bits.dig = df::enums::tile_dig_designation::Default;
}
}
}
}
}
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters)
{
EventManager::unregisterAll(plugin_self);
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
if ( parameters.size() == 1 ) {
int32_t i = atoi(parameters[0].c_str());
if ( i < 1 ) {
out.print("treefarm disabled\n");
return CR_OK;
}
frequency = i;
}
EventManager::registerTick(handler, 1, plugin_self);
out.print("treefarm enabled with update frequency %d ticks\n", frequency);
return CR_OK;
}

@ -251,10 +251,16 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
// Force update of ui state
set<df::interface_key> tmp;
tmp.insert(interface_key::CURSOR_DOWN_Z);
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_UP_Z);
else
tmp.insert(interface_key::CURSOR_DOWN_Z);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear();
tmp.insert(interface_key::CURSOR_UP_Z);
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_DOWN_Z);
else
tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp);
}
else if (!is_default && cur_cursor.isValid())
@ -696,7 +702,7 @@ static bool can_spar(df::unit *unit) {
return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap
(unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) &&
(!unit->health || (unit->health->flags.whole&0x7FF) == 0) &&
(!unit->job.current_job || unit->job.current_job != job_type::Rest);
(!unit->job.current_job || unit->job.current_job->job_type != job_type::Rest);
}
static bool has_spar_inventory(df::unit *unit, df::job_skill skill)
@ -809,14 +815,18 @@ struct military_training_ct_hook : df::activity_event_combat_trainingst {
spar++;
}
#if 0
color_ostream_proxy out(Core::getInstance().getConsole());
#endif
// If the xp gap is low, sometimes replace with sparring
if ((maxv - minv) < 64*15 && spar == units.size() &&
random_int(45) >= 30 + (maxv-minv)/64)
{
#if 0
out.print("Replacing %s demonstration (xp %d-%d, gap %d) with sparring.\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(), minv, maxv, maxv-minv);
#endif
if (auto spar = df::allocate<df::activity_event_sparringst>())
{
@ -838,18 +848,22 @@ struct military_training_ct_hook : df::activity_event_combat_trainingst {
// If the teacher has less xp than somebody else, switch
if (best >= 0 && maxv > cur_xp)
{
#if 0
out.print("Replacing %s teacher %d (%d xp) with %d (%d xp); xp gap %d.\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(),
sd->unit_id, cur_xp, units[best], maxv, maxv-minv);
#endif
sd->hist_figure_id = sd->participants.histfigs[best];
sd->unit_id = units[best];
}
else
{
#if 0
out.print("Not changing %s demonstration (xp %d-%d, gap %d).\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(),
minv, maxv, maxv-minv);
#endif
}
}
}

@ -0,0 +1,580 @@
#include <algorithm>
#include <map>
#include <string>
#include <set>
#include "Core.h"
#include "MiscUtils.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
#include "modules/Screen.h"
#include "df/enabler.h"
using std::string;
using std::vector;
using std::map;
using std::ostringstream;
using std::set;
using namespace DFHack;
using namespace df::enums;
using df::global::enabler;
using df::global::gps;
#ifndef HAVE_NULLPTR
#define nullptr 0L
#endif
#define COLOR_TITLE COLOR_BLUE
#define COLOR_UNSELECTED COLOR_GREY
#define COLOR_SELECTED COLOR_WHITE
#define COLOR_HIGHLIGHTED COLOR_GREEN
template <class T, typename Fn>
static void for_each_(vector<T> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void for_each_(map<T, V> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
{
transform(src.begin(), src.end(), back_inserter(dst), func);
}
typedef int8_t UIColor;
void OutputString(UIColor color, int &x, int &y, const std::string &text,
bool newline = false, int left_margin = 0, const UIColor bg_color = 0)
{
Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text);
if (newline)
{
++y;
x = left_margin;
}
else
x += text.length();
}
void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false,
int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN)
{
OutputString(hotkey_color, x, y, hotkey);
string display(": ");
display.append(text);
OutputString(text_color, x, y, display, newline, left_margin);
}
void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false,
int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN)
{
OutputString(hotkey_color, x, y, hotkey);
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin);
}
void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE)
{
OutputHotkeyString(x, y, text, hotkey);
OutputString(COLOR_WHITE, x, y, ": ");
if (state)
OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin);
else
OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin);
}
const int ascii_to_enum_offset = interface_key::STRING_A048 - '0';
inline string int_to_string(const int n)
{
return static_cast<ostringstream*>( &(ostringstream() << n) )->str();
}
static void set_to_limit(int &value, const int maximum, const int min = 0)
{
if (value < min)
value = min;
else if (value > maximum)
value = maximum;
}
inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0)
{
Screen::paintString(Screen::Pen(' ', color, background), x, y, text);
}
static string pad_string(string text, const int size, const bool front = true, const bool trim = false)
{
if (text.length() > size)
{
if (trim && size > 10)
{
text = text.substr(0, size-3);
text.append("...");
}
return text;
}
string aligned(size - text.length(), ' ');
if (front)
{
aligned.append(text);
return aligned;
}
else
{
text.append(aligned);
return text;
}
}
/*
* List classes
*/
template <typename T>
class ListEntry
{
public:
T elem;
string text, keywords;
bool selected;
ListEntry(const string text, const T elem, const string keywords = "") :
elem(elem), text(text), selected(false), keywords(keywords)
{
}
};
template <typename T>
class ListColumn
{
public:
int highlighted_index;
int display_start_offset;
unsigned short text_clip_at;
int32_t bottom_margin, search_margin, left_margin;
bool multiselect;
bool allow_null;
bool auto_select;
bool force_sort;
bool allow_search;
bool feed_changed_highlight;
ListColumn()
{
bottom_margin = 3;
clear();
left_margin = 2;
search_margin = 63;
highlighted_index = 0;
text_clip_at = 0;
multiselect = false;
allow_null = true;
auto_select = false;
force_sort = false;
allow_search = true;
feed_changed_highlight = false;
}
void clear()
{
list.clear();
display_list.clear();
display_start_offset = 0;
max_item_width = title.length();
resize();
}
void resize()
{
display_max_rows = gps->dimy - 4 - bottom_margin;
}
void add(ListEntry<T> &entry)
{
list.push_back(entry);
if (entry.text.length() > max_item_width)
max_item_width = entry.text.length();
}
void add(const string &text, const T &elem)
{
list.push_back(ListEntry<T>(text, elem));
if (text.length() > max_item_width)
max_item_width = text.length();
}
int fixWidth()
{
if (text_clip_at > 0 && max_item_width > text_clip_at)
max_item_width = text_clip_at;
for (auto it = list.begin(); it != list.end(); it++)
{
it->text = pad_string(it->text, max_item_width, false);
}
return left_margin + max_item_width;
}
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
void display(const bool is_selected_column) const
{
int32_t y = 2;
paint_text(COLOR_TITLE, left_margin, y, title);
int last_index_able_to_display = display_start_offset + display_max_rows;
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
{
++y;
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED;
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
string item_label = display_list[i]->text;
if (text_clip_at > 0 && item_label.length() > text_clip_at)
item_label.resize(text_clip_at);
paint_text(fg_color, left_margin, y, item_label, bg_color);
int x = left_margin + display_list[i]->text.length() + 1;
display_extras(display_list[i]->elem, x, y);
}
if (is_selected_column && allow_search)
{
y = gps->dimy - 3;
int32_t x = search_margin;
OutputHotkeyString(x, y, "Search" ,"S");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, search_string);
OutputString(COLOR_LIGHTGREEN, x, y, "_");
}
}
void filterDisplay()
{
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
display_list.clear();
search_string = toLower(search_string);
vector<string> search_tokens;
if (!search_string.empty())
split_string(&search_tokens, search_string, " ");
for (size_t i = 0; i < list.size(); i++)
{
ListEntry<T> *entry = &list[i];
bool include_item = true;
if (!search_string.empty())
{
string item_string = toLower(list[i].text);
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
{
if (!si->empty() && item_string.find(*si) == string::npos &&
list[i].keywords.find(*si) == string::npos)
{
include_item = false;
break;
}
}
}
if (include_item)
{
display_list.push_back(entry);
if (entry == prev_selected)
highlighted_index = display_list.size() - 1;
}
else if (auto_select)
{
entry->selected = false;
}
}
changeHighlight(0);
feed_changed_highlight = true;
}
void selectDefaultEntry()
{
for (size_t i = 0; i < display_list.size(); i++)
{
if (display_list[i]->selected)
{
highlighted_index = i;
break;
}
}
}
void validateHighlight()
{
set_to_limit(highlighted_index, display_list.size() - 1);
if (highlighted_index < display_start_offset)
display_start_offset = highlighted_index;
else if (highlighted_index >= display_start_offset + display_max_rows)
display_start_offset = highlighted_index - display_max_rows + 1;
if (auto_select || (!allow_null && list.size() == 1))
display_list[highlighted_index]->selected = true;
feed_changed_highlight = true;
}
void changeHighlight(const int highlight_change, const int offset_shift = 0)
{
if (!initHighlightChange())
return;
highlighted_index += highlight_change + offset_shift * display_max_rows;
display_start_offset += offset_shift * display_max_rows;
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
validateHighlight();
}
void setHighlight(const int index)
{
if (!initHighlightChange())
return;
highlighted_index = index;
validateHighlight();
}
bool initHighlightChange()
{
if (display_list.size() == 0)
return false;
if (auto_select && !multiselect)
{
for (auto it = list.begin(); it != list.end(); it++)
{
it->selected = false;
}
}
return true;
}
void toggleHighlighted()
{
if (auto_select)
return;
ListEntry<T> *entry = display_list[highlighted_index];
if (!multiselect || !allow_null)
{
int selected_count = 0;
for (size_t i = 0; i < list.size(); i++)
{
if (!multiselect && !entry->selected)
list[i].selected = false;
if (!allow_null && list[i].selected)
selected_count++;
}
if (!allow_null && entry->selected && selected_count == 1)
return;
}
entry->selected = !entry->selected;
}
vector<T> getSelectedElems(bool only_one = false)
{
vector<T> results;
for (auto it = list.begin(); it != list.end(); it++)
{
if ((*it).selected)
{
results.push_back(it->elem);
if (only_one)
break;
}
}
return results;
}
T getFirstSelectedElem()
{
vector<T> results = getSelectedElems(true);
if (results.size() == 0)
return nullptr;
else
return results[0];
}
void clearSelection()
{
for_each_(list, [] (ListEntry<T> &e) { e.selected = false; });
}
void selectItem(const T elem)
{
int i = 0;
for (; i < display_list.size(); i++)
{
if (display_list[i]->elem == elem)
{
setHighlight(i);
break;
}
}
}
void clearSearch()
{
search_string.clear();
filterDisplay();
}
size_t getDisplayListSize()
{
return display_list.size();
}
vector<ListEntry<T>*> &getDisplayList()
{
return display_list;
}
size_t getBaseListSize()
{
return list.size();
}
bool feed(set<df::interface_key> *input)
{
feed_changed_highlight = false;
if (input->count(interface_key::CURSOR_UP))
{
changeHighlight(-1);
}
else if (input->count(interface_key::CURSOR_DOWN))
{
changeHighlight(1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
{
changeHighlight(0, -1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
{
changeHighlight(0, 1);
}
else if (input->count(interface_key::SELECT) && !auto_select)
{
toggleHighlighted();
}
else if (input->count(interface_key::CUSTOM_SHIFT_S))
{
clearSearch();
}
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
{
return setHighlightByMouse();
}
else if (allow_search)
{
// Search query typing mode always on
df::interface_key last_token = *input->rbegin();
if ((last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) ||
last_token == interface_key::STRING_A032)
{
// Standard character
search_string += last_token - ascii_to_enum_offset;
filterDisplay();
}
else if (last_token == interface_key::STRING_A000)
{
// Backspace
if (search_string.length() > 0)
{
search_string.erase(search_string.length()-1);
filterDisplay();
}
}
else
{
return false;
}
return true;
}
else
{
return false;
}
return true;
}
bool setHighlightByMouse()
{
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
{
int new_index = display_start_offset + gps->mouse_y - 3;
if (new_index < display_list.size())
setHighlight(new_index);
enabler->mouse_lbut = enabler->mouse_rbut = 0;
return true;
}
return false;
}
void sort()
{
if (force_sort || list.size() < 100)
std::sort(list.begin(), list.end(),
[] (ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; });
filterDisplay();
}
void setTitle(const string t)
{
title = t;
if (title.length() > max_item_width)
max_item_width = title.length();
}
size_t getDisplayedListSize()
{
return display_list.size();
}
private:
vector<ListEntry<T>> list;
vector<ListEntry<T>*> display_list;
string search_string;
string title;
int display_max_rows;
int max_item_width;
};

@ -0,0 +1,93 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include "modules/EventManager.h"
#include "modules/World.h"
#include "df/global_objects.h"
#include <vector>
using namespace std;
using namespace DFHack;
DFHACK_PLUGIN("workNow");
static int mode = 0;
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters);
void jobCompletedHandler(color_ostream& out, void* ptr);
EventManager::EventHandler handler(jobCompletedHandler,1);
DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs whever they finish one, or every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu. When workNow is in mode 2, it will make dwarves look for jobs every time a job completes.\n"
"workNow\n"
" print workNow status\n"
"workNow 0\n"
" deactivate workNow\n"
"workNow 1\n"
" activate workNow (look for jobs on pause, and only then)\n"
"workNow 2\n"
" make dwarves look for jobs whenever a job completes\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) {
mode = 0;
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) {
if ( !mode )
return CR_OK;
if ( e == DFHack::SC_WORLD_UNLOADED ) {
mode = 0;
return CR_OK;
}
if ( e != DFHack::SC_PAUSED )
return CR_OK;
*df::global::process_jobs = true;
*df::global::process_dig = true;
return CR_OK;
}
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters) {
if ( parameters.size() == 0 ) {
out.print("workNow status = %d\n", mode);
return CR_OK;
}
if ( parameters.size() > 1 ) {
return CR_WRONG_USAGE;
}
int32_t a = atoi(parameters[0].c_str());
if (a < 0 || a > 2)
return CR_WRONG_USAGE;
if ( a == 2 && mode != 2 ) {
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handler, plugin_self);
} else if ( mode == 2 && a != 2 ) {
EventManager::unregister(EventManager::EventType::JOB_COMPLETED, handler, plugin_self);
}
mode = a;
out.print("workNow status = %d\n", mode);
return CR_OK;
}
void jobCompletedHandler(color_ostream& out, void* ptr) {
if ( mode < 2 )
return;
*df::global::process_jobs = true;
*df::global::process_dig = true;
}

File diff suppressed because it is too large Load Diff

@ -1,165 +1,176 @@
class AutoFarm
def initialize
@thresholds = Hash.new(50)
@lastcounts = Hash.new(0)
end
def setthreshold(id, v)
if df.world.raws.plants.all.find { |r| r.id == id }
@thresholds[id] = v.to_i
else
puts "No plant with id #{id}"
end
end
def setdefault(v)
@thresholds.default = v.to_i
end
def is_plantable (plant)
has_seed = plant.flags[:SEED]
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
def find_plantable_plants
plantable = {}
counts = Hash.new(0)
df.world.items.other[:SEEDS].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact)
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
end
}
counts.keys.each { |i|
if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
}
return plantable
end
def set_farms( plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
end
season = df.cur_season
idx = 0
farms.each { |f|
f.plant_id[season] = plants[idx]
idx = (idx + 1) % plants.length
}
end
def process
return false unless @running
plantable = find_plantable_plants
counts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index))
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
end
}
plants_s = []
plants_u = []
@lastcounts.clear
plantable.each_key { |k|
plant = df.world.raws.plants.all[k]
if (counts[k] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground
end
@lastcounts[plant.id] = counts[k]
}
farms_s = []
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
outside = df.map_designation_at(f.centerx,f.centery,f.z).outside
farms_s.push(f) if outside
farms_u.push(f) unless outside
end
}
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
@onupdate = df.onupdate_register('autofarm', 100) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
stat = @running ? "Running." : "Stopped."
@thresholds.each { |k,v|
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
}
stat += "\nDefault: #{@thresholds.default}"
stat
end
end
$AutoFarm = AutoFarm.new unless $AutoFarm
case $script_args[0]
when 'start'
$AutoFarm.start
when 'end', 'stop'
$AutoFarm.stop
when 'default'
$AutoFarm.setdefault($script_args[1])
when 'threshold'
t = $script_args[1]
$script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t)
}
when 'delete'
$AutoFarm.stop
$AutoFarm = nil
else
if $AutoFarm
puts $AutoFarm.status
else
puts "AI not started"
end
end
class AutoFarm
def initialize
@thresholds = Hash.new(50)
@lastcounts = Hash.new(0)
end
def setthreshold(id, v)
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
if tok = df.match_rawname(id, list)
@thresholds[tok] = v.to_i
else
puts "No plant with id #{id}, try one of " +
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
end
end
def setdefault(v)
@thresholds.default = v.to_i
end
def is_plantable(plant)
has_seed = plant.flags[:SEED]
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
def find_plantable_plants
plantable = {}
counts = Hash.new(0)
df.world.items.other[:SEEDS].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact)
counts[i.mat_index] += i.stack_size
end
}
counts.keys.each { |i|
if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
}
return plantable
end
def set_farms(plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
end
season = df.cur_season
farms.each_with_index { |f, idx|
f.plant_id[season] = plants[idx % plants.length]
}
end
def process
plantable = find_plantable_plants
@lastcounts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index))
id = df.world.raws.plants.all[i.mat_index].id
@lastcounts[id] += i.stack_size
end
}
return unless @running
plants_s = []
plants_u = []
plantable.each_key { |k|
plant = df.world.raws.plants.all[k]
if (@lastcounts[plant.id] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground
end
}
farms_s = []
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
farms_s.push(f) unless underground
farms_u.push(f) if underground
end
}
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
return if @running
@onupdate = df.onupdate_register('autofarm', 1200) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
stat = @running ? "Running." : "Stopped."
@lastcounts.each { |k,v|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
}
@thresholds.each { |k,v|
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
}
stat << "\nDefault: #{@thresholds.default}"
stat
end
end
$AutoFarm ||= AutoFarm.new
case $script_args[0]
when 'start', 'enable'
$AutoFarm.start
puts $AutoFarm.status
when 'end', 'stop', 'disable'
$AutoFarm.stop
puts 'Stopped.'
when 'default'
$AutoFarm.setdefault($script_args[1])
when 'threshold'
t = $script_args[1]
$script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t)
}
when 'delete'
$AutoFarm.stop
$AutoFarm = nil
when 'help', '?'
puts <<EOS
Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots.
Usage:
autofarm start
autofarm default 30
autofarm threshold 150 helmet_plump tail_pig
EOS
else
$AutoFarm.process
puts $AutoFarm.status
end

@ -1,58 +1,58 @@
class AutoUnsuspend
def initialize
end
def process
return false unless @running
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend)
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)." unless count == 0
end
def start
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
$AutoUnsuspend.start
when 'end', 'stop'
$AutoUnsuspend.stop
else
if $AutoUnsuspend
puts $AutoUnsuspend.status
else
puts 'Not loaded.'
end
end
class AutoUnsuspend
def initialize
end
def process
return false unless @running
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend)
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)." unless count == 0
end
def start
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
$AutoUnsuspend.start
when 'end', 'stop'
$AutoUnsuspend.stop
else
if $AutoUnsuspend
puts $AutoUnsuspend.status
else
puts 'Not loaded.'
end
end

@ -1,161 +1,177 @@
# create arbitrary items under cursor
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
if category == 'help'
puts <<EOS
Create items under the cursor.
Usage:
create [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, web
Raw token:
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
use 'list' to show all possibilities
Exemples:
create boulders hematite 30
create bars CREATURE_MAT:CAT:SOAP 10
create web cave_giant
create plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
# create first necessity items under cursor
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help'
puts <<EOS
Create first necessity items under the cursor.
Usage:
create-items [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, webs, anvils
Raw token:
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
Use 'list' to show all possibilities
Exemples:
create-items boulders hematite 30
create-items bars CREATURE_MAT:CAT:SOAP 10
create-items web cave_giant
create-items plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
when 'anvils'
cls = DFHack::ItemAnvilst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
count ||= 1
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
if df.cursor.z > 5
df.curview.feed_keys(:CURSOR_DOWN_Z)
df.curview.feed_keys(:CURSOR_UP_Z)
else
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
end

@ -1,67 +1,67 @@
# show death cause of a creature
def display_death_event(e)
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
str << " (cause: #{e.death_cause.to_s.downcase}),"
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end
if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id
elsif unit
hf = unit.hist_figure_id
end
if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !"
else
events = df.world.history.events
(0...events.length).reverse_each { |i|
e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e)
break
end
}
end
end
# show death cause of a creature
def display_death_event(e)
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
str << " (cause: #{e.death_cause.to_s.downcase}),"
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end
if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id
elsif unit
hf = unit.hist_figure_id
end
if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !"
else
events = df.world.history.events
(0...events.length).reverse_each { |i|
e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e)
break
end
}
end
end

@ -0,0 +1,18 @@
-- invasion-now civ_id delay : schedules an invasion in the near future
args = {...}
num = tonumber(args[1])
if ( num ~= nil ) then
time = tonumber(args[2]) or 1
time2 = tonumber(args[3]) or time
for i = time,time2 do
df.global.timed_events:insert('#',{
new = df.timed_event,
type = df.timed_event_type.CivAttack,
season = df.global.cur_season,
season_ticks = df.global.cur_season_tick+i,
entity = df.historical_entity.find(num)
})
end
end

@ -0,0 +1,10 @@
# list indexes in world.item.other[] where current selected item appears
tg = df.item_find
raise 'select an item' if not tg
o = df.world.items.other
# discard ANY/BAD
o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k|
puts k if o[k].find { |i| i == tg }
}

@ -0,0 +1,3 @@
# unforbid all items
df.world.items.all.each { |i| i.flags.forbid = false }

@ -1,38 +1,38 @@
# designate an area for digging according to a plan in csv format
raise "usage: digfort <plan filename>" if not $script_args[0]
planfile = File.read($script_args[0])
if df.cursor.x == -30000
raise "place the game cursor to the top-left corner of the design"
end
tiles = planfile.lines.map { |l|
l.sub(/#.*/, '').split(';').map { |t| t.strip }
}
x = x0 = df.cursor.x
y = df.cursor.y
z = df.cursor.z
tiles.each { |line|
next if line.empty? or line == ['']
line.each { |tile|
t = df.map_tile_at(x, y, z)
s = t.shape_basic
case tile
when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No)
end
x += 1
}
x = x0
y += 1
}
puts 'done'
# designate an area for digging according to a plan in csv format
raise "usage: digfort <plan filename>" if not $script_args[0]
planfile = File.read($script_args[0])
if df.cursor.x == -30000
raise "place the game cursor to the top-left corner of the design"
end
tiles = planfile.lines.map { |l|
l.sub(/#.*/, '').split(';').map { |t| t.strip }
}
x = x0 = df.cursor.x
y = df.cursor.y
z = df.cursor.z
tiles.each { |line|
next if line.empty? or line == ['']
line.each { |tile|
t = df.map_tile_at(x, y, z)
s = t.shape_basic
case tile
when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No)
end
x += 1
}
x = x0
y += 1
}
puts 'done'

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