114 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Plaintext
		
	
			
		
		
	
	
			114 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Plaintext
		
	
| 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
 |