This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).
The plugin maps all the structures available in library/xml/ to ruby objects.
These objects are described in ruby-autogen.rb, they are all in the DFHack::
module. The toplevel 'df' method returs the DFHack module.
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
access to the raw DF data structures in memory is provided.
Some library methods are stored in the ruby.rb file, with shortcuts to read a
map block, find an unit or an item, etc.
Global objects are stored in the GlobalObjects class ; each object accessor is
mirrored as a DFHack module method.
The ruby plugin defines 2 dfhack console commands:
rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes)
rb_eval <ruby expression> ; evaluate a ruby expression, show the result in the
console. Ex: rb_eval df.find_unit.name.first_name
You can use single-quotes for strings ; avoid double-quotes that are parsed
and removed by the dfhack console.
The plugin also interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use:
handle = df.onupdate_register { puts 'i love flood' }
To stop being called, use:
df.onupdate_unregister handle
Exemples
--------
For more complex exemples, check the ruby/plugins/ folder.
Show info on the currently selected unit ('v' or 'k' DF menu)
p df.find_unit.flags1
Set a custom nickname to unit with id '123'
df.find_unit(123).name.nickname = 'moo'
Show current unit profession
p df.find_unit.profession
Center the screen on unit '123'
df.center_viewscreen(df.find_unit(123))
Find an item at a given position, show its C++ classname
df.find_item(df.cursor)._rtti_classname
Find the raws name of the plant under cursor
plant = df.world.plants.all.find { |p| df.at_cursor?(p) }
df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor
df.map_designation_at(df.cursor).dig = TileDigDesignation::Channel
df.map_block_at(df.cursor).flags.designated = true
Compilation
-----------
The plugin consists of the ruby.rb file including user comfort functions ;
ruby-memstruct.rb describing basic classes used by the autogenerated code, and
embedded at the beginnig of ruby-autogen.rb, and the generated code.
The generated code is generated by codegen.pl, which takes the codegen.out.xml
file as input.
One of the limitations of the xml file is that it does not include structure
offsets, as they depend on the compiler. To overcome that, codegen runs in two
passes. The first pass generates a ruby-autogen.cpp file, that will output the
structure offsets ; the second pass will generate the ruby-autogen.rb using the
output of the compiled ruby-autogen.cpp.
For exemple, from
<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"/>
<ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
We generate the cpp
printf("%s = %d", "offsetof(df::unit, language_name)", offsetof(df::unit, language_name));
printf("%s = %d", "offsetof(df::unit, custom_profession)", offsetof(df::unit, custom_profession));
printf("%s = %d", "offsetof(df::unit, profession)", offsetof(df::unit, profession));
Which generates (on linux)
offsetof(df::unit, name) = 0
offsetof(df::unit, custom_profession) = 60
offsetof(df::unit, profession) = 64
Which generates
class Unit < MemHack::Compound
field(:name, 0) { global :LanguageName }
field(:custom_profession, 60) { stl_string }
field(:profession, 64) { number 16, true }
The field method has 2 arguments: the name of the method and the member offset ;
the block specifies the member type. See ruby-memstruct.rb for more information.
Primitive type access is done through native methods in ruby.cpp (vector length,
raw memory access, etc)
MemHack::Pointers are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. Null pointers yield the 'nil' value.
This allows to use code such as 'df.world.units.all[0].pos', with 'all' being
really a vector of pointer.
Todo
----
Correct c++ object (de)allocation (call ctor etc) ; ability to call vtable methods