v50 Steam/Premium information for editors
  • v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
  • Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.

Difference between revisions of "Utility:DFHack/Programming"

From Dwarf Fortress Wiki
Jump to navigation Jump to search
(Partially fixed spelling, structure and revamped condescending text.)
Line 1: Line 1:
 +
<pre>v1.1 (see change log at the end)</pre>
  
v1.1 (see change log at the end)
 
 
== Introduction ==
 
  
 +
== INTRODUCTION ==
  
DFHack is a Dwarf Fortress (DF) memory access library and a set of basic
+
DFHack is a Dwarf Fortress (DF) memory access library and a set of basic tools that use it (see the readme at https://github.com/peterix/dfhack/blob/master/Readme.rst ).
tools that use it (see the readme at https://github.com/peterix/dfhack/blob/master/Readme.rst ).
 
 
 
It is possible for developers to write plugins for DFHack, in
 
order to extend the capabilities of DFHack (and therefore DF's gameplay).
 
The plugins come mainly in two flavours:
 
* C++ plugins: libraries written in C++, that need to be compiled. They can use DFHack's
 
low level functions very closely.
 
* Lua and Ruby scripts: placed in the right folder, they are loaded at
 
DFHack's startup (outside of this document's scope. see more at
 
https://github.com/peterix/dfhack/blob/master/Lua%20API.rst )
 
 
 
This document explains how to get started at writing C++ plugins with
 
VisualC++ 2010, step by step. It is intended for people who are totally
 
new to DFHack.
 
  
 +
It is possible for developers to write plugins for DFHack, in order to extend the capabilities of DFHack (and therefore DF’s gameplay). The plugins come mainly in two flavours: * C++ plugins: libraries written in C++, that need to be compiled. They can use DFHack’s low level functions very closely. * Lua and Ruby scripts: placed in the right folder, they are loaded at DFHack’s startup (outside of this document’s scope. see more at https://github.com/peterix/dfhack/blob/master/Lua%20API.rst )
  
 +
This document explains how to get started at writing C++ plugins with VisualC++ 2010, step by step. It is intended for people who are totally new to DFHack.
  
 
== Getting Dwarf Fortress ==
 
== Getting Dwarf Fortress ==
  
You'll need a test environment to run your script. Hence the need of getting
+
You’ll need a test environment to run your script. Hence the need of getting DF. I’d recommend getting the latest “Lazy Newb Pack”, which includes not only Dwarf Fortress but also DFHack and other nice things, ready-to-use:
DF. I'd recommend getting the latest "Starter Pack", which includes not only
+
<blockquote>
Dwarf Fortress but also DFHack and other nice things, ready-to-use:
+
* Download the latest “Lazy Newb Pack”.
 +
</blockquote>
 +
You can run DF once and observe the DFHack console. Type <code>die</code> in that window to observe that the command line works (<code>die</code> is a command to force-close DF).
  
* Download the latest "Starer Pack".
 
 
You can run DF once and observe the DFHack console. Type ``die`` in that window
 
to observe that the command line works (``die`` is a command to force-close DF).
 
 
 
== Getting DFHack ==
 
== Getting DFHack ==
  
 
You must download the sources, and build them.
 
You must download the sources, and build them.
 +
<blockquote>
 +
* Follow exactly the steps described in DFHack’s readme : https://github.com/peterix/dfhack/blob/master/Compile.rst
 +
</blockquote>
  
* Follow exactly the steps described in DFHack's readme :
+
== Make yourself comfy ==
https://github.com/peterix/dfhack/blob/master/Compile.rst
 
  
== Make yourself comfortable ==
+
As explained in the compiling guidelines (above), there are several scripts available, that will do different things : some will just build, some will also install the results to your DF’s DFHack folder. To save some time, here is to run the latter script in your VisualC++ every time you press <code>F7</code> (Generate solution):
 +
<blockquote>
 +
* Open <code>dfhack.sln</code> in VisualC++
 +
* Right-click on the Solution, and select “Properties”
 +
* Go to “configuration properties”–&gt;“Configuration”
 +
* In the full list of projects, uncheck <code>ALL_BUILD</code>, and instead check <code>INSTALL</code>
 +
</blockquote>
 +
This way, it will force-build “INSTALL” every time. And guess what? That runs the install script.
  
As explained in the compiling guidelines (above), there are several scripts
+
== Create your very own plugin ==
available, that will do different things. Some will just build, some will
 
also install the results to your DF's DFHack folder.
 
To save some time, here is to run the latter script in your VisualC++ every
 
time you press ``F7`` (Generate solution):
 
  
* Open ``dfhack.sln`` in VisualC++
+
; What are the requirements for a plugin?
* Right-click on the Solution, and select "Properties"
+
<blockquote>
* Go to "configuration properties"-->"Configuration"
+
;* Its set of C++ source files (there can be just one if you want!)
* In the full list of projects, uncheck ``ALL_BUILD``, and instead check ``INSTALL``
+
;* The libraries (<code>.lib</code>) it will use
+
;* The protobufs it will use (if any!)
This way, it will force-build "INSTALL" every time. Which runs the install script.
+
</blockquote>
 +
Protobufs are some sort of serialization standard developed by Google. This is outside of the scope of this document. Only a handful of plugins use that technology. The <code>rename</code> and <code>isoworldremote</code> plugins do.
  
== Create your own plugin ==
+
The libraries are all the 3rd-party libraries you want to use in your project. The good news is that most libraries normally used in C++ have a high probability to be already used by some other plugin.
  
What are the requirements for a plugin?
+
; For example:
* Its set of C++ source files (there can be just one if you want!)
+
<blockquote>
* The libraries (``.lib``) it will use
+
;* <code>workflow</code> links to <code>Lua</code>.
* The protobufs it will use (if any!)
+
;* <code>dfstream</code> links to <code>dfhack-tinythread</code>.
+
;* I believe I’ve seen a plugin using <code>boost</code>.
Protobufs are some sort of serialization standard developed by Google. This is
+
;* <code>SDL</code> is available in any case.
outside of the scope of this document. Only a handful of plugins use that
+
</blockquote>
technology. The ``rename`` and ``isoworldremote`` plugins do.
+
; The first step is to add your plugin to the list of plugins :
 +
<blockquote>
 +
;* Open <code>dfhack/plugins/CMakeLists.txt</code>
 +
;* Locate the section listing all the plugins : they start with <code>DFHACK_PLUGIN</code>
 +
;* Add a line with your own plugin: <code>DFHACK_PLUGIN(myplugin myplugin.cpp)</code> , and save the file.
 +
;* Create a blank file called <code>myplugin.cpp</code> in <code>dfhack/plugins</code> , alongside all the other plugins
 +
</blockquote>
 +
Now you need to run DFHack’s awesome scripts, to make them generate all the relevant project files for you: * Close VisualStudio * Observe all the batch files in <code>dfhack/build</code> starting with “generate” * Run your favourite one. For example you can choose <code>generate-MSVC-minimal.bat</code>
  
The libraries are all the 3rd-party libraries you want to use in your project.
+
Now if you re-open <code>dfhack.sln</code> (in <code>dfhack/build/VC2010/</code>) , you’ll notice that your project “myplugin” has magically appeared. But its’ not ready to compile yet! (the source file is empty, dummy)
The good news is that most libraries normally used in C++ have a high probability
 
to be already used by some other plugin.
 
 
 
For example:
 
* ``workflow`` links to ``Lua``.
 
* ``dfstream`` links to ``dfhack-tinythread``.
 
* ``SDL`` is available in any case.
 
 
 
The first step is to add your plugin to the list of plugins :
 
* Open ``dfhack/plugins/CMakeLists.txt``
 
* Locate the section listing all the plugins : they start with ``DFHACK_PLUGIN``
 
* Add a line with your own plugin: ``DFHACK_PLUGIN(myplugin myplugin.cpp)`` , and save the file.
 
* Create a blank file called ``myplugin.cpp`` in ``dfhack/plugins`` , alongside all the other plugins.
 
 
 
Now you need to run DFHack's scripts, to make them generate all the
 
relevant project files for you:
 
* Close Visual Studio
 
* Observe all the batch files in ``dfhack/build`` starting with "generate"
 
* Run your favourite one. For example you can choose ``generate-MSVC-minimal.bat``
 
 
Now if you re-open ``dfhack.sln`` (in ``dfhack/build/VC2010/``) , you'll
 
notice that your project "myplugin" is in the list. But it's not
 
ready to compile yet since the source file is empty.
 
  
 
== Mandatory C++ instructions ==
 
== Mandatory C++ instructions ==
  
Open any simple plugin (e.g. ``catsplosion.cpp`` ) and observe its structure.
+
Open any simple plugin (e.g. <code>catsplosion.cpp</code> ) and observe its structure.
  
You'll notice all the includes for using standard data structures (lists,
+
You’ll notice all the includes for using standard data structures (lists, vectors, etc.) :
vectors, etc.) ::
 
  
#include <iostream>
+
<pre>#include &lt;iostream&gt;
#include <cstdlib>
+
#include &lt;cstdlib&gt;
#include <assert.h>
+
#include &lt;assert.h&gt;
#include <climits>
+
#include &lt;climits&gt;
#include <stdlib.h> // for rand()
+
#include &lt;stdlib.h&gt; // for rand()
#include <algorithm> // for std::transform
+
#include &lt;algorithm&gt; // for std::transform
#include <vector>
+
#include &lt;vector&gt;
#include <list>
+
#include &lt;list&gt;
#include <iterator>
+
#include &lt;iterator&gt;
using namespace std;
+
using namespace std;</pre>
 +
More interestingly, you’ll notice all the includes allowing you to use DFHack:
  
More interestingly, you'll notice all the includes allowing you to use DFHack:
+
<pre>#include &quot;DFHack.h&quot;
 +
#include &quot;Core.h&quot;
 +
#include &quot;Console.h&quot;
 +
#include &quot;Export.h&quot;
 +
#include &quot;PluginManager.h&quot;
 +
#include &quot;DataDefs.h&quot;
  
#include "DFHack.h"
+
using namespace DFHack;</pre>
#include "Core.h"
+
Let’s imagine your plugin will be a command called from DFHack’s console. Well then, that command will call an actual function. It is that function that you declare here:
#include "Console.h"
 
#include "Export.h"
 
#include "PluginManager.h"
 
#include "DataDefs.h"
 
  
using namespace DFHack;
+
<pre>command_result catsplosion (color_ostream &amp;out, std::vector &lt;std::string&gt; &amp; parameters);</pre>
 +
For now, the function only gets declared, we’ll define it later in the source code. You’ll need to do the same, by replacing <code>catsplosion</code> with <code>myplugin</code>
  
 +
Now, tell DFHack’s global plugin engine to be aware of our plugin (so that it can load it, etc.):
  
Let's imagine your plugin will be a command called from DFHack's console. Well
+
<pre>DFHACK_PLUGIN(&quot;catsplosion&quot;);</pre>
then, that command will call an actual function. It is that function that you
+
From now on, every time you see “catsplosion”, replace it with “myplugin”.
declare here::
 
  
command_result catsplosion (color_ostream &out, std::vector <std::string> & parameters);
+
; Each plugin has two central functions:
 +
<blockquote>
 +
* <code>plugin_init</code>. This is run when the plugin gets loaded, whether or not its command gets run from the command line.
 +
* <code>plugin_shutdown</code>. This is run when the plugin gets unloaded (i.e. when DFHack shuts down).
 +
</blockquote>
 +
You’ll notice that catsplosion’s <code>plugin_shutdown</code> is empty (it just returns the standardized “OK” return code : <code>CR_OK</code>). That’s because the plugin is simple and doesn’t need to delete objects, or close connections, or do any kind of post-processing.
  
For now, the function only gets declared, we'll define it later in the source
+
However, catsplosion’s <code>plugin_init</code> contains an important instruction, that will tell DFHack to be aware of a new custom command-line instruction:
code. You'll need to do the same, by replacing ``catsplosion`` with ``myplugin``
 
 
 
Now, tell DFHack's global plugin engine to be aware of our plugin (so that it
 
can load it, etc.):
 
 
 
DFHACK_PLUGIN("catsplosion");
 
 
 
From now on, every time you see "catsplosion", replace it with "myplugin".
 
 
 
Each plugin has two central functions:
 
* ``plugin_init``. This is run when the plugin gets loaded, whether or not its command
 
gets run from the command line.
 
* ``plugin_shutdown``. This is run when the plugin gets unloaded (i.e. when DFHack shuts
 
down).
 
 
 
You'll notice that catsplosion's ``plugin_shutdown`` is empty (it just returns
 
the standardized "OK" return code : ``CR_OK``). That's because the plugin is
 
simple and doesn't need to delete objects, or close connections, or do any kind
 
of post-processing.
 
 
 
However, catsplosion's ``plugin_init`` contains an important instruction, that  
 
will tell DFHack to be aware of a new custom command-line instruction::
 
 
 
    // Fill the command list with your commands.
 
    commands.push_back(PluginCommand(
 
        "catsplosion", "Make cats just /multiply/.",
 
        catsplosion, false,
 
        "  Makes cats abnormally abundant, if you provide some base population ;)\n"
 
    ));
 
  
 +
<pre>// Fill the command list with your commands.
 +
commands.push_back(PluginCommand(
 +
    &quot;catsplosion&quot;, &quot;Make cats just /multiply/.&quot;,
 +
    catsplosion, false,
 +
    &quot;  Makes cats abnormally abundant, if you provide some base population ;)\n&quot;
 +
));</pre>
 
Details:
 
Details:
* ``"catsplosion"`` will be the string to which the command line must respond ,when
 
entered.
 
* ``catsplosion`` is the function that must be called when the command is typed
 
in to the command line.
 
* ``"Make cats just multiply"`` and ``"Make cats abnormally ..."`` are the
 
"manual" that will be displayed in the help or when the command is called
 
with the wrong parameters (see below how to test that).
 
  
Finally, you'll find the actual function definition::
+
<blockquote>
 +
* <code>&quot;catsplosion&quot;</code> will be the string to which the command line must respond ,when entered.
 +
* <code>catsplosion</code> is the function that must be called when the command is typed intot he command line.
 +
* <code>&quot;Make cats just multiply&quot;</code> and <code>&quot;Make cats abnormally ...&quot;</code> are the “manual” that will be displayed in the help or when the command is called with the wrong parameters (see below how to test that).
 +
</blockquote>
 +
Finally, you’ll find the actual function definition:
  
command_result catsplosion (color_ostream &out, std::vector <std::string> & parameters)
+
<pre>command_result catsplosion (color_ostream &amp;out, std::vector &lt;std::string&gt; &amp; parameters)
{
+
{
...
+
    ...
}
+
}
return CR_OK;
+
return CR_OK;</pre>
 
 
 
== Simplest possible plugin: Hello world! ==
 
== Simplest possible plugin: Hello world! ==
  
Here is how to just have a message displayed in the DFHack console when someone enters
+
Here is how to just have a message displayed in the DFHack console when someone enters your command:
your command::
 
 
 
 
 
command_result myplugin (color_ostream &out, std::vector <std::string> & parameters)
 
{
 
out.print("Hello Monsieur Ouxx!\n");
 
}
 
return CR_OK;
 
 
 
Please note that you have other outputs available::
 
  
out.printerr("Oh my gosh, this is an error.\n");
+
<pre>command_result myplugin (color_ostream &amp;out, std::vector &lt;std::string&gt; &amp; parameters)
 +
{
 +
    out.print(&quot;Hello Monsieur Ouxx!\n&quot;);
 +
}
 +
return CR_OK;</pre>
 +
Please note that you have other outputs available:
  
You can now test your plugin: Press F7, then run DF and enter "myplugin" in the DFHack console.
+
<pre>out.printerr(&quot;Oh my gosh, this is an error.\n&quot;);</pre>
You should see the message "Hello Monsieur Ouxx!".
+
You can now test your plugin : Press F7, then run DF and enter “myplugin” in the DFHack console. You should see the message “Hello Monsieur Ouxx!.
  
 
== Parameters checking ==
 
== Parameters checking ==
  
You might want to test that your command was called with the relevant parameters.
+
You might want to test that your command was called with the relevant parameters. Here is an example of how to do it:
Here is an example of how to do it:
 
  
command_result myplugin (color_ostream &out, std::vector <std::string> & parameters)
+
<pre>command_result myplugin (color_ostream &amp;out, std::vector &lt;std::string&gt; &amp; parameters)
{
+
{
bool param1_ok, param2_ok;
+
    bool param1_ok, param2_ok;
for(size_t i = 0; i < parameters.size();i++)
+
    for(size_t i = 0; i &lt; parameters.size();i++)
{
+
    {
if(parameters[i] == "param1")
+
        if(parameters[i] == &quot;param1&quot;)
param1_ok = true;
+
            param1_ok = true;
else if(parameters[i] == "param2")
+
        else if(parameters[i] == &quot;param2&quot;)
param2_ok = true;
+
            param2_ok = true;
else
+
        else
return CR_WRONG_USAGE;
+
            return CR_WRONG_USAGE;
}
+
    }
if(!param1_ok && !param2_ok)
+
    if(!param1_ok &amp;&amp; !param2_ok)
return CR_WRONG_USAGE;
+
        return CR_WRONG_USAGE;
 
return CR_OK;
 
}
 
  
As explained before, if ``CR_WRONG_USAGE`` is returned, then the verbose help
+
    return CR_OK;
text defined in ``commands.push_back`` will be displayed.
+
}</pre>
 +
As explained before, if <code>CR_WRONG_USAGE</code> is returned, then the verbose help text defined in <code>commands.push_back</code> will be displayed.
  
Another interesting approach to that (as seen in plugin "buildingplan"):
+
Another interesting approach to that (as seen in plugin “buildingplan”):
  
if (!parameters.empty())
+
<pre>if (!parameters.empty())
{
+
{
if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
+
    if (parameters.size() == 1 &amp;&amp; toLower(parameters[0])[0] == 'v')
{
+
    {
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
+
        out &lt;&lt; &quot;Building Plan&quot; &lt;&lt; endl &lt;&lt; &quot;Version: &quot; &lt;&lt; PLUGIN_VERSION &lt;&lt; endl;
}
+
    }
else if (parameters.size() == 2 && toLower(parameters[0]) == "debug")
+
    else if (parameters.size() == 2 &amp;&amp; toLower(parameters[0]) == &quot;debug&quot;)
{
+
    {
show_debugging = (toLower(parameters[1]) == "on");
+
        show_debugging = (toLower(parameters[1]) == &quot;on&quot;);
out << "Debugging " << ((show_debugging) ? "enabled" : "disabled") << endl;
+
        out &lt;&lt; &quot;Debugging &quot; &lt;&lt; ((show_debugging) ? &quot;enabled&quot; : &quot;disabled&quot;) &lt;&lt; endl;
}         
+
    }         
}
+
}</pre>
 
== Suspend the core! ==
 
  
This might be the '''MOST IMPORTANT''' section in this document.
+
== Suspend the core!!!!!111!!11 ==
Never ever forget to suspend the game's core execution before you manipulate
 
anything in its internal data. It is done by running the following instruction::
 
  
CoreSuspender suspend;
+
This might be the MOST IMPORTANT section in this document. Never ever forget to suspend the game’s core execution before you manipulate anything in its internal data. It is done by running the following instruction:
  
You will observe that most plugins actually start with that instruction.
+
<pre>CoreSuspender suspend;</pre>
Fortunately, our "Hello world" plugin doesn't manipulate any internal data,
+
You will observe that most plugins actually start with that instruction. Fortunately, our “Hello world” plugin doesn’t manipulate any internal data, so we didn’t need that. But that’s about to change.
so we didn't need that. But that's about to change.
 
  
 
 
== Create your own view screen ==
 
== Create your own view screen ==
  
Many plugins consist in adding menus to DF in order to extend capabilities  
+
Many plugins consist in adding menus to DF in order to extend capabilities (or, at least, sort out DF’s mess ;) ).
(or, at least, sort out DF's mess ;) ).
 
 
 
  
A good example of that is plugin ``buildingplan``. It displays a menu with  
+
A good example of that is plugin <code>buildingplan</code>. It displays a menu with lists of materials, and allows to filter the materials. Lists and filters. That’s what DF is all about! ;)
lists of materials, and allows to filter the materials. Lists and filters.  
 
That's what DF is all about! ;)
 
  
 
http://imageshack.us/a/img28/4686/materials.png
 
http://imageshack.us/a/img28/4686/materials.png
  
You make them fit into a "view screen", that is: an additional screen  
+
You make them fit into a “view screen”, that is: an additional screen that will fit into DF. This section is about creating a NEW viewscreen, not replacing one already existing in DF.
that will fit into DF. This section is about creating a NEW viewscreen, not  
 
replacing one already existing in DF.
 
  
Have a look at class ``ViewscreenChooseMaterial`` : it extends DFHack-provided  
+
Have a look at class <code>ViewscreenChooseMaterial</code> : it extends DFHack-provided class <code>dfhack_viewscreen</code>:
class ``dfhack_viewscreen``:
 
  
class ViewscreenChooseMaterial : public dfhack_viewscreen
+
<pre>class ViewscreenChooseMaterial : public dfhack_viewscreen
{
+
{
...
+
...</pre>
 +
It needs to implement the two following functions:
  
It needs to implement the two following functions::
+
<pre>void feed(set&lt;df::interface_key&gt; *input)
 +
{
 +
    ...
 +
}
  
    void feed(set<df::interface_key> *input)
+
void render()
{
+
{
...
+
    ...
}
+
    dfhack_viewscreen::render();
+
    ...
void render()
+
}</pre>
{
+
Notice how <code>render</code> calls the overridden function of its parent class.
...
 
dfhack_viewscreen::render();
 
...
 
}
 
  
Notice how ``render`` calls the overridden function of its parent class.
+
You will find more examples in plugin <code>manipulator</code>. Have a look at class <code>viewscreen_unitlaborsst</code>
  
You will find more examples in plugin ``manipulator``. Have a look at class ``viewscreen_unitlaborsst``
+
See also: “Replacing an existing view screen”
 
See also: "Replacing an existing view screen"
 
  
 
 
== Manipulate the display ==
 
== Manipulate the display ==
  
Once your view screen is up and running, with its very own ``render``
+
Once your view screen is up and running, with its very own <code>render</code> function, you may do whatever you please with the display:
function, you may do whatever you please with the display::
 
 
 
        Screen::clear(); //delete the screen
 
        Screen::drawBorder("  Building Material  "); //create a new DF-stryle screen, with a title and a border
 
 
 
        masks_column.display(selected_column == 0); //display our column (read this tutorial further)
 
 
 
        int32_t y = gps->dimy - 3; //do some calculation on the window size, to position stuff
 
        OutputHotkeyString(2, y, "Toggle", "Enter"); //define some hotkey
 
        x += 3;
 
        OutputHotkeyString(x, y, "Save", "Shift-Enter");
 
  
 +
<pre>Screen::clear();                                    //delete the screen
 +
Screen::drawBorder(&quot;  Building Material  &quot;);        //create a new DF-stryle screen, with a title and a border
  
 +
masks_column.display(selected_column == 0);        //display our column (read this tutorial further)
  
 +
int32_t y = gps-&gt;dimy - 3;                          //do some calculation on the window size, to position stuff
 +
OutputHotkeyString(2, y, &quot;Toggle&quot;, &quot;Enter&quot;);        //define some hotkey
 +
x += 3;
 +
OutputHotkeyString(x, y, &quot;Save&quot;, &quot;Shift-Enter&quot;);</pre>
 
== List columns ==
 
== List columns ==
  
In class ``ViewscreenChooseMaterial`` 's private members, you'll see this::
+
In class <code>ViewscreenChooseMaterial</code> ’s private members, you’ll see this:
  
ListColumn<df::dfhack_material_category> masks_column;
+
<pre>ListColumn&lt;df::dfhack_material_category&gt; masks_column;</pre>
+
<code>ListColumn</code> is a template provided by DFHack. Just feed it with anything you like.
``ListColumn`` is a template provided by DFHack. Just feed it with anything you like.
 
  
 +
== Manipulating DF’s data ==
  
== Manipulating DF's data ==
+
Start with observing class <code>ItemFilter</code>. You’ll notice that DF exposes pretty much every possible type of object/item from the game. And what DF doesn’t, DFHack does. You’ll immediately notice these:: df::dfhack_material_category DFHack::MaterialInfo df::enums::item
 +
<blockquote>etc.
 +
</blockquote>
 +
Have a look at <code>vector&lt;string&gt; getMaterialFilterAsVector()</code>. It shows you how to get the description from a material, as a string:
  
Start with observing class ``ItemFilter``. You'll notice that DF exposes
+
<pre>transform_(materials, descriptions, material_to_string_fn);
pretty much every possible type of object/item from the game. And what DF
 
doesn't, DFHack does. You'll immediately notice these::
 
df::dfhack_material_category
 
DFHack::MaterialInfo
 
df::enums::item_quality
 
etc.
 
  
Have a look at ``vector<string> getMaterialFilterAsVector()``. It shows
+
if (descriptions.size() == 0)
you how to get the description from a material, as a string::
+
    bitfield_to_string(&amp;descriptions, mat_mask);</pre>
 +
<blockquote>
  
 +
== Managing speed ==
  
        transform_(materials, descriptions, material_to_string_fn);
+
Some plugins need to run regular processing, but at a certain rate, in order not to block DF. Here is how <code>buildingplan</code> handles game ticks (important: this is only provided as an out-of-context code snippet, with no further explanations):
  
        if (descriptions.size() == 0)
+
<pre>#define DAY_TICKS 1200
            bitfield_to_string(&descriptions, mat_mask);
+
DFhackCExport command_result plugin_onupdate(color_ostream &amp;out)
+
{
 
+
    static decltype(world-&gt;frame_counter) last_frame_count = 0;
 
+
    if ((world-&gt;frame_counter - last_frame_count) &gt;= DAY_TICKS/2)
==  Managing speed ==
+
    {
+
        last_frame_count = world-&gt;frame_counter;
Some plugins need to run regular processing, but at a certain rate,
+
        planner.doCycle();
in order not to block DF. Here is how ``buildingplan`` handles game
+
    }
ticks (important: this is only provided as an out-of-context code
 
snippet, with no further explanations)::
 
 
 
#define DAY_TICKS 1200
 
DFhackCExport command_result plugin_onupdate(color_ostream &out)
 
{
 
static decltype(world->frame_counter) last_frame_count = 0;
 
if ((world->frame_counter - last_frame_count) >= DAY_TICKS/2)
 
{
 
last_frame_count = world->frame_counter;
 
planner.doCycle();
 
}
 
 
 
return CR_OK;
 
}
 
 
==  Replacing an existing view screen ==
 
 
Maybe you don't want to add an additional view screen, but instead add
 
features to one that already exists in DF.
 
In that case, creating a child to ``dfhack_viewscreen``` (as seen in
 
section "Create your own view screen") and overriding ``render`` and
 
``feed`` is not enough. You need to "interpose" those two functions
 
in the global DF system.
 
 
 
That's the case of class ``buildingplan_hook`` in plugin ``buildingplan``.
 
 
 
You will notice that, unlike ``ViewscreenChooseMaterial``, it extends class
 
``df::viewscreen_dwarfmodest`` instead of ``dfhack_viewscreen``::
 
 
 
struct buildingplan_hook : public df::viewscreen_dwarfmodest
 
{
 
  
 +
    return CR_OK;
 +
}</pre>
  
The following instructions are then required to glue everything together::
+
== Replacing an existing view screen ==
 +
</blockquote>
 +
Maybe you don’t want to add an additional view screen, but instead add features to one that already exists in DF. In that case, creating a child to <code>dfhack_viewscreen</code>` (as seen in section “Create your own view screen”) and overriding <code>render</code> and <code>feed</code> is not enough. You need to “interpose” those two functions in the global DF system.
  
    DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
+
That’s the case of class <code>buildingplan_hook</code> in plugin <code>buildingplan</code>.
    {
 
        ...
 
  
    DEFINE_VMETHOD_INTERPOSE(void, render, ())
+
You will notice that, unlike <code>ViewscreenChooseMaterial</code>, it extends class <code>df::viewscreen_dwarfmodest</code> instead of <code>dfhack_viewscreen</code>:
    {
 
...
 
}
 
  
Once you've defined how the two functions will insert into DF, you can put them to action::
+
<pre>struct buildingplan_hook : public df::viewscreen_dwarfmodest
 +
{</pre>
 +
The following instructions are then required to glue everything together:
  
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed);
+
<pre>DEFINE_VMETHOD_INTERPOSE(void, feed, (set&lt;df::interface_key&gt; *input))
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render);
+
{
 +
    ...
  
 +
DEFINE_VMETHOD_INTERPOSE(void, render, ())
 +
 +
    ...
 +
}</pre>
 +
Once you’ve defined how the two functions will insert into DF, you can put them to action:
  
 +
<pre>IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed);
 +
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render);</pre>
 
Note: That would need more explanations.
 
Note: That would need more explanations.
  
See also: "Create your own view screen"
+
See also: “Create your own view screen”
 
 
 
 
 
 
  
 
== CHANGE LOG ==
 
== CHANGE LOG ==
* v1.0 Redaction
+
<blockquote>
* v1.1  
+
* v1.0 Redaction
- moved "return CR_OK;" in one of the code snippets
+
* v1.1
- corrected a mistake about the need of using "DEFINE_VMETHOD_INTERPOSE"
+
** moved “return CR_OK;in one of the code snippets
 +
** corrected a mistake about the need of using “DEFINE_VMETHOD_INTERPOSE”
 +
</blockquote>

Revision as of 11:33, 28 August 2014

v1.1 (see change log at the end)


INTRODUCTION

DFHack is a Dwarf Fortress (DF) memory access library and a set of basic tools that use it (see the readme at https://github.com/peterix/dfhack/blob/master/Readme.rst ).

It is possible for developers to write plugins for DFHack, in order to extend the capabilities of DFHack (and therefore DF’s gameplay). The plugins come mainly in two flavours: * C++ plugins: libraries written in C++, that need to be compiled. They can use DFHack’s low level functions very closely. * Lua and Ruby scripts: placed in the right folder, they are loaded at DFHack’s startup (outside of this document’s scope. see more at https://github.com/peterix/dfhack/blob/master/Lua%20API.rst )

This document explains how to get started at writing C++ plugins with VisualC++ 2010, step by step. It is intended for people who are totally new to DFHack.

Getting Dwarf Fortress

You’ll need a test environment to run your script. Hence the need of getting DF. I’d recommend getting the latest “Lazy Newb Pack”, which includes not only Dwarf Fortress but also DFHack and other nice things, ready-to-use:

  • Download the latest “Lazy Newb Pack”.

You can run DF once and observe the DFHack console. Type die in that window to observe that the command line works (die is a command to force-close DF).

Getting DFHack

You must download the sources, and build them.

Make yourself comfy

As explained in the compiling guidelines (above), there are several scripts available, that will do different things : some will just build, some will also install the results to your DF’s DFHack folder. To save some time, here is to run the latter script in your VisualC++ every time you press F7 (Generate solution):

  • Open dfhack.sln in VisualC++
  • Right-click on the Solution, and select “Properties”
  • Go to “configuration properties”–>“Configuration”
  • In the full list of projects, uncheck ALL_BUILD, and instead check INSTALL

This way, it will force-build “INSTALL” every time. And guess what? That runs the install script.

Create your very own plugin

What are the requirements for a plugin?
  • Its set of C++ source files (there can be just one if you want!)
  • The libraries (.lib) it will use
  • The protobufs it will use (if any!)

Protobufs are some sort of serialization standard developed by Google. This is outside of the scope of this document. Only a handful of plugins use that technology. The rename and isoworldremote plugins do.

The libraries are all the 3rd-party libraries you want to use in your project. The good news is that most libraries normally used in C++ have a high probability to be already used by some other plugin.

For example
  • workflow links to Lua.
  • dfstream links to dfhack-tinythread.
  • I believe I’ve seen a plugin using boost.
  • SDL is available in any case.
The first step is to add your plugin to the list of plugins
  • Open dfhack/plugins/CMakeLists.txt
  • Locate the section listing all the plugins : they start with DFHACK_PLUGIN
  • Add a line with your own plugin: DFHACK_PLUGIN(myplugin myplugin.cpp) , and save the file.
  • Create a blank file called myplugin.cpp in dfhack/plugins , alongside all the other plugins

Now you need to run DFHack’s awesome scripts, to make them generate all the relevant project files for you: * Close VisualStudio * Observe all the batch files in dfhack/build starting with “generate” * Run your favourite one. For example you can choose generate-MSVC-minimal.bat

Now if you re-open dfhack.sln (in dfhack/build/VC2010/) , you’ll notice that your project “myplugin” has magically appeared. But its’ not ready to compile yet! (the source file is empty, dummy)

Mandatory C++ instructions

Open any simple plugin (e.g. catsplosion.cpp ) and observe its structure.

You’ll notice all the includes for using standard data structures (lists, vectors, etc.) :

#include <iostream>
#include <cstdlib>
#include <assert.h>
#include <climits>
#include <stdlib.h> // for rand()
#include <algorithm> // for std::transform
#include <vector>
#include <list>
#include <iterator>
using namespace std;

More interestingly, you’ll notice all the includes allowing you to use DFHack:

#include "DFHack.h"
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"

using namespace DFHack;

Let’s imagine your plugin will be a command called from DFHack’s console. Well then, that command will call an actual function. It is that function that you declare here:

command_result catsplosion (color_ostream &out, std::vector <std::string> & parameters);

For now, the function only gets declared, we’ll define it later in the source code. You’ll need to do the same, by replacing catsplosion with myplugin

Now, tell DFHack’s global plugin engine to be aware of our plugin (so that it can load it, etc.):

DFHACK_PLUGIN("catsplosion");

From now on, every time you see “catsplosion”, replace it with “myplugin”.

Each plugin has two central functions
  • plugin_init. This is run when the plugin gets loaded, whether or not its command gets run from the command line.
  • plugin_shutdown. This is run when the plugin gets unloaded (i.e. when DFHack shuts down).

You’ll notice that catsplosion’s plugin_shutdown is empty (it just returns the standardized “OK” return code : CR_OK). That’s because the plugin is simple and doesn’t need to delete objects, or close connections, or do any kind of post-processing.

However, catsplosion’s plugin_init contains an important instruction, that will tell DFHack to be aware of a new custom command-line instruction:

// Fill the command list with your commands.
commands.push_back(PluginCommand(
    "catsplosion", "Make cats just /multiply/.",
    catsplosion, false,
    "  Makes cats abnormally abundant, if you provide some base population ;)\n"
));

Details:

  • "catsplosion" will be the string to which the command line must respond ,when entered.
  • catsplosion is the function that must be called when the command is typed intot he command line.
  • "Make cats just multiply" and "Make cats abnormally ..." are the “manual” that will be displayed in the help or when the command is called with the wrong parameters (see below how to test that).

Finally, you’ll find the actual function definition:

command_result catsplosion (color_ostream &out, std::vector <std::string> & parameters)
{
    ...
}
return CR_OK;

Simplest possible plugin: Hello world!

Here is how to just have a message displayed in the DFHack console when someone enters your command:

command_result myplugin (color_ostream &out, std::vector <std::string> & parameters)
{
    out.print("Hello Monsieur Ouxx!\n");
}
return CR_OK;

Please note that you have other outputs available:

out.printerr("Oh my gosh, this is an error.\n");

You can now test your plugin : Press F7, then run DF and enter “myplugin” in the DFHack console. You should see the message “Hello Monsieur Ouxx!”.

Parameters checking

You might want to test that your command was called with the relevant parameters. Here is an example of how to do it:

command_result myplugin (color_ostream &out, std::vector <std::string> & parameters)
{
    bool param1_ok, param2_ok;
    for(size_t i = 0; i < parameters.size();i++)
    {
        if(parameters[i] == "param1")
            param1_ok = true;
        else if(parameters[i] == "param2")
            param2_ok = true;
        else
            return CR_WRONG_USAGE;
    }
    if(!param1_ok && !param2_ok)
        return CR_WRONG_USAGE;

    return CR_OK;
}

As explained before, if CR_WRONG_USAGE is returned, then the verbose help text defined in commands.push_back will be displayed.

Another interesting approach to that (as seen in plugin “buildingplan”):

if (!parameters.empty())
{
    if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
    {
        out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
    }
    else if (parameters.size() == 2 && toLower(parameters[0]) == "debug")
    {
        show_debugging = (toLower(parameters[1]) == "on");
        out << "Debugging " << ((show_debugging) ? "enabled" : "disabled") << endl;
    }        
}

Suspend the core!!!!!111!!11

This might be the MOST IMPORTANT section in this document. Never ever forget to suspend the game’s core execution before you manipulate anything in its internal data. It is done by running the following instruction:

CoreSuspender suspend;

You will observe that most plugins actually start with that instruction. Fortunately, our “Hello world” plugin doesn’t manipulate any internal data, so we didn’t need that. But that’s about to change.

Create your own view screen

Many plugins consist in adding menus to DF in order to extend capabilities (or, at least, sort out DF’s mess ;) ).

A good example of that is plugin buildingplan. It displays a menu with lists of materials, and allows to filter the materials. Lists and filters. That’s what DF is all about! ;)

http://imageshack.us/a/img28/4686/materials.png

You make them fit into a “view screen”, that is: an additional screen that will fit into DF. This section is about creating a NEW viewscreen, not replacing one already existing in DF.

Have a look at class ViewscreenChooseMaterial : it extends DFHack-provided class dfhack_viewscreen:

class ViewscreenChooseMaterial : public dfhack_viewscreen
{
...

It needs to implement the two following functions:

void feed(set<df::interface_key> *input)
{
    ...
}

void render()
{
    ...
    dfhack_viewscreen::render();
    ...
}

Notice how render calls the overridden function of its parent class.

You will find more examples in plugin manipulator. Have a look at class viewscreen_unitlaborsst

See also: “Replacing an existing view screen”

Manipulate the display

Once your view screen is up and running, with its very own render function, you may do whatever you please with the display:

Screen::clear();                                    //delete the screen
Screen::drawBorder("  Building Material  ");        //create a new DF-stryle screen, with a title and a border

masks_column.display(selected_column == 0);         //display our column (read this tutorial further)

int32_t y = gps->dimy - 3;                          //do some calculation on the window size, to position stuff
OutputHotkeyString(2, y, "Toggle", "Enter");        //define some hotkey
x += 3;
OutputHotkeyString(x, y, "Save", "Shift-Enter");

List columns

In class ViewscreenChooseMaterial ’s private members, you’ll see this:

ListColumn<df::dfhack_material_category> masks_column;

ListColumn is a template provided by DFHack. Just feed it with anything you like.

Manipulating DF’s data

Start with observing class ItemFilter. You’ll notice that DF exposes pretty much every possible type of object/item from the game. And what DF doesn’t, DFHack does. You’ll immediately notice these:: df::dfhack_material_category DFHack::MaterialInfo df::enums::item

etc.

Have a look at vector<string> getMaterialFilterAsVector(). It shows you how to get the description from a material, as a string:

transform_(materials, descriptions, material_to_string_fn);

if (descriptions.size() == 0)
    bitfield_to_string(&descriptions, mat_mask);

Managing speed

Some plugins need to run regular processing, but at a certain rate, in order not to block DF. Here is how buildingplan handles game ticks (important: this is only provided as an out-of-context code snippet, with no further explanations):

#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
    static decltype(world->frame_counter) last_frame_count = 0;
    if ((world->frame_counter - last_frame_count) >= DAY_TICKS/2)
    {
        last_frame_count = world->frame_counter;
        planner.doCycle();
    }

    return CR_OK;
}

Replacing an existing view screen

Maybe you don’t want to add an additional view screen, but instead add features to one that already exists in DF. In that case, creating a child to dfhack_viewscreen` (as seen in section “Create your own view screen”) and overriding render and feed is not enough. You need to “interpose” those two functions in the global DF system.

That’s the case of class buildingplan_hook in plugin buildingplan.

You will notice that, unlike ViewscreenChooseMaterial, it extends class df::viewscreen_dwarfmodest instead of dfhack_viewscreen:

struct buildingplan_hook : public df::viewscreen_dwarfmodest
{

The following instructions are then required to glue everything together:

DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
    ...

DEFINE_VMETHOD_INTERPOSE(void, render, ())
{   
    ...
}

Once you’ve defined how the two functions will insert into DF, you can put them to action:

IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render);

Note: That would need more explanations.

See also: “Create your own view screen”

CHANGE LOG

  • v1.0 Redaction
  • v1.1
    • moved “return CR_OK;” in one of the code snippets
    • corrected a mistake about the need of using “DEFINE_VMETHOD_INTERPOSE”