dfhack/plugins/ruby
jj ba7d4f8edd ruby: extract additional info for enums 2012-04-26 20:03:56 +02:00
..
plugins ruby: add plugins/unit exemple 2012-04-26 19:38:15 +02:00
CMakeLists.txt ruby: clean helper with offsetof 2012-04-21 21:20:51 +02:00
README ruby: fix new c++ obj initialization, use xml init-value 2012-04-26 14:16:47 +02:00
codegen.pl ruby: extract additional info for enums 2012-04-26 20:03:56 +02:00
ruby-memstruct.rb ruby: add vector#binsearch 2012-04-26 19:37:58 +02:00
ruby.cpp ruby: fix plugin/plant.rb, add onstatechange hook, use console proxy, general cleanup, enable build by default 2012-04-25 20:21:09 +02:00
ruby.rb ruby: fix new c++ obj initialization, use xml init-value 2012-04-26 14:16:47 +02:00

README

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 DFHack::Profession::ENUM[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 have a special behavior: they accept and forward any method to
the pointed object. To retrieve the pointed object directly, use the _getv
method. Null pointers resolve to 'nil'.
Beware, invalid pointers (!= 0) will crash the plugin and the game.

This allows to use code such as 'df.world.units.all[0].pos', with all[0] being
really a Pointer (with no 'pos' method).


Todo
----

Correct c++ object (de)allocation (call ctor etc) ; ability to call vtable methods