Merge branch 'ruby' of https://github.com/jjyg/dfhack
commit
489f22e550
@ -0,0 +1,29 @@
|
||||
find_package(Ruby)
|
||||
if(RUBY_FOUND)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ruby-autogen.cpp
|
||||
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.cpp
|
||||
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl
|
||||
)
|
||||
ADD_EXECUTABLE(ruby-autogen ruby-autogen.cpp)
|
||||
if(CMAKE_COMPILER_IS_GNUCC)
|
||||
set_target_properties (ruby-autogen PROPERTIES COMPILE_FLAGS "-Wno-invalid-offsetof")
|
||||
endif(CMAKE_COMPILER_IS_GNUCC)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ruby-autogen.offsets
|
||||
COMMAND ruby-autogen ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets
|
||||
DEPENDS ruby-autogen
|
||||
)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ruby-autogen.rb
|
||||
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb
|
||||
DEPENDS ruby-autogen.offsets ruby-memstruct.rb
|
||||
)
|
||||
ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb)
|
||||
include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH})
|
||||
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
|
||||
target_link_libraries(ruby ${RUBY_LIBRARY})
|
||||
install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION})
|
||||
else(RUBY_FOUND)
|
||||
MESSAGE(STATUS "Required library (ruby) not found - ruby plugin can't be built.")
|
||||
endif(RUBY_FOUND)
|
@ -0,0 +1,113 @@
|
||||
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
|
@ -0,0 +1,708 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use XML::LibXML;
|
||||
|
||||
our @lines_rb;
|
||||
my @lines_cpp;
|
||||
my @include_cpp;
|
||||
my %offsets;
|
||||
|
||||
sub indent_rb(&) {
|
||||
my ($sub) = @_;
|
||||
my @lines;
|
||||
{
|
||||
local @lines_rb;
|
||||
$sub->();
|
||||
@lines = map { " " . $_ } @lines_rb;
|
||||
}
|
||||
push @lines_rb, @lines
|
||||
}
|
||||
|
||||
sub rb_ucase {
|
||||
my ($name) = @_;
|
||||
return $name if ($name eq uc($name));
|
||||
return join("", map { ucfirst $_ } (split('_', $name)));
|
||||
}
|
||||
|
||||
my %item_renderer = (
|
||||
'global' => \&render_item_global,
|
||||
'number' => \&render_item_number,
|
||||
'container' => \&render_item_container,
|
||||
'compound' => \&render_item_compound,
|
||||
'pointer' => \&render_item_pointer,
|
||||
'static-array' => \&render_item_staticarray,
|
||||
'primitive' => \&render_item_primitive,
|
||||
'bytes' => \&render_item_bytes,
|
||||
);
|
||||
|
||||
my %global_types;
|
||||
|
||||
sub render_global_enum {
|
||||
my ($name, $type) = @_;
|
||||
|
||||
my $rbname = rb_ucase($name);
|
||||
push @lines_rb, "class $rbname < MemHack::Enum";
|
||||
indent_rb {
|
||||
render_enum_fields($type);
|
||||
};
|
||||
push @lines_rb, "end\n";
|
||||
}
|
||||
sub render_enum_fields {
|
||||
my ($type) = @_;
|
||||
|
||||
my $value = -1;
|
||||
push @lines_rb, "ENUM = Hash.new";
|
||||
push @lines_rb, "NUME = Hash.new";
|
||||
|
||||
my %attr_type;
|
||||
my %attr_list;
|
||||
for my $attr ($type->findnodes('child::enum-attr')) {
|
||||
my $rbattr = rb_ucase($attr->getAttribute('name'));
|
||||
my $typeattr = $attr->getAttribute('type-name');
|
||||
# find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool)
|
||||
if ($typeattr) {
|
||||
if ($global_types{$typeattr}) {
|
||||
$attr_type{$rbattr} = 'symbol';
|
||||
} else {
|
||||
$attr_type{$rbattr} = 'naked';
|
||||
}
|
||||
} else {
|
||||
$attr_type{$rbattr} = 'quote';
|
||||
}
|
||||
|
||||
my $def = $attr->getAttribute('default-value');
|
||||
if ($attr->getAttribute('is-list')) {
|
||||
push @lines_rb, "$rbattr = Hash.new { |h, k| h[k] = [] }";
|
||||
$attr_list{$rbattr} = 1;
|
||||
} elsif ($def) {
|
||||
$def = ":$def" if ($attr_type{$rbattr} eq 'symbol');
|
||||
$def =~ s/'/\\'/g if ($attr_type{$rbattr} eq 'quote');
|
||||
$def = "'$def'" if ($attr_type{$rbattr} eq 'quote');
|
||||
push @lines_rb, "$rbattr = Hash.new($def)";
|
||||
} else {
|
||||
push @lines_rb, "$rbattr = Hash.new";
|
||||
}
|
||||
}
|
||||
|
||||
for my $item ($type->findnodes('child::enum-item')) {
|
||||
$value = $item->getAttribute('value') || ($value+1);
|
||||
my $elemname = $item->getAttribute('name'); # || "unk_$value";
|
||||
|
||||
if ($elemname) {
|
||||
my $rbelemname = rb_ucase($elemname);
|
||||
push @lines_rb, "ENUM[$value] = :$rbelemname ; NUME[:$rbelemname] = $value";
|
||||
for my $iattr ($item->findnodes('child::item-attr')) {
|
||||
my $ian = $iattr->getAttribute('name');
|
||||
my $iav = $iattr->getAttribute('value');
|
||||
my $rbattr = rb_ucase($ian);
|
||||
my $op = ($attr_list{$rbattr} ? '<<' : '=');
|
||||
$iav = ":$iav" if ($attr_type{$rbattr} eq 'symbol');
|
||||
$iav =~ s/'/\\'/g if ($attr_type{$rbattr} eq 'quote');
|
||||
$iav = "'$iav'" if ($attr_type{$rbattr} eq 'quote');
|
||||
$lines_rb[$#lines_rb] .= " ; ${rbattr}[:$rbelemname] $op $iav";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub render_global_bitfield {
|
||||
my ($name, $type) = @_;
|
||||
|
||||
push @lines_cpp, "}" if @include_cpp;
|
||||
push @lines_cpp, "void cpp_$name(FILE *fout) {";
|
||||
push @include_cpp, $name;
|
||||
|
||||
my $rbname = rb_ucase($name);
|
||||
push @lines_rb, "class $rbname < MemHack::Compound";
|
||||
indent_rb {
|
||||
render_bitfield_fields($type);
|
||||
};
|
||||
push @lines_rb, "end\n";
|
||||
}
|
||||
sub render_bitfield_fields {
|
||||
my ($type) = @_;
|
||||
|
||||
push @lines_rb, "field(:_whole, 0) {";
|
||||
indent_rb {
|
||||
render_item_number($type, '');
|
||||
};
|
||||
push @lines_rb, "}";
|
||||
|
||||
my $shift = 0;
|
||||
for my $field ($type->findnodes('child::ld:field')) {
|
||||
my $count = $field->getAttribute('count') || 1;
|
||||
my $name = $field->getAttribute('name');
|
||||
my $type = $field->getAttribute('type-name');
|
||||
my $enum = rb_ucase($type) if ($type and $global_types{$type});
|
||||
$name = $field->getAttribute('ld:anon-name') if (!$name);
|
||||
print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number'));
|
||||
if ($count == 1) {
|
||||
push @lines_rb, "field(:$name, 0) { bit $shift }" if ($name);
|
||||
} elsif ($enum) {
|
||||
push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }" if ($name);
|
||||
} else {
|
||||
push @lines_rb, "field(:$name, 0) { bits $shift, $count }" if ($name);
|
||||
}
|
||||
$shift += $count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub render_global_struct {
|
||||
my ($name, $type) = @_;
|
||||
|
||||
my $rbname = rb_ucase($name);
|
||||
|
||||
my $cppns = "df::$name";
|
||||
push @lines_cpp, "}" if @include_cpp;
|
||||
push @lines_cpp, "void cpp_$name(FILE *fout) {";
|
||||
push @include_cpp, $name;
|
||||
|
||||
push @lines_rb, "class $rbname < MemHack::Compound";
|
||||
indent_rb {
|
||||
my $sz = query_cpp("sizeof($cppns)");
|
||||
push @lines_rb, "sizeof $sz";
|
||||
render_struct_fields($type, "$cppns");
|
||||
};
|
||||
push @lines_rb, "end\n";
|
||||
}
|
||||
my %seen_class;
|
||||
sub render_global_class {
|
||||
my ($name, $type) = @_;
|
||||
|
||||
my $rbname = rb_ucase($name);
|
||||
|
||||
# ensure pre-definition of ancestors
|
||||
my $parent = $type->getAttribute('inherits-from');
|
||||
render_global_class($parent, $global_types{$parent}) if ($parent and !$seen_class{$parent});
|
||||
|
||||
return if $seen_class{$name};
|
||||
$seen_class{$name}++;
|
||||
|
||||
my $rtti_name;
|
||||
if ($type->getAttribute('ld:meta') eq 'class-type') {
|
||||
$rtti_name = $type->getAttribute('original-name') ||
|
||||
$type->getAttribute('type-name') ||
|
||||
$name;
|
||||
}
|
||||
|
||||
my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound');
|
||||
|
||||
my $cppns = "df::$name";
|
||||
push @lines_cpp, "}" if @include_cpp;
|
||||
push @lines_cpp, "void cpp_$name(FILE *fout) {";
|
||||
push @include_cpp, $name;
|
||||
|
||||
push @lines_rb, "class $rbname < $rbparent";
|
||||
indent_rb {
|
||||
my $sz = query_cpp("sizeof($cppns)");
|
||||
push @lines_rb, "sizeof $sz";
|
||||
push @lines_rb, "rtti_classname :$rtti_name" if $rtti_name;
|
||||
render_struct_fields($type, "$cppns");
|
||||
my $vms = $type->findnodes('child::virtual-methods')->[0];
|
||||
render_class_vmethods($vms) if $vms;
|
||||
};
|
||||
push @lines_rb, "end\n";
|
||||
}
|
||||
sub render_struct_fields {
|
||||
my ($type, $cppns) = @_;
|
||||
|
||||
for my $field ($type->findnodes('child::ld:field')) {
|
||||
my $name = $field->getAttribute('name');
|
||||
$name = $field->getAttribute('ld:anon-name') if (!$name);
|
||||
if (!$name and $field->getAttribute('ld:anon-compound')) {
|
||||
render_struct_fields($field, $cppns);
|
||||
}
|
||||
next if (!$name);
|
||||
my $offset = get_offset($cppns, $name);
|
||||
|
||||
push @lines_rb, "field(:$name, $offset) {";
|
||||
indent_rb {
|
||||
render_item($field, "$cppns");
|
||||
};
|
||||
push @lines_rb, "}";
|
||||
}
|
||||
}
|
||||
sub render_class_vmethods {
|
||||
my ($vms) = @_;
|
||||
my $voff = 0;
|
||||
for my $meth ($vms->findnodes('child::vmethod')) {
|
||||
my $name = $meth->getAttribute('name');
|
||||
if ($name) {
|
||||
my @argnames;
|
||||
my @argargs;
|
||||
for my $arg ($meth->findnodes('child::ld:field')) {
|
||||
my $nr = $#argnames + 1;
|
||||
my $argname = lcfirst($arg->getAttribute('name') || "arg$nr");
|
||||
push @argnames, $argname;
|
||||
if ($arg->getAttribute('ld:meta') eq 'global' and $arg->getAttribute('ld:subtype') eq 'enum') {
|
||||
push @argargs, rb_ucase($arg->getAttribute('type-name')) . ".to_i($argname)";
|
||||
} else {
|
||||
push @argargs, $argname;
|
||||
}
|
||||
}
|
||||
push @lines_rb, "def $name(" . join(', ', @argnames) . ')';
|
||||
indent_rb {
|
||||
my $args = join('', map { ", $_" } @argargs);
|
||||
my $call = "DFHack.vmethod_call(self, $voff$args)";
|
||||
my $ret = $meth->findnodes('child::ret-type')->[0];
|
||||
render_class_vmethod_ret($call, $ret);
|
||||
};
|
||||
push @lines_rb, 'end';
|
||||
}
|
||||
# on linux, the destructor uses 2 entries
|
||||
$voff += 4 if $meth->getAttribute('is-destructor') and $^O =~ /linux/i;
|
||||
$voff += 4;
|
||||
}
|
||||
}
|
||||
|
||||
sub render_class_vmethod_ret {
|
||||
my ($call, $ret) = @_;
|
||||
if (!$ret) {
|
||||
push @lines_rb, "$call ; nil";
|
||||
return;
|
||||
}
|
||||
my $retmeta = $ret->getAttribute('ld:meta') || '';
|
||||
if ($retmeta eq 'global') { # enum
|
||||
my $retname = $ret->getAttribute('type-name');
|
||||
if ($retname and $global_types{$retname} and
|
||||
$global_types{$retname}->getAttribute('ld:meta') eq 'enum-type') {
|
||||
push @lines_rb, rb_ucase($retname) . ".to_sym($call)";
|
||||
} else {
|
||||
print "vmethod global nonenum $call\n";
|
||||
push @lines_rb, $call;
|
||||
}
|
||||
} elsif ($retmeta eq 'number') {
|
||||
my $retsubtype = $ret->getAttribute('ld:subtype');
|
||||
my $retbits = $ret->getAttribute('ld:bits');
|
||||
push @lines_rb, "val = $call";
|
||||
if ($retsubtype eq 'bool') {
|
||||
push @lines_rb, "(val & 1) != 0";
|
||||
} elsif ($ret->getAttribute('ld:unsigned')) {
|
||||
push @lines_rb, "val & ((1 << $retbits) - 1)";
|
||||
} else { # signed
|
||||
push @lines_rb, "val &= ((1 << $retbits) - 1)";
|
||||
push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)";
|
||||
}
|
||||
} elsif ($retmeta eq 'pointer') {
|
||||
push @lines_rb, "ptr = $call";
|
||||
push @lines_rb, "class << self";
|
||||
indent_rb {
|
||||
render_item($ret->findnodes('child::ld:item')->[0]);
|
||||
};
|
||||
push @lines_rb, "end._at(ptr) if ptr != 0";
|
||||
} else {
|
||||
print "vmethod unkret $call\n";
|
||||
push @lines_rb, $call;
|
||||
}
|
||||
}
|
||||
|
||||
sub render_global_objects {
|
||||
my (@objects) = @_;
|
||||
my @global_objects;
|
||||
|
||||
my $sname = 'global_objects';
|
||||
my $rbname = rb_ucase($sname);
|
||||
|
||||
push @lines_cpp, "}" if @include_cpp;
|
||||
push @lines_cpp, "void cpp_$sname(FILE *fout) {";
|
||||
push @include_cpp, $sname;
|
||||
|
||||
push @lines_rb, "class $rbname < MemHack::Compound";
|
||||
indent_rb {
|
||||
for my $obj (@objects) {
|
||||
my $oname = $obj->getAttribute('name');
|
||||
my $addr = "DFHack.get_global_address('$oname')";
|
||||
push @lines_rb, "addr = $addr";
|
||||
push @lines_rb, "if addr != 0";
|
||||
indent_rb {
|
||||
push @lines_rb, "field(:$oname, addr) {";
|
||||
my $item = $obj->findnodes('child::ld:item')->[0];
|
||||
indent_rb {
|
||||
render_item($item, 'df::global');
|
||||
};
|
||||
push @lines_rb, "}";
|
||||
};
|
||||
push @lines_rb, "end";
|
||||
|
||||
push @global_objects, $oname;
|
||||
}
|
||||
};
|
||||
push @lines_rb, "end";
|
||||
|
||||
indent_rb {
|
||||
push @lines_rb, "Global = GlobalObjects.new._at(0)";
|
||||
for my $obj (@global_objects) {
|
||||
push @lines_rb, "def self.$obj ; Global.$obj ; end";
|
||||
push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
sub render_item {
|
||||
my ($item, $pns) = @_;
|
||||
return if (!$item);
|
||||
|
||||
my $meta = $item->getAttribute('ld:meta');
|
||||
|
||||
my $renderer = $item_renderer{$meta};
|
||||
if ($renderer) {
|
||||
$renderer->($item, $pns);
|
||||
} else {
|
||||
print "no render item $meta\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub render_item_global {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $typename = $item->getAttribute('type-name');
|
||||
my $subtype = $item->getAttribute('ld:subtype');
|
||||
|
||||
if ($subtype and $subtype eq 'enum') {
|
||||
render_item_number($item, $pns);
|
||||
} else {
|
||||
my $rbname = rb_ucase($typename);
|
||||
push @lines_rb, "global :$rbname";
|
||||
}
|
||||
}
|
||||
|
||||
sub render_item_number {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $subtype = $item->getAttribute('ld:subtype');
|
||||
my $meta = $item->getAttribute('ld:meta');
|
||||
my $initvalue = $item->getAttribute('init-value');
|
||||
my $typename = $item->getAttribute('type-name');
|
||||
undef $typename if ($meta and $meta eq 'bitfield-type');
|
||||
$typename = rb_ucase($typename) if $typename;
|
||||
$typename = $pns if (!$typename and $subtype and $subtype eq 'enum'); # compound enum
|
||||
|
||||
$initvalue = 1 if ($initvalue and $initvalue eq 'true');
|
||||
$initvalue = ":$initvalue" if ($initvalue and $typename and $initvalue =~ /[a-zA-Z]/);
|
||||
$initvalue ||= 'nil' if $typename;
|
||||
|
||||
$subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'enum' or $subtype eq 'bitfield');
|
||||
$subtype = 'int32_t' if (!$subtype);
|
||||
|
||||
if ($subtype eq 'int64_t') {
|
||||
push @lines_rb, 'number 64, true';
|
||||
} elsif ($subtype eq 'uint32_t') {
|
||||
push @lines_rb, 'number 32, false';
|
||||
} elsif ($subtype eq 'int32_t') {
|
||||
push @lines_rb, 'number 32, true';
|
||||
} elsif ($subtype eq 'uint16_t') {
|
||||
push @lines_rb, 'number 16, false';
|
||||
} elsif ($subtype eq 'int16_t') {
|
||||
push @lines_rb, 'number 16, true';
|
||||
} elsif ($subtype eq 'uint8_t') {
|
||||
push @lines_rb, 'number 8, false';
|
||||
} elsif ($subtype eq 'int8_t') {
|
||||
push @lines_rb, 'number 8, false';
|
||||
} elsif ($subtype eq 'bool') {
|
||||
push @lines_rb, 'number 8, true';
|
||||
} elsif ($subtype eq 's-float') {
|
||||
push @lines_rb, 'float';
|
||||
return;
|
||||
} else {
|
||||
print "no render number $subtype\n";
|
||||
return;
|
||||
}
|
||||
$lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue);
|
||||
$lines_rb[$#lines_rb] .= ", $typename" if ($typename);
|
||||
}
|
||||
|
||||
sub render_item_compound {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $cppns = $pns . '::' . $item->getAttribute('ld:typedef-name');
|
||||
my $subtype = $item->getAttribute('ld:subtype');
|
||||
|
||||
my @namecomponents = split('::', $cppns);
|
||||
shift @namecomponents;
|
||||
my $classname = join('_', map { rb_ucase($_) } @namecomponents);
|
||||
|
||||
if (!$subtype || $subtype eq 'bitfield') {
|
||||
push @lines_rb, "compound(:$classname) {";
|
||||
indent_rb {
|
||||
if (!$subtype) {
|
||||
render_struct_fields($item, $cppns);
|
||||
} else {
|
||||
render_bitfield_fields($item);
|
||||
}
|
||||
};
|
||||
push @lines_rb, "}"
|
||||
} elsif ($subtype eq 'enum') {
|
||||
push @lines_rb, "class ::DFHack::$classname < MemHack::Enum";
|
||||
indent_rb {
|
||||
# declare constants
|
||||
render_enum_fields($item);
|
||||
};
|
||||
push @lines_rb, "end\n";
|
||||
|
||||
# actual field
|
||||
render_item_number($item, $classname);
|
||||
} else {
|
||||
print "no render compound $subtype\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub render_item_container {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $subtype = $item->getAttribute('ld:subtype');
|
||||
my $rbmethod = join('_', split('-', $subtype));
|
||||
my $tg = $item->findnodes('child::ld:item')->[0];
|
||||
my $indexenum = $item->getAttribute('index-enum');
|
||||
if ($tg) {
|
||||
if ($rbmethod eq 'df_linked_list') {
|
||||
push @lines_rb, "$rbmethod {";
|
||||
} else {
|
||||
my $tglen = get_tglen($tg, $pns);
|
||||
push @lines_rb, "$rbmethod($tglen) {";
|
||||
}
|
||||
indent_rb {
|
||||
render_item($tg, $pns);
|
||||
};
|
||||
push @lines_rb, "}";
|
||||
} elsif ($indexenum) {
|
||||
$indexenum = rb_ucase($indexenum);
|
||||
push @lines_rb, "$rbmethod($indexenum)";
|
||||
} else {
|
||||
push @lines_rb, "$rbmethod";
|
||||
}
|
||||
}
|
||||
|
||||
sub render_item_pointer {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $tg = $item->findnodes('child::ld:item')->[0];
|
||||
my $ary = $item->getAttribute('is-array');
|
||||
if ($ary and $ary eq 'true') {
|
||||
my $tglen = get_tglen($tg, $pns);
|
||||
push @lines_rb, "pointer_ary($tglen) {";
|
||||
} else {
|
||||
push @lines_rb, "pointer {";
|
||||
}
|
||||
indent_rb {
|
||||
render_item($tg, $pns);
|
||||
};
|
||||
push @lines_rb, "}";
|
||||
}
|
||||
|
||||
sub render_item_staticarray {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $count = $item->getAttribute('count');
|
||||
my $tg = $item->findnodes('child::ld:item')->[0];
|
||||
my $tglen = get_tglen($tg, $pns);
|
||||
my $indexenum = $item->getAttribute('index-enum');
|
||||
if ($indexenum) {
|
||||
$indexenum = rb_ucase($indexenum);
|
||||
push @lines_rb, "static_array($count, $tglen, $indexenum) {";
|
||||
} else {
|
||||
push @lines_rb, "static_array($count, $tglen) {";
|
||||
}
|
||||
indent_rb {
|
||||
render_item($tg, $pns);
|
||||
};
|
||||
push @lines_rb, "}";
|
||||
}
|
||||
|
||||
sub render_item_primitive {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $subtype = $item->getAttribute('ld:subtype');
|
||||
if ($subtype eq 'stl-string') {
|
||||
push @lines_rb, "stl_string";
|
||||
} else {
|
||||
print "no render primitive $subtype\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub render_item_bytes {
|
||||
my ($item, $pns) = @_;
|
||||
|
||||
my $subtype = $item->getAttribute('ld:subtype');
|
||||
if ($subtype eq 'padding') {
|
||||
} elsif ($subtype eq 'static-string') {
|
||||
my $size = $item->getAttribute('size');
|
||||
push @lines_rb, "static_string($size)";
|
||||
} else {
|
||||
print "no render bytes $subtype\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub get_offset {
|
||||
my ($cppns, $fname) = @_;
|
||||
|
||||
return query_cpp("offsetof($cppns, $fname)");
|
||||
}
|
||||
|
||||
sub get_tglen {
|
||||
my ($tg, $cppns) = @_;
|
||||
|
||||
if (!$tg) {
|
||||
return 'nil';
|
||||
}
|
||||
|
||||
my $meta = $tg->getAttribute('ld:meta');
|
||||
if ($meta eq 'number') {
|
||||
return $tg->getAttribute('ld:bits')/8;
|
||||
} elsif ($meta eq 'pointer') {
|
||||
return 4;
|
||||
} elsif ($meta eq 'container') {
|
||||
my $subtype = $tg->getAttribute('ld:subtype');
|
||||
if ($subtype eq 'stl-vector') {
|
||||
return query_cpp("sizeof(std::vector<int>)");
|
||||
} elsif ($subtype eq 'df-linked-list') {
|
||||
return 12;
|
||||
} else {
|
||||
print "cannot tglen container $subtype\n";
|
||||
}
|
||||
} elsif ($meta eq 'compound') {
|
||||
my $cname = $tg->getAttribute('ld:typedef-name');
|
||||
return query_cpp("sizeof(${cppns}::$cname)");
|
||||
} elsif ($meta eq 'static-array') {
|
||||
my $count = $tg->getAttribute('count');
|
||||
my $ttg = $tg->findnodes('child::ld:item')->[0];
|
||||
my $ttgl = get_tglen($ttg, $cppns);
|
||||
if ($ttgl =~ /^\d+$/) {
|
||||
return $count * $ttgl;
|
||||
} else {
|
||||
return "$count*$ttgl";
|
||||
}
|
||||
} elsif ($meta eq 'global') {
|
||||
my $typename = $tg->getAttribute('type-name');
|
||||
my $subtype = $tg->getAttribute('ld:subtype');
|
||||
if ($subtype and $subtype eq 'enum') {
|
||||
my $base = $tg->getAttribute('base-type') || 'int32_t';
|
||||
if ($base eq 'int32_t') {
|
||||
return 4;
|
||||
} elsif ($base eq 'int16_t') {
|
||||
return 2;
|
||||
} elsif ($base eq 'int8_t') {
|
||||
return 1;
|
||||
} else {
|
||||
print "cannot tglen enum $base\n";
|
||||
}
|
||||
} else {
|
||||
return query_cpp("sizeof(df::$typename)");
|
||||
}
|
||||
} elsif ($meta eq 'primitive') {
|
||||
my $subtype = $tg->getAttribute('ld:subtype');
|
||||
if ($subtype eq 'stl-string') {
|
||||
return query_cpp("sizeof(std::string)");
|
||||
} else {
|
||||
print "cannot tglen primitive $subtype\n";
|
||||
}
|
||||
} else {
|
||||
print "cannot tglen $meta\n";
|
||||
}
|
||||
}
|
||||
|
||||
my %query_cpp_cache;
|
||||
sub query_cpp {
|
||||
my ($query) = @_;
|
||||
|
||||
my $ans = $offsets{$query};
|
||||
return $ans if (defined($ans));
|
||||
|
||||
my $cached = $query_cpp_cache{$query};
|
||||
return $cached if (defined($cached));
|
||||
$query_cpp_cache{$query} = 1;
|
||||
|
||||
push @lines_cpp, " fprintf(fout, \"%s = %d\\n\", \"$query\", $query);";
|
||||
return "'$query'";
|
||||
}
|
||||
|
||||
|
||||
|
||||
my $input = $ARGV[0] || '../../library/include/df/codegen.out.xml';
|
||||
|
||||
# run once with output = 'ruby-autogen.cpp'
|
||||
# compile
|
||||
# execute, save output to 'ruby-autogen.offsets'
|
||||
# re-run this script with output = 'ruby-autogen.rb' and offsetfile = 'ruby-autogen.offsets'
|
||||
# delete binary
|
||||
# delete offsets
|
||||
my $output = $ARGV[1] or die "need output file";
|
||||
my $offsetfile = $ARGV[2];
|
||||
my $memstruct = $ARGV[3];
|
||||
|
||||
if ($offsetfile) {
|
||||
open OF, "<$offsetfile";
|
||||
while (my $line = <OF>) {
|
||||
chomp($line);
|
||||
my ($key, $val) = split(' = ', $line);
|
||||
$offsets{$key} = $val;
|
||||
}
|
||||
close OF;
|
||||
}
|
||||
|
||||
|
||||
my $doc = XML::LibXML->new()->parse_file($input);
|
||||
$global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type');
|
||||
|
||||
my @nonenums;
|
||||
for my $name (sort { $a cmp $b } keys %global_types) {
|
||||
my $type = $global_types{$name};
|
||||
my $meta = $type->getAttribute('ld:meta');
|
||||
if ($meta eq 'enum-type') {
|
||||
render_global_enum($name, $type);
|
||||
} else {
|
||||
push @nonenums, $name;
|
||||
}
|
||||
}
|
||||
|
||||
for my $name (@nonenums) {
|
||||
my $type = $global_types{$name};
|
||||
my $meta = $type->getAttribute('ld:meta');
|
||||
if ($meta eq 'struct-type') {
|
||||
render_global_struct($name, $type);
|
||||
} elsif ($meta eq 'class-type') {
|
||||
render_global_class($name, $type);
|
||||
} elsif ($meta eq 'bitfield-type') {
|
||||
render_global_bitfield($name, $type);
|
||||
} else {
|
||||
print "no render global type $meta\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object'));
|
||||
|
||||
|
||||
open FH, ">$output";
|
||||
if ($output =~ /\.cpp$/) {
|
||||
print FH "#include \"DataDefs.h\"\n";
|
||||
print FH "#include \"df/$_.h\"\n" for @include_cpp;
|
||||
print FH "#include <stdio.h>\n";
|
||||
print FH "#include <stddef.h>\n";
|
||||
print FH "$_\n" for @lines_cpp;
|
||||
print FH "}\n";
|
||||
print FH "int main(int argc, char **argv) {\n";
|
||||
print FH " FILE *fout;\n";
|
||||
print FH " if (argc < 2) return 1;\n";
|
||||
print FH " fout = fopen(argv[1], \"w\");\n";
|
||||
print FH " cpp_$_(fout);\n" for @include_cpp;
|
||||
print FH " fclose(fout);\n";
|
||||
print FH " return 0;\n";
|
||||
print FH "}\n";
|
||||
|
||||
} else {
|
||||
if ($memstruct) {
|
||||
open MH, "<$memstruct";
|
||||
print FH "$_" while(<MH>);
|
||||
close MH;
|
||||
}
|
||||
print FH "module DFHack\n";
|
||||
print FH "$_\n" for @lines_rb;
|
||||
print FH "end\n";
|
||||
}
|
||||
close FH;
|
@ -0,0 +1,267 @@
|
||||
module DFHack
|
||||
|
||||
# allocate a new building object
|
||||
def self.building_alloc(type, subtype=-1, custom=-1)
|
||||
type = BuildingType.to_sym(type)
|
||||
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
|
||||
raise "invalid building type #{type.inspect}" if not cls
|
||||
bld = cls.cpp_new
|
||||
bld.race = ui.race_id
|
||||
bld.setSubtype(subtype) if subtype != -1
|
||||
bld.setCustomType(custom) if custom != -1
|
||||
case type
|
||||
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
|
||||
when :Coffin; bld.initBurialFlags
|
||||
when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate
|
||||
end
|
||||
bld
|
||||
end
|
||||
|
||||
# used by building_setsize
|
||||
def self.building_check_bridge_support(bld)
|
||||
x1 = bld.x1-1
|
||||
x2 = bld.x2+1
|
||||
y1 = bld.y1-1
|
||||
y2 = bld.y2+1
|
||||
z = bld.z
|
||||
(x1..x2).each { |x|
|
||||
(y1..y2).each { |y|
|
||||
next if ((x == x1 or x == x2) and
|
||||
(y == y1 or y == y2))
|
||||
if mb = map_block_at(x, y, z) and tile = mb.tiletype[x%16][y%16] and TiletypeShape::BasicShape[Tiletype::Shape[tile]] == :Open
|
||||
bld.gate_flags.has_support = true
|
||||
return
|
||||
end
|
||||
}
|
||||
}
|
||||
bld.gate_flags.has_support = false
|
||||
end
|
||||
|
||||
# sets x2/centerx/y2/centery from x1/y1/bldtype
|
||||
# x2/y2 preserved for :FarmPlot etc
|
||||
def self.building_setsize(bld)
|
||||
bld.x2 = bld.x1 if bld.x1 > bld.x2
|
||||
bld.y2 = bld.y1 if bld.y1 > bld.y2
|
||||
case bld.getType
|
||||
when :Bridge
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
building_check_bridge_support(bld)
|
||||
when :FarmPlot, :RoadDirt, :RoadPaved, :Stockpile, :Civzone
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
when :TradeDepot, :Shop
|
||||
bld.x2 = bld.x1+4
|
||||
bld.y2 = bld.y1+4
|
||||
bld.centerx = bld.x1+2
|
||||
bld.centery = bld.y1+2
|
||||
when :SiegeEngine, :Windmill, :Wagon
|
||||
bld.x2 = bld.x1+2
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.centery = bld.y1+1
|
||||
when :AxleHorizontal
|
||||
if bld.is_vertical == 1
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||
else
|
||||
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
when :WaterWheel
|
||||
if bld.is_vertical == 1
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centery = bld.y1+1
|
||||
else
|
||||
bld.x2 = bld.x1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
when :Workshop, :Furnace
|
||||
# Furnace = Custom or default case only
|
||||
case bld.type
|
||||
when :Quern, :Millstone, :Tool
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :Siege, :Kennels
|
||||
bld.x2 = bld.x1+4
|
||||
bld.y2 = bld.y1+4
|
||||
bld.centerx = bld.x1+2
|
||||
bld.centery = bld.y1+2
|
||||
when :Custom
|
||||
if bdef = world.raws.buildings.all.binsearch(bld.getCustomType)
|
||||
bld.x2 = bld.x1 + bdef.dim_x - 1
|
||||
bld.y2 = bld.y1 + bdef.dim_y - 1
|
||||
bld.centerx = bld.x1 + bdef.workloc_x
|
||||
bld.centery = bld.y1 + bdef.workloc_y
|
||||
end
|
||||
else
|
||||
bld.x2 = bld.x1+2
|
||||
bld.y2 = bld.y1+2
|
||||
bld.centerx = bld.x1+1
|
||||
bld.centery = bld.y1+1
|
||||
end
|
||||
when :ScrewPump
|
||||
case bld.direction
|
||||
when :FromEast
|
||||
bld.x2 = bld.centerx = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :FromSouth
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1+1
|
||||
when :FromWest
|
||||
bld.x2 = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.centerx = bld.x1
|
||||
else
|
||||
bld.x2 = bld.x1+1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.centerx = bld.x1
|
||||
end
|
||||
when :Well
|
||||
bld.bucket_z = bld.z
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
when :Construction
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
bld.setMaterialAmount(1)
|
||||
return
|
||||
else
|
||||
bld.x2 = bld.centerx = bld.x1
|
||||
bld.y2 = bld.centery = bld.y1
|
||||
end
|
||||
bld.setMaterialAmount((bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1)
|
||||
end
|
||||
|
||||
# set building at position, with optional width/height
|
||||
def self.building_position(bld, pos, w=nil, h=nil)
|
||||
bld.x1 = pos.x
|
||||
bld.y1 = pos.y
|
||||
bld.z = pos.z
|
||||
bld.x2 = bld.x1+w-1 if w
|
||||
bld.y2 = bld.y1+h-1 if h
|
||||
building_setsize(bld)
|
||||
end
|
||||
|
||||
# set map occupancy/stockpile/etc for a building
|
||||
def self.building_setoccupancy(bld)
|
||||
stockpile = (bld.getType == :Stockpile)
|
||||
complete = (bld.getBuildStage >= bld.getMaxBuildStage)
|
||||
extents = (bld.room.extents and bld.isExtentShaped)
|
||||
|
||||
z = bld.z
|
||||
(bld.x1..bld.x2).each { |x|
|
||||
(bld.y1..bld.y2).each { |y|
|
||||
next if !extents or bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0
|
||||
next if not mb = map_block_at(x, y, z)
|
||||
des = mb.designation[x%16][y%16]
|
||||
des.pile = stockpile
|
||||
des.dig = :No
|
||||
if complete
|
||||
bld.updateOccupancy(x, y)
|
||||
else
|
||||
mb.occupancy[x%16][y%16].building = :Planned
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# link bld into other rooms if it is inside their extents
|
||||
def self.building_linkrooms(bld)
|
||||
didstuff = false
|
||||
world.buildings.other[:ANY_FREE].each { |ob|
|
||||
next if !ob.is_room or ob.z != bld.z
|
||||
next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0
|
||||
didstuff = true
|
||||
ob.children << bld
|
||||
bld.parents << ob
|
||||
}
|
||||
ui.equipment.update.buildings = true if didstuff
|
||||
end
|
||||
|
||||
# link the building into the world, set map data, link rooms, bld.id
|
||||
def self.building_link(bld)
|
||||
bld.id = df.building_next_id
|
||||
df.building_next_id += 1
|
||||
|
||||
world.buildings.all << bld
|
||||
bld.categorize(true)
|
||||
building_setoccupancy(bld) if bld.isSettingOccupancy
|
||||
building_linkrooms(bld)
|
||||
end
|
||||
|
||||
# set a design for the building
|
||||
def self.building_createdesign(bld, rough=true)
|
||||
job = bld.jobs[0]
|
||||
job.mat_type = bld.mat_type
|
||||
job.mat_index = bld.mat_index
|
||||
if bld.needsDesign
|
||||
bld.design = BuildingDesign.cpp_new
|
||||
bld.design.flags.rough = rough
|
||||
end
|
||||
end
|
||||
|
||||
# creates a job to build bld, return it
|
||||
def self.building_linkforconstruct(bld)
|
||||
building_link bld
|
||||
ref = GeneralRefBuildingHolderst.cpp_new
|
||||
ref.building_id = bld.id
|
||||
job = Job.cpp_new
|
||||
job.job_type = :ConstructBuilding
|
||||
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||
job.references << ref
|
||||
bld.jobs << job
|
||||
job_link job
|
||||
job
|
||||
end
|
||||
|
||||
# construct a building with items or JobItems
|
||||
def self.building_construct(bld, items)
|
||||
job = building_linkforconstruct(bld)
|
||||
rough = false
|
||||
items.each { |item|
|
||||
if items.kind_of?(JobItem)
|
||||
item.quantity = (bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1 if item.quantity < 0
|
||||
job.job_items << item
|
||||
else
|
||||
job_attachitem(job, item, :Hauled)
|
||||
end
|
||||
rough = true if item.getType == :BOULDER
|
||||
bld.mat_type = item.getMaterial if bld.mat_type == -1
|
||||
bld.mat_index = item.getMaterialIndex if bld.mat_index == -1
|
||||
}
|
||||
building_createdesign(bld, rough)
|
||||
end
|
||||
|
||||
# creates a job to deconstruct the building
|
||||
def self.building_deconstruct(bld)
|
||||
job = Job.cpp_new
|
||||
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
|
||||
job.job_type = :DestroyBuilding
|
||||
refbuildingholder.building_id = building.id
|
||||
job.references << refbuildingholder
|
||||
building.jobs << job
|
||||
job_link job
|
||||
job
|
||||
end
|
||||
|
||||
# exemple usage
|
||||
def self.buildbed(pos=cursor)
|
||||
suspend {
|
||||
raise 'where to ?' if pos.x < 0
|
||||
|
||||
item = world.items.all.find { |i|
|
||||
i.kind_of?(ItemBedst) and
|
||||
i.itemrefs.empty? and
|
||||
!i.flags.in_job
|
||||
}
|
||||
raise 'no free bed, build more !' if not item
|
||||
|
||||
bld = building_alloc(:Bed)
|
||||
building_position(bld, pos)
|
||||
building_construct(bld, [item])
|
||||
}
|
||||
end
|
||||
end
|
@ -0,0 +1,152 @@
|
||||
module DFHack
|
||||
def self.each_tree(material=:any)
|
||||
@raws_tree_name ||= {}
|
||||
if @raws_tree_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_tree_name[idx] = p.id if p.flags[:TREE]
|
||||
}
|
||||
end
|
||||
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_tree_name.values)
|
||||
unless wantmat = @raws_tree_name.index(mat)
|
||||
raise "invalid tree material #{material}"
|
||||
end
|
||||
end
|
||||
|
||||
world.plants.all.each { |plant|
|
||||
next if not @raws_tree_name[plant.material]
|
||||
next if wantmat and plant.material != wantmat
|
||||
yield plant
|
||||
}
|
||||
end
|
||||
|
||||
def self.each_shrub(material=:any)
|
||||
@raws_shrub_name ||= {}
|
||||
if @raws_tree_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_shrub_name[idx] = p.id if not p.flags[:GRASS] and not p.flags[:TREE]
|
||||
}
|
||||
end
|
||||
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_shrub_name.values)
|
||||
unless wantmat = @raws_shrub_name.index(mat)
|
||||
raise "invalid shrub material #{material}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SaplingToTreeAge = 120960
|
||||
def self.cuttrees(material=nil, count_max=100)
|
||||
if !material
|
||||
# list trees
|
||||
cnt = Hash.new(0)
|
||||
suspend {
|
||||
each_tree { |plant|
|
||||
next if plant.grow_counter < SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
cnt[plant.material] += 1
|
||||
}
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = @raws_tree_name[mat]
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
cnt = 0
|
||||
suspend {
|
||||
each_tree(material) { |plant|
|
||||
next if plant.grow_counter < SaplingToTreeAge
|
||||
b = map_block_at(plant)
|
||||
d = b.designation[plant.pos.x%16][plant.pos.y%16]
|
||||
next if d.hidden
|
||||
if d.dig == :No
|
||||
d.dig = :Default
|
||||
b.flags.designated = true
|
||||
cnt += 1
|
||||
break if cnt == count_max
|
||||
end
|
||||
}
|
||||
}
|
||||
puts "Updated #{cnt} plant designations"
|
||||
end
|
||||
end
|
||||
|
||||
def self.growtrees(material=nil, count_max=100)
|
||||
if !material
|
||||
# list plants
|
||||
cnt = Hash.new(0)
|
||||
suspend {
|
||||
each_tree { |plant|
|
||||
next if plant.grow_counter >= SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
cnt[plant.material] += 1
|
||||
}
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = @raws_tree_name[mat]
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
cnt = 0
|
||||
suspend {
|
||||
each_tree(material) { |plant|
|
||||
next if plant.grow_counter >= SaplingToTreeAge
|
||||
next if map_designation_at(plant).hidden
|
||||
plant.grow_counter = SaplingToTreeAge
|
||||
cnt += 1
|
||||
break if cnt == count_max
|
||||
}
|
||||
}
|
||||
puts "Grown #{cnt} saplings"
|
||||
end
|
||||
end
|
||||
|
||||
def self.growcrops(material=nil, count_max=100)
|
||||
@raws_plant_name ||= {}
|
||||
@raws_plant_growdur ||= {}
|
||||
if @raws_plant_name.empty?
|
||||
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||
@raws_plant_name[idx] = p.id
|
||||
@raws_plant_growdur[idx] = p.growdur
|
||||
}
|
||||
end
|
||||
|
||||
if !material
|
||||
cnt = Hash.new(0)
|
||||
suspend {
|
||||
world.items.other[:SEEDS].each { |seed|
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
cnt[seed.mat_index] += 1
|
||||
}
|
||||
}
|
||||
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||
name = world.raws.plants.all[mat].id
|
||||
puts " #{name} #{c}"
|
||||
}
|
||||
else
|
||||
if material != :any
|
||||
mat = match_rawname(material, @raws_plant_name.values)
|
||||
unless wantmat = @raws_plant_name.index(mat)
|
||||
raise "invalid plant material #{material}"
|
||||
end
|
||||
end
|
||||
|
||||
cnt = 0
|
||||
suspend {
|
||||
world.items.other[:SEEDS].each { |seed|
|
||||
next if wantmat and seed.mat_index != wantmat
|
||||
next if not seed.flags.in_building
|
||||
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||
cnt += 1
|
||||
}
|
||||
}
|
||||
puts "Grown #{cnt} crops"
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,52 @@
|
||||
module DFHack
|
||||
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
|
||||
def self.unit_citizens
|
||||
race = ui.race_id
|
||||
civ = ui.civ_id
|
||||
world.units.active.find_all { |u|
|
||||
u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
|
||||
!u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
|
||||
!u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
|
||||
u.mood != :Berserk
|
||||
# TODO check curse ; currently this should keep vampires, but may include werebeasts
|
||||
}
|
||||
end
|
||||
|
||||
# list workers (citizen, not crazy / child / inmood / noble)
|
||||
def self.unit_workers
|
||||
unit_citizens.find_all { |u|
|
||||
u.mood == :None and
|
||||
u.profession != :CHILD and
|
||||
u.profession != :BABY and
|
||||
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
|
||||
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
|
||||
}
|
||||
end
|
||||
|
||||
# list currently idle workers
|
||||
def self.unit_idlers
|
||||
unit_workers.find_all { |u|
|
||||
# current_job includes eat/drink/sleep/pickupequip
|
||||
!u.job.current_job._getv and
|
||||
# filter 'attend meeting'
|
||||
u.meetings.length == 0 and
|
||||
# filter soldiers (TODO check schedule)
|
||||
u.military.squad_index == -1 and
|
||||
# filter 'on break'
|
||||
!u.status.misc_traits.find { |t| id == :OnBreak }
|
||||
}
|
||||
end
|
||||
|
||||
def self.unit_entitypositions(unit)
|
||||
list = []
|
||||
return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
|
||||
hf.entity_links.each { |el|
|
||||
next if el._rtti_classname != :histfig_entity_link_positionst
|
||||
next if not ent = world.entities.all.binsearch(el.entity_id)
|
||||
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
|
||||
next if not pos = ent.positions.own.binsearch(pa.position_id)
|
||||
list << pos
|
||||
}
|
||||
list
|
||||
end
|
||||
end
|
@ -0,0 +1,747 @@
|
||||
module DFHack
|
||||
module MemHack
|
||||
INSPECT_SIZE_LIMIT=16384
|
||||
class MemStruct
|
||||
attr_accessor :_memaddr
|
||||
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
|
||||
def _get ; self ; end
|
||||
def _cpp_init ; end
|
||||
end
|
||||
|
||||
class Compound < MemStruct
|
||||
class << self
|
||||
attr_accessor :_fields, :_rtti_classname, :_sizeof
|
||||
def field(name, offset)
|
||||
struct = yield
|
||||
return if not struct
|
||||
@_fields ||= []
|
||||
@_fields << [name, offset, struct]
|
||||
define_method(name) { struct._at(@_memaddr+offset)._get }
|
||||
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
|
||||
end
|
||||
def _fields_ancestors
|
||||
if superclass.respond_to?(:_fields_ancestors)
|
||||
superclass._fields_ancestors + _fields.to_a
|
||||
else
|
||||
_fields.to_a
|
||||
end
|
||||
end
|
||||
|
||||
def number(bits, signed, initvalue=nil, enum=nil)
|
||||
Number.new(bits, signed, initvalue, enum)
|
||||
end
|
||||
def float
|
||||
Float.new
|
||||
end
|
||||
def bit(shift)
|
||||
BitField.new(shift, 1)
|
||||
end
|
||||
def bits(shift, len, enum=nil)
|
||||
BitField.new(shift, len, enum)
|
||||
end
|
||||
def pointer
|
||||
Pointer.new((yield if block_given?))
|
||||
end
|
||||
def pointer_ary(tglen)
|
||||
PointerAry.new(tglen, yield)
|
||||
end
|
||||
def static_array(len, tglen, indexenum=nil)
|
||||
StaticArray.new(tglen, len, indexenum, yield)
|
||||
end
|
||||
def static_string(len)
|
||||
StaticString.new(len)
|
||||
end
|
||||
|
||||
def stl_vector(tglen=nil)
|
||||
tg = yield if tglen
|
||||
case tglen
|
||||
when 1; StlVector8.new(tg)
|
||||
when 2; StlVector16.new(tg)
|
||||
else StlVector32.new(tg)
|
||||
end
|
||||
end
|
||||
def stl_string
|
||||
StlString.new
|
||||
end
|
||||
def stl_bit_vector
|
||||
StlBitVector.new
|
||||
end
|
||||
def stl_deque(tglen)
|
||||
StlDeque.new(tglen, yield)
|
||||
end
|
||||
|
||||
def df_flagarray(indexenum=nil)
|
||||
DfFlagarray.new(indexenum)
|
||||
end
|
||||
def df_array(tglen)
|
||||
DfArray.new(tglen, yield)
|
||||
end
|
||||
def df_linked_list
|
||||
DfLinkedList.new(yield)
|
||||
end
|
||||
|
||||
def global(glob)
|
||||
Global.new(glob)
|
||||
end
|
||||
def compound(name=nil, &b)
|
||||
m = Class.new(Compound)
|
||||
DFHack.const_set(name, m) if name
|
||||
m.instance_eval(&b)
|
||||
m.new
|
||||
end
|
||||
def rtti_classname(n)
|
||||
DFHack.rtti_register(n, self)
|
||||
@_rtti_classname = n
|
||||
end
|
||||
def sizeof(n)
|
||||
@_sizeof = n
|
||||
end
|
||||
|
||||
# allocate a new c++ object, return its ruby wrapper
|
||||
def cpp_new
|
||||
ptr = DFHack.malloc(_sizeof)
|
||||
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
|
||||
DFHack.memory_write_int32(ptr, vt)
|
||||
# TODO call constructor
|
||||
end
|
||||
o = new._at(ptr)
|
||||
o._cpp_init
|
||||
o
|
||||
end
|
||||
end
|
||||
def _cpp_init
|
||||
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
|
||||
end
|
||||
def _set(h)
|
||||
case h
|
||||
when Hash; h.each { |k, v| send("_#{k}=", v) }
|
||||
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
|
||||
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
|
||||
else raise 'wut?'
|
||||
end
|
||||
end
|
||||
def _fields ; self.class._fields.to_a ; end
|
||||
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
|
||||
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
|
||||
def _rtti_classname ; self.class._rtti_classname ; end
|
||||
def _sizeof ; self.class._sizeof ; end
|
||||
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
|
||||
def inspect
|
||||
cn = self.class.name.sub(/^DFHack::/, '')
|
||||
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
|
||||
out = "#<#{cn}"
|
||||
return out << ' ...>' if @@inspecting[_memaddr]
|
||||
@@inspecting[_memaddr] = true
|
||||
_fields_ancestors.each { |n, o, s|
|
||||
out << ' ' if out.length != 0 and out[-1, 1] != ' '
|
||||
if out.length > INSPECT_SIZE_LIMIT
|
||||
out << '...'
|
||||
break
|
||||
end
|
||||
out << inspect_field(n, o, s)
|
||||
}
|
||||
out.chomp!(' ')
|
||||
@@inspecting.delete _memaddr
|
||||
out << '>'
|
||||
end
|
||||
def inspect_field(n, o, s)
|
||||
if s.kind_of?(BitField) and s._len == 1
|
||||
send(n) ? n.to_s : ''
|
||||
elsif s.kind_of?(Pointer)
|
||||
"#{n}=#{s._at(@_memaddr+o).inspect}"
|
||||
elsif n == :_whole
|
||||
"_whole=0x#{_whole.to_s(16)}"
|
||||
else
|
||||
v = send(n).inspect
|
||||
"#{n}=#{v}"
|
||||
end
|
||||
rescue
|
||||
"#{n}=ERR(#{$!})"
|
||||
end
|
||||
end
|
||||
|
||||
class Enum
|
||||
# number -> symbol
|
||||
def self.enum
|
||||
# ruby weirdness, needed to make the constants 'virtual'
|
||||
@enum ||= const_get(:ENUM)
|
||||
end
|
||||
# symbol -> number
|
||||
def self.nume
|
||||
@nume ||= const_get(:NUME)
|
||||
end
|
||||
|
||||
def self.to_i(i)
|
||||
nume[i] || i
|
||||
end
|
||||
def self.to_sym(i)
|
||||
enum[i] || i
|
||||
end
|
||||
end
|
||||
|
||||
class Number < MemStruct
|
||||
attr_accessor :_bits, :_signed, :_initvalue, :_enum
|
||||
def initialize(bits, signed, initvalue, enum)
|
||||
@_bits = bits
|
||||
@_signed = signed
|
||||
@_initvalue = initvalue
|
||||
@_enum = enum
|
||||
end
|
||||
|
||||
def _get
|
||||
v = case @_bits
|
||||
when 32; DFHack.memory_read_int32(@_memaddr)
|
||||
when 16; DFHack.memory_read_int16(@_memaddr)
|
||||
when 8; DFHack.memory_read_int8( @_memaddr)
|
||||
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
|
||||
end
|
||||
v &= (1 << @_bits) - 1 if not @_signed
|
||||
v = @_enum.to_sym(v) if @_enum
|
||||
v
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
v = @_enum.to_i(v) if @_enum
|
||||
case @_bits
|
||||
when 32; DFHack.memory_write_int32(@_memaddr, v)
|
||||
when 16; DFHack.memory_write_int16(@_memaddr, v)
|
||||
when 8; DFHack.memory_write_int8( @_memaddr, v)
|
||||
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
|
||||
end
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
_set(@_initvalue) if @_initvalue
|
||||
end
|
||||
end
|
||||
class Float < MemStruct
|
||||
def _get
|
||||
DFHack.memory_read_float(@_memaddr)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
DFHack.memory_write_float(@_memaddr, v)
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
_set(0.0)
|
||||
end
|
||||
end
|
||||
class BitField < MemStruct
|
||||
attr_accessor :_shift, :_len, :_enum
|
||||
def initialize(shift, len, enum=nil)
|
||||
@_shift = shift
|
||||
@_len = len
|
||||
@_enum = enum
|
||||
end
|
||||
def _mask
|
||||
(1 << @_len) - 1
|
||||
end
|
||||
|
||||
def _get
|
||||
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
|
||||
if @_len == 1
|
||||
((v & 1) == 0) ? false : true
|
||||
else
|
||||
v &= _mask
|
||||
v = @_enum.to_sym(v) if @_enum
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
if @_len == 1
|
||||
# allow 'bit = 0'
|
||||
v = (v && v != 0 ? 1 : 0)
|
||||
end
|
||||
v = @_enum.to_i(v) if @_enum
|
||||
v = (v & _mask) << @_shift
|
||||
|
||||
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
|
||||
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
|
||||
end
|
||||
end
|
||||
|
||||
class Pointer < MemStruct
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def _getp
|
||||
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
|
||||
end
|
||||
|
||||
def _get
|
||||
addr = _getp
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
# XXX shaky...
|
||||
def _set(v)
|
||||
if v.kind_of?(Pointer)
|
||||
DFHack.memory_write_int32(@_memaddr, v._getp)
|
||||
elsif v.kind_of?(MemStruct)
|
||||
DFHack.memory_write_int32(@_memaddr, v._memaddr)
|
||||
else
|
||||
_get._set(v)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
ptr = _getp
|
||||
if ptr == 0
|
||||
'NULL'
|
||||
else
|
||||
cn = ''
|
||||
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
|
||||
cn = @_tg._glob if cn == 'Global'
|
||||
"#<Pointer #{cn} #{'0x%X' % _getp}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
class PointerAry < MemStruct
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def _getp(i=0)
|
||||
delta = (i != 0 ? i*@_tglen : 0)
|
||||
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
|
||||
end
|
||||
|
||||
def _get
|
||||
addr = _getp
|
||||
return if addr == 0
|
||||
self
|
||||
end
|
||||
|
||||
def [](i)
|
||||
addr = _getp(i)
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
addr = _getp(i)
|
||||
raise 'null pointer' if addr == 0
|
||||
@_tg._at(addr)._set(v)
|
||||
end
|
||||
|
||||
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
|
||||
end
|
||||
module Enumerable
|
||||
include ::Enumerable
|
||||
attr_accessor :_indexenum
|
||||
def each ; (0...length).each { |i| yield self[i] } ; end
|
||||
def inspect
|
||||
out = '['
|
||||
each_with_index { |e, idx|
|
||||
out << ', ' if out.length > 1
|
||||
if out.length > INSPECT_SIZE_LIMIT
|
||||
out << '...'
|
||||
break
|
||||
end
|
||||
out << "#{_indexenum.to_sym(idx)}=" if _indexenum
|
||||
out << e.inspect
|
||||
}
|
||||
out << ']'
|
||||
end
|
||||
def empty? ; length == 0 ; end
|
||||
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
|
||||
end
|
||||
class StaticArray < MemStruct
|
||||
attr_accessor :_tglen, :_length, :_indexenum, :_tg
|
||||
def initialize(tglen, length, indexenum, tg)
|
||||
@_tglen = tglen
|
||||
@_length = length
|
||||
@_indexenum = indexenum
|
||||
@_tg = tg
|
||||
end
|
||||
def _set(a)
|
||||
a.each_with_index { |v, i| self[i] = v }
|
||||
end
|
||||
def _cpp_init
|
||||
_length.times { |i| _tgat(i)._cpp_init }
|
||||
end
|
||||
alias length _length
|
||||
alias size _length
|
||||
def _tgat(i)
|
||||
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
|
||||
end
|
||||
def [](i)
|
||||
i = _indexenum.to_i(i) if _indexenum
|
||||
i += @_length if i < 0
|
||||
_tgat(i)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
i = _indexenum.to_i(i) if _indexenum
|
||||
i += @_length if i < 0
|
||||
_tgat(i)._set(v)
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class StaticString < MemStruct
|
||||
attr_accessor :_length
|
||||
def initialize(length)
|
||||
@_length = length
|
||||
end
|
||||
def _get
|
||||
DFHack.memory_read(@_memaddr, @_length)
|
||||
end
|
||||
def _set(v)
|
||||
DFHack.memory_write(@_memaddr, v[0, @_length])
|
||||
end
|
||||
end
|
||||
|
||||
class StlVector32 < MemStruct
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def length
|
||||
DFHack.memory_vector32_length(@_memaddr)
|
||||
end
|
||||
def size ; length ; end # alias wouldnt work for subclasses
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector32_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector32_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector32_delete(@_memaddr, idx)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
delete_at(length-1) while length > v.length # match lengthes
|
||||
v.each_with_index { |e, i| self[i] = e } # patch entries
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
DFHack.memory_vector_init(@_memaddr)
|
||||
end
|
||||
|
||||
def clear
|
||||
delete_at(length-1) while length > 0
|
||||
end
|
||||
def [](idx)
|
||||
idx += length if idx < 0
|
||||
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx += length if idx < 0
|
||||
if idx >= length
|
||||
insert_at(idx, 0)
|
||||
elsif idx < 0
|
||||
raise 'invalid idx'
|
||||
end
|
||||
@_tg._at(valueptr_at(idx))._set(v)
|
||||
end
|
||||
def push(v)
|
||||
self[length] = v
|
||||
self
|
||||
end
|
||||
def <<(v) ; push(v) ; end
|
||||
def pop
|
||||
l = length
|
||||
if l > 0
|
||||
v = self[l-1]
|
||||
delete_at(l-1)
|
||||
end
|
||||
v
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
# do a binary search in an ordered vector for a specific target attribute
|
||||
# ex: world.history.figures.binsearch(unit.hist_figure_id)
|
||||
def binsearch(target, field=:id)
|
||||
o_start = 0
|
||||
o_end = length - 1
|
||||
while o_end >= o_start
|
||||
o_half = o_start + (o_end-o_start)/2
|
||||
obj = self[o_half]
|
||||
oval = obj.send(field)
|
||||
if oval == target
|
||||
return obj
|
||||
elsif oval < target
|
||||
o_start = o_half+1
|
||||
else
|
||||
o_end = o_half-1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
class StlVector16 < StlVector32
|
||||
def length
|
||||
DFHack.memory_vector16_length(@_memaddr)
|
||||
end
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector16_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector16_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector16_delete(@_memaddr, idx)
|
||||
end
|
||||
end
|
||||
class StlVector8 < StlVector32
|
||||
def length
|
||||
DFHack.memory_vector8_length(@_memaddr)
|
||||
end
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector8_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector8_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector8_delete(@_memaddr, idx)
|
||||
end
|
||||
end
|
||||
class StlBitVector < StlVector32
|
||||
def initialize ; end
|
||||
def length
|
||||
DFHack.memory_vectorbool_length(@_memaddr)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vectorbool_delete(@_memaddr, idx)
|
||||
end
|
||||
def [](idx)
|
||||
idx += length if idx < 0
|
||||
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx += length if idx < 0
|
||||
if idx >= length
|
||||
insert_at(idx, v)
|
||||
elsif idx < 0
|
||||
raise 'invalid idx'
|
||||
else
|
||||
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
class StlString < MemStruct
|
||||
def _get
|
||||
DFHack.memory_read_stlstring(@_memaddr)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
DFHack.memory_write_stlstring(@_memaddr, v)
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
DFHack.memory_stlstring_init(@_memaddr)
|
||||
end
|
||||
end
|
||||
class StlDeque < MemStruct
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_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.
|
||||
def inspect ; "#<StlDeque>" ; end
|
||||
end
|
||||
|
||||
class DfFlagarray < MemStruct
|
||||
attr_accessor :_indexenum
|
||||
def initialize(indexenum)
|
||||
@_indexenum = indexenum
|
||||
end
|
||||
def length
|
||||
DFHack.memory_bitarray_length(@_memaddr)
|
||||
end
|
||||
# TODO _cpp_init
|
||||
def size ; length ; end
|
||||
def resize(len)
|
||||
DFHack.memory_bitarray_resize(@_memaddr, len)
|
||||
end
|
||||
def [](idx)
|
||||
idx = _indexenum.to_i(idx) if _indexenum
|
||||
idx += length if idx < 0
|
||||
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx = _indexenum.to_i(idx) if _indexenum
|
||||
idx += length if idx < 0
|
||||
if idx >= length or idx < 0
|
||||
raise 'invalid idx'
|
||||
else
|
||||
DFHack.memory_bitarray_set(@_memaddr, idx, v)
|
||||
end
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class DfArray < Compound
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
field(:_ptr, 0) { number 32, false }
|
||||
field(:_length, 4) { number 16, false }
|
||||
|
||||
def length ; _length ; end
|
||||
def size ; _length ; end
|
||||
# TODO _cpp_init
|
||||
def _tgat(i)
|
||||
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
|
||||
end
|
||||
def [](i)
|
||||
i += _length if i < 0
|
||||
_tgat(i)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
i += _length if i < 0
|
||||
_tgat(i)._set(v)
|
||||
end
|
||||
def _set(a)
|
||||
a.each_with_index { |v, i| self[i] = v }
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class DfLinkedList < Compound
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
field(:_ptr, 0) { number 32, false }
|
||||
field(:_prev, 4) { number 32, false }
|
||||
field(:_next, 8) { number 32, false }
|
||||
|
||||
def item
|
||||
# With the current xml structure, currently _tg designate
|
||||
# the type of the 'next' and 'prev' fields, not 'item'.
|
||||
# List head has item == NULL, so we can safely return nil.
|
||||
|
||||
#addr = _ptr
|
||||
#return if addr == 0
|
||||
#@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
def item=(v)
|
||||
#addr = _ptr
|
||||
#raise 'null pointer' if addr == 0
|
||||
#@_tg.at(addr)._set(v)
|
||||
raise 'null pointer'
|
||||
end
|
||||
|
||||
def prev
|
||||
addr = _prev
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
def next
|
||||
addr = _next
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
def each
|
||||
o = self
|
||||
while o
|
||||
yield o.item if o.item
|
||||
o = o.next
|
||||
end
|
||||
end
|
||||
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
|
||||
end
|
||||
|
||||
class Global < MemStruct
|
||||
attr_accessor :_glob
|
||||
def initialize(glob)
|
||||
@_glob = glob
|
||||
end
|
||||
def _at(addr)
|
||||
g = DFHack.const_get(@_glob)
|
||||
g = DFHack.rtti_getclassat(g, addr)
|
||||
g.new._at(addr)
|
||||
end
|
||||
def inspect ; "#<#{@_glob}>" ; end
|
||||
end
|
||||
end # module MemHack
|
||||
|
||||
|
||||
# cpp rtti name -> rb class
|
||||
@rtti_n2c = {}
|
||||
@rtti_c2n = {}
|
||||
|
||||
# cpp rtti name -> vtable ptr
|
||||
@rtti_n2v = {}
|
||||
@rtti_v2n = {}
|
||||
|
||||
def self.rtti_n2c ; @rtti_n2c ; end
|
||||
def self.rtti_c2n ; @rtti_c2n ; end
|
||||
def self.rtti_n2v ; @rtti_n2v ; end
|
||||
def self.rtti_v2n ; @rtti_v2n ; end
|
||||
|
||||
# register a ruby class with a cpp rtti class name
|
||||
def self.rtti_register(cppname, cls)
|
||||
@rtti_n2c[cppname] = cls
|
||||
@rtti_c2n[cls] = cppname
|
||||
end
|
||||
|
||||
# return the ruby class to use for the cpp object at address if rtti info is available
|
||||
def self.rtti_getclassat(cls, addr)
|
||||
if addr != 0 and @rtti_c2n[cls]
|
||||
# rtti info exist for class => cpp object has a vtable
|
||||
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
|
||||
else
|
||||
cls
|
||||
end
|
||||
end
|
||||
|
||||
# try to read the rtti classname from an object vtable pointer
|
||||
def self.rtti_readclassname(vptr)
|
||||
unless n = @rtti_v2n[vptr]
|
||||
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
|
||||
@rtti_n2v[n] = vptr
|
||||
end
|
||||
n
|
||||
end
|
||||
|
||||
# return the vtable pointer from the cpp rtti name
|
||||
def self.rtti_getvtable(cppname)
|
||||
unless v = @rtti_n2v[cppname]
|
||||
v = get_vtable(cppname.to_s)
|
||||
@rtti_n2v[cppname] = v
|
||||
@rtti_v2n[v] = cppname if v != 0
|
||||
end
|
||||
v if v != 0
|
||||
end
|
||||
|
||||
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
|
||||
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
|
||||
end
|
||||
|
||||
def self.vmethod_arg(arg)
|
||||
case arg
|
||||
when nil, false; 0
|
||||
when true; 1
|
||||
when Integer; arg
|
||||
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
|
||||
when MemHack::Compound; arg._memaddr
|
||||
else raise "bad vmethod arg #{arg.class}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,713 @@
|
||||
// blindly copied imports from fastdwarf
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
#include "VersionInfo.h"
|
||||
|
||||
#include "DataDefs.h"
|
||||
#include "df/world.h"
|
||||
#include "df/unit.h"
|
||||
|
||||
#include "tinythread.h"
|
||||
|
||||
#include <ruby.h>
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
|
||||
|
||||
// DFHack stuff
|
||||
|
||||
|
||||
static void df_rubythread(void*);
|
||||
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters);
|
||||
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters);
|
||||
static void ruby_bind_dfhack(void);
|
||||
|
||||
// inter-thread communication stuff
|
||||
enum RB_command {
|
||||
RB_IDLE,
|
||||
RB_INIT,
|
||||
RB_DIE,
|
||||
RB_EVAL,
|
||||
RB_CUSTOM,
|
||||
};
|
||||
tthread::mutex *m_irun;
|
||||
tthread::mutex *m_mutex;
|
||||
static RB_command r_type;
|
||||
static const char *r_command;
|
||||
static command_result r_result;
|
||||
static tthread::thread *r_thread;
|
||||
static int onupdate_active;
|
||||
|
||||
DFHACK_PLUGIN("ruby")
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
m_irun = new tthread::mutex();
|
||||
m_mutex = new tthread::mutex();
|
||||
r_type = RB_INIT;
|
||||
|
||||
r_thread = new tthread::thread(df_rubythread, 0);
|
||||
|
||||
while (r_type != RB_IDLE)
|
||||
tthread::this_thread::yield();
|
||||
|
||||
m_irun->lock();
|
||||
|
||||
if (r_result == CR_FAILURE)
|
||||
return CR_FAILURE;
|
||||
|
||||
onupdate_active = 0;
|
||||
|
||||
commands.push_back(PluginCommand("rb_load",
|
||||
"Ruby interpreter. Loads the given ruby script.",
|
||||
df_rubyload));
|
||||
|
||||
commands.push_back(PluginCommand("rb_eval",
|
||||
"Ruby interpreter. Eval() a ruby string.",
|
||||
df_rubyeval));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
m_mutex->lock();
|
||||
if (!r_thread)
|
||||
return CR_OK;
|
||||
|
||||
r_type = RB_DIE;
|
||||
r_command = 0;
|
||||
m_irun->unlock();
|
||||
|
||||
r_thread->join();
|
||||
|
||||
delete r_thread;
|
||||
r_thread = 0;
|
||||
delete m_irun;
|
||||
m_mutex->unlock();
|
||||
delete m_mutex;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// send a single ruby line to be evaluated by the ruby thread
|
||||
static command_result plugin_eval_rb(const char *command)
|
||||
{
|
||||
command_result ret;
|
||||
|
||||
// serialize 'accesses' to the ruby thread
|
||||
m_mutex->lock();
|
||||
if (!r_thread)
|
||||
// raced with plugin_shutdown ?
|
||||
return CR_OK;
|
||||
|
||||
r_type = RB_EVAL;
|
||||
r_command = command;
|
||||
m_irun->unlock();
|
||||
|
||||
// could use a condition_variable or something...
|
||||
while (r_type != RB_IDLE)
|
||||
tthread::this_thread::yield();
|
||||
|
||||
// XXX non-atomic with previous r_type change check
|
||||
ret = r_result;
|
||||
|
||||
m_irun->lock();
|
||||
m_mutex->unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static command_result plugin_eval_rb(std::string &command)
|
||||
{
|
||||
return plugin_eval_rb(command.c_str());
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
if (!onupdate_active)
|
||||
return CR_OK;
|
||||
|
||||
return plugin_eval_rb("DFHack.onupdate");
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e)
|
||||
{
|
||||
std::string cmd = "DFHack.onstatechange ";
|
||||
switch (e) {
|
||||
#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break
|
||||
SCASE(WORLD_LOADED);
|
||||
SCASE(WORLD_UNLOADED);
|
||||
SCASE(MAP_LOADED);
|
||||
SCASE(MAP_UNLOADED);
|
||||
SCASE(VIEWSCREEN_CHANGED);
|
||||
SCASE(CORE_INITIALIZED);
|
||||
SCASE(BEGIN_UNLOAD);
|
||||
}
|
||||
|
||||
return plugin_eval_rb(cmd);
|
||||
}
|
||||
|
||||
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
|
||||
{
|
||||
out.print("This command loads the ruby script whose path is given as parameter, and run it.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
std::string cmd = "load '";
|
||||
cmd += parameters[0]; // TODO escape singlequotes
|
||||
cmd += "'";
|
||||
|
||||
return plugin_eval_rb(cmd);
|
||||
}
|
||||
|
||||
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
command_result ret;
|
||||
|
||||
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
|
||||
{
|
||||
out.print("This command executes an arbitrary ruby statement.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
std::string full = "";
|
||||
|
||||
for (unsigned i=0 ; i<parameters.size() ; ++i) {
|
||||
full += parameters[i];
|
||||
if (i != parameters.size()-1)
|
||||
full += " ";
|
||||
}
|
||||
|
||||
return plugin_eval_rb(full);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ruby stuff
|
||||
|
||||
// ruby thread code
|
||||
static void dump_rb_error(void)
|
||||
{
|
||||
VALUE s, err;
|
||||
|
||||
err = rb_gv_get("$!");
|
||||
|
||||
s = rb_funcall(err, rb_intern("class"), 0);
|
||||
s = rb_funcall(s, rb_intern("name"), 0);
|
||||
Core::printerr("E: %s: ", rb_string_value_ptr(&s));
|
||||
|
||||
s = rb_funcall(err, rb_intern("message"), 0);
|
||||
Core::printerr("%s\n", rb_string_value_ptr(&s));
|
||||
|
||||
err = rb_funcall(err, rb_intern("backtrace"), 0);
|
||||
for (int i=0 ; i<8 ; ++i)
|
||||
if ((s = rb_ary_shift(err)) != Qnil)
|
||||
Core::printerr(" %s\n", rb_string_value_ptr(&s));
|
||||
}
|
||||
|
||||
static color_ostream_proxy *console_proxy;
|
||||
|
||||
// ruby thread main loop
|
||||
static void df_rubythread(void *p)
|
||||
{
|
||||
int state, running;
|
||||
|
||||
// initialize the ruby interpreter
|
||||
ruby_init();
|
||||
ruby_init_loadpath();
|
||||
// default value for the $0 "current script name"
|
||||
ruby_script("dfhack");
|
||||
|
||||
// create the ruby objects to map DFHack to ruby methods
|
||||
ruby_bind_dfhack();
|
||||
|
||||
console_proxy = new color_ostream_proxy(Core::getInstance().getConsole());
|
||||
|
||||
r_result = CR_OK;
|
||||
r_type = RB_IDLE;
|
||||
|
||||
running = 1;
|
||||
while (running) {
|
||||
// wait for new command
|
||||
m_irun->lock();
|
||||
|
||||
switch (r_type) {
|
||||
case RB_IDLE:
|
||||
case RB_INIT:
|
||||
break;
|
||||
|
||||
case RB_DIE:
|
||||
running = 0;
|
||||
ruby_finalize();
|
||||
break;
|
||||
|
||||
case RB_EVAL:
|
||||
state = 0;
|
||||
rb_eval_string_protect(r_command, &state);
|
||||
if (state)
|
||||
dump_rb_error();
|
||||
break;
|
||||
|
||||
case RB_CUSTOM:
|
||||
// TODO handle ruby custom commands
|
||||
break;
|
||||
}
|
||||
|
||||
r_result = CR_OK;
|
||||
r_type = RB_IDLE;
|
||||
m_irun->unlock();
|
||||
tthread::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define BOOL_ISFALSE(v) ((v) == Qfalse || (v) == Qnil || (v) == INT2FIX(0))
|
||||
|
||||
// main DFHack ruby module
|
||||
static VALUE rb_cDFHack;
|
||||
|
||||
|
||||
// DFHack module ruby methods, binds specific dfhack methods
|
||||
|
||||
// enable/disable calls to DFHack.onupdate()
|
||||
static VALUE rb_dfonupdateactive(VALUE self)
|
||||
{
|
||||
if (onupdate_active)
|
||||
return Qtrue;
|
||||
else
|
||||
return Qfalse;
|
||||
}
|
||||
|
||||
static VALUE rb_dfonupdateactiveset(VALUE self, VALUE val)
|
||||
{
|
||||
onupdate_active = (BOOL_ISFALSE(val) ? 0 : 1);
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
static VALUE rb_dfresume(VALUE self)
|
||||
{
|
||||
Core::getInstance().Resume();
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
static VALUE rb_dfsuspend(VALUE self)
|
||||
{
|
||||
Core::getInstance().Suspend();
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// returns the delta to apply to dfhack xml addrs wrt actual memory addresses
|
||||
// usage: real_addr = addr_from_xml + this_delta;
|
||||
static VALUE rb_dfrebase_delta(void)
|
||||
{
|
||||
uint32_t expected_base_address;
|
||||
uint32_t actual_base_address = 0;
|
||||
#ifdef WIN32
|
||||
expected_base_address = 0x00400000;
|
||||
actual_base_address = (uint32_t)GetModuleHandle(0);
|
||||
#else
|
||||
expected_base_address = 0x08048000;
|
||||
FILE *f = fopen("/proc/self/maps", "r");
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strstr(line, "libs/Dwarf_Fortress")) {
|
||||
actual_base_address = strtoul(line, 0, 16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return rb_int2inum(actual_base_address - expected_base_address);
|
||||
}
|
||||
|
||||
static VALUE rb_dfprint_str(VALUE self, VALUE s)
|
||||
{
|
||||
console_proxy->print("%s", rb_string_value_ptr(&s));
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE rb_dfprint_err(VALUE self, VALUE s)
|
||||
{
|
||||
Core::printerr("%s", rb_string_value_ptr(&s));
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
/* TODO needs main dfhack support
|
||||
this needs a custom DFHack::Plugin subclass to pass the cmdname to invoke(), to match the ruby callback
|
||||
// register a ruby method as dfhack console command
|
||||
// usage: DFHack.register("moo", "this commands prints moo on the console") { DFHack.puts "moo !" }
|
||||
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
|
||||
{
|
||||
commands.push_back(PluginCommand(rb_string_value_ptr(&name),
|
||||
rb_string_value_ptr(&descr),
|
||||
df_rubycustom));
|
||||
|
||||
return Qtrue;
|
||||
}
|
||||
*/
|
||||
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
|
||||
{
|
||||
rb_raise(rb_eRuntimeError, "not implemented");
|
||||
}
|
||||
|
||||
static VALUE rb_dfget_global_address(VALUE self, VALUE name)
|
||||
{
|
||||
return rb_uint2inum(Core::getInstance().vinfo->getAddress(rb_string_value_ptr(&name)));
|
||||
}
|
||||
|
||||
static VALUE rb_dfget_vtable(VALUE self, VALUE name)
|
||||
{
|
||||
return rb_uint2inum((uint32_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name)));
|
||||
}
|
||||
|
||||
// read the c++ class name from a vtable pointer, inspired from doReadClassName
|
||||
// XXX virtual classes only! dark pointer arithmetic, use with caution !
|
||||
static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr)
|
||||
{
|
||||
char *ptr = (char*)rb_num2ulong(vptr);
|
||||
#ifdef WIN32
|
||||
char *rtti = *(char**)(ptr - 0x4);
|
||||
char *typeinfo = *(char**)(rtti + 0xC);
|
||||
// skip the .?AV, trim @@ from end
|
||||
return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2);
|
||||
#else
|
||||
char *typeinfo = *(char**)(ptr - 0x4);
|
||||
char *typestring = *(char**)(typeinfo + 0x4);
|
||||
while (*typestring >= '0' && *typestring <= '9')
|
||||
typestring++;
|
||||
return rb_str_new2(typestring);
|
||||
#endif
|
||||
}
|
||||
|
||||
static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
|
||||
{
|
||||
// actually, rb_dfmemory_read_int32
|
||||
return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// raw memory access
|
||||
// used by the ruby class definitions
|
||||
// XXX may cause game crash ! double-check your addresses !
|
||||
|
||||
static VALUE rb_dfmalloc(VALUE self, VALUE len)
|
||||
{
|
||||
char *ptr = (char*)malloc(FIX2INT(len));
|
||||
if (!ptr)
|
||||
rb_raise(rb_eRuntimeError, "no memory");
|
||||
memset(ptr, 0, FIX2INT(len));
|
||||
return rb_uint2inum((long)ptr);
|
||||
}
|
||||
|
||||
static VALUE rb_dffree(VALUE self, VALUE ptr)
|
||||
{
|
||||
free((void*)rb_num2ulong(ptr));
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// memory reading (buffer)
|
||||
static VALUE rb_dfmemory_read(VALUE self, VALUE addr, VALUE len)
|
||||
{
|
||||
return rb_str_new((char*)rb_num2ulong(addr), rb_num2ulong(len));
|
||||
}
|
||||
|
||||
// memory reading (integers/floats)
|
||||
static VALUE rb_dfmemory_read_int8(VALUE self, VALUE addr)
|
||||
{
|
||||
return rb_int2inum(*(char*)rb_num2ulong(addr));
|
||||
}
|
||||
static VALUE rb_dfmemory_read_int16(VALUE self, VALUE addr)
|
||||
{
|
||||
return rb_int2inum(*(short*)rb_num2ulong(addr));
|
||||
}
|
||||
static VALUE rb_dfmemory_read_int32(VALUE self, VALUE addr)
|
||||
{
|
||||
return rb_int2inum(*(int*)rb_num2ulong(addr));
|
||||
}
|
||||
|
||||
static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr)
|
||||
{
|
||||
return rb_float_new(*(float*)rb_num2ulong(addr));
|
||||
}
|
||||
|
||||
|
||||
// memory writing (buffer)
|
||||
static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw)
|
||||
{
|
||||
// no stable api for raw.length between rb1.8/rb1.9 ...
|
||||
int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
|
||||
|
||||
memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen);
|
||||
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// memory writing (integers/floats)
|
||||
static VALUE rb_dfmemory_write_int8(VALUE self, VALUE addr, VALUE val)
|
||||
{
|
||||
*(char*)rb_num2ulong(addr) = rb_num2ulong(val);
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_write_int16(VALUE self, VALUE addr, VALUE val)
|
||||
{
|
||||
*(short*)rb_num2ulong(addr) = rb_num2ulong(val);
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_write_int32(VALUE self, VALUE addr, VALUE val)
|
||||
{
|
||||
*(int*)rb_num2ulong(addr) = rb_num2ulong(val);
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
|
||||
{
|
||||
*(float*)rb_num2ulong(addr) = rb_num2dbl(val);
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
|
||||
// stl::string
|
||||
static VALUE rb_dfmemory_stlstring_init(VALUE self, VALUE addr)
|
||||
{
|
||||
// XXX THIS IS TERRIBLE
|
||||
std::string *ptr = new std::string;
|
||||
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr)
|
||||
{
|
||||
std::string *s = (std::string*)rb_num2ulong(addr);
|
||||
return rb_str_new(s->c_str(), s->length());
|
||||
}
|
||||
static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val)
|
||||
{
|
||||
std::string *s = (std::string*)rb_num2ulong(addr);
|
||||
int strlen = FIX2INT(rb_funcall(val, rb_intern("length"), 0));
|
||||
s->assign(rb_string_value_ptr(&val), strlen);
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
|
||||
// vector access
|
||||
static VALUE rb_dfmemory_vec_init(VALUE self, VALUE addr)
|
||||
{
|
||||
std::vector<uint8_t> *ptr = new std::vector<uint8_t>;
|
||||
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
|
||||
return Qtrue;
|
||||
}
|
||||
// vector<uint8>
|
||||
static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr)
|
||||
{
|
||||
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum(v->size());
|
||||
}
|
||||
static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
|
||||
}
|
||||
static VALUE rb_dfmemory_vec8_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||
{
|
||||
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_vec8_delete(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||
v->erase(v->begin()+FIX2INT(idx));
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// vector<uint16>
|
||||
static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr)
|
||||
{
|
||||
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum(v->size());
|
||||
}
|
||||
static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
|
||||
}
|
||||
static VALUE rb_dfmemory_vec16_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||
{
|
||||
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_vec16_delete(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||
v->erase(v->begin()+FIX2INT(idx));
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// vector<uint32>
|
||||
static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr)
|
||||
{
|
||||
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum(v->size());
|
||||
}
|
||||
static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
|
||||
}
|
||||
static VALUE rb_dfmemory_vec32_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||
{
|
||||
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_vec32_delete(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||
v->erase(v->begin()+FIX2INT(idx));
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// vector<bool>
|
||||
static VALUE rb_dfmemory_vecbool_length(VALUE self, VALUE addr)
|
||||
{
|
||||
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum(v->size());
|
||||
}
|
||||
static VALUE rb_dfmemory_vecbool_at(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||
return v->at(FIX2INT(idx)) ? Qtrue : Qfalse;
|
||||
}
|
||||
static VALUE rb_dfmemory_vecbool_setat(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||
{
|
||||
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||
v->at(FIX2INT(idx)) = (BOOL_ISFALSE(val) ? 0 : 1);
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_vecbool_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||
{
|
||||
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||
v->insert(v->begin()+FIX2INT(idx), (BOOL_ISFALSE(val) ? 0 : 1));
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||
v->erase(v->begin()+FIX2INT(idx));
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
// BitArray
|
||||
static VALUE rb_dfmemory_bitarray_length(VALUE self, VALUE addr)
|
||||
{
|
||||
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||
return rb_uint2inum(b->size*8); // b->size is in bytes
|
||||
}
|
||||
static VALUE rb_dfmemory_bitarray_resize(VALUE self, VALUE addr, VALUE sz)
|
||||
{
|
||||
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||
b->resize(rb_num2ulong(sz));
|
||||
return Qtrue;
|
||||
}
|
||||
static VALUE rb_dfmemory_bitarray_isset(VALUE self, VALUE addr, VALUE idx)
|
||||
{
|
||||
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||
return b->is_set(rb_num2ulong(idx)) ? Qtrue : Qfalse;
|
||||
}
|
||||
static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||
{
|
||||
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||
b->set(rb_num2ulong(idx), (BOOL_ISFALSE(val) ? 0 : 1));
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
|
||||
/* call an arbitrary object virtual method */
|
||||
static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE a1, VALUE a2, VALUE a3)
|
||||
{
|
||||
#ifdef WIN32
|
||||
__thiscall
|
||||
#endif
|
||||
int (*fptr)(char **me, int, int, int, int);
|
||||
char **that = (char**)rb_num2ulong(cppobj);
|
||||
int ret;
|
||||
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
|
||||
ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
|
||||
return rb_uint2inum(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// define module DFHack and its methods
|
||||
static void ruby_bind_dfhack(void) {
|
||||
rb_cDFHack = rb_define_module("DFHack");
|
||||
|
||||
// global DFHack commands
|
||||
rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdateactive), 0);
|
||||
rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdateactiveset), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "resume", RUBY_METHOD_FUNC(rb_dfresume), 0);
|
||||
rb_define_singleton_method(rb_cDFHack, "do_suspend", RUBY_METHOD_FUNC(rb_dfsuspend), 0);
|
||||
rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1);
|
||||
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, "register_dfcommand", RUBY_METHOD_FUNC(rb_dfregister), 2);
|
||||
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);
|
||||
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 6);
|
||||
rb_define_const(rb_cDFHack, "REBASE_DELTA", rb_dfrebase_delta());
|
||||
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1);
|
||||
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
|
||||
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_init", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_init), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_read_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_read_stlstring), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_write_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_write_stlstring), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector_init", RUBY_METHOD_FUNC(rb_dfmemory_vec_init), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector8_length", RUBY_METHOD_FUNC(rb_dfmemory_vec8_length), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector8_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_ptrat), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector8_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec8_insert), 3);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector8_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec8_delete), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector16_length", RUBY_METHOD_FUNC(rb_dfmemory_vec16_length), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector16_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_ptrat), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector16_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec16_insert), 3);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector16_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec16_delete), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector32_length", RUBY_METHOD_FUNC(rb_dfmemory_vec32_length), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector32_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insert), 3);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vector32_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec32_delete), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_length", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_length), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_at", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_at), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_setat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_setat), 3);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_insert", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_insert), 3);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_length", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_length), 1);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_resize", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_resize), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_isset", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_isset), 2);
|
||||
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_set", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_set), 3);
|
||||
|
||||
// load the default ruby-level definitions
|
||||
int state=0;
|
||||
rb_load_protect(rb_str_new2("./hack/ruby.rb"), Qfalse, &state);
|
||||
if (state)
|
||||
dump_rb_error();
|
||||
}
|
@ -0,0 +1,302 @@
|
||||
require 'hack/ruby-autogen'
|
||||
|
||||
module DFHack
|
||||
class << self
|
||||
# update the ruby.cpp version to accept a block
|
||||
def suspend
|
||||
if block_given?
|
||||
begin
|
||||
do_suspend
|
||||
yield
|
||||
ensure
|
||||
resume
|
||||
end
|
||||
else
|
||||
do_suspend
|
||||
end
|
||||
end
|
||||
|
||||
module ::Kernel
|
||||
def puts(*a)
|
||||
a.flatten.each { |l|
|
||||
DFHack.print_str(l.to_s.chomp + "\n")
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
def puts_err(*a)
|
||||
a.flatten.each { |l|
|
||||
DFHack.print_err(l.to_s.chomp + "\n")
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
def p(*a)
|
||||
a.each { |e|
|
||||
puts_err e.inspect
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# register a callback to be called every gframe or more
|
||||
# ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 }
|
||||
def onupdate_register(&b)
|
||||
@onupdate_list ||= []
|
||||
@onupdate_list << b
|
||||
DFHack.onupdate_active = true
|
||||
@onupdate_list.last
|
||||
end
|
||||
|
||||
# delete the callback for onupdate ; use the value returned by onupdate_register
|
||||
def onupdate_unregister(b)
|
||||
@onupdate_list.delete b
|
||||
DFHack.onupdate_active = false if @onupdate_list.empty?
|
||||
end
|
||||
|
||||
# this method is called by dfhack every 'onupdate' if onupdate_active is true
|
||||
def onupdate
|
||||
@onupdate_list ||= []
|
||||
@onupdate_list.each { |cb| cb.call }
|
||||
end
|
||||
|
||||
# register a callback to be called every gframe or more
|
||||
# ex: DFHack.onstatechange_register { |newstate| puts "state changed to #{newstate}" }
|
||||
def onstatechange_register(&b)
|
||||
@onstatechange_list ||= []
|
||||
@onstatechange_list << b
|
||||
@onstatechange_list.last
|
||||
end
|
||||
|
||||
# delete the callback for onstatechange ; use the value returned by onstatechange_register
|
||||
def onstatechange_unregister(b)
|
||||
@onstatechange_list.delete b
|
||||
end
|
||||
|
||||
# this method is called by dfhack every 'onstatechange'
|
||||
def onstatechange(newstate)
|
||||
@onstatechange_list ||= []
|
||||
@onstatechange_list.each { |cb| cb.call(newstate) }
|
||||
end
|
||||
|
||||
|
||||
# return an Unit
|
||||
# with no arg, return currently selected unit in df UI ('v' or 'k' menu)
|
||||
# with numeric arg, search unit by unit.id
|
||||
# with an argument that respond to x/y/z (eg cursor), find first unit at this position
|
||||
def find_unit(what=:selected)
|
||||
if what == :selected
|
||||
case ui.main.mode
|
||||
when :ViewUnits
|
||||
# nobody selected => idx == 0
|
||||
v = world.units.active[ui_selected_unit]
|
||||
v if v and v.pos.z == cursor.z
|
||||
when :LookAround
|
||||
k = ui_look_list.items[ui_look_cursor]
|
||||
k.unit if k.type == :Unit
|
||||
end
|
||||
elsif what.kind_of?(Integer)
|
||||
world.units.all.binsearch(what)
|
||||
elsif what.respond_to?(:x) or what.respond_to?(:pos)
|
||||
what = what.pos if what.respond_to?(:pos)
|
||||
x, y, z = what.x, what.y, what.z
|
||||
world.units.all.find { |u| u.pos.x == x and u.pos.y == y and u.pos.z == z }
|
||||
else
|
||||
raise "what what?"
|
||||
end
|
||||
end
|
||||
|
||||
# return an Item
|
||||
# arg similar to find_unit; no arg = 'k' menu
|
||||
def find_item(what=:selected)
|
||||
if what == :selected
|
||||
case ui.main.mode
|
||||
when :LookAround
|
||||
k = ui_look_list.items[ui_look_cursor]
|
||||
k.item if k.type == :Item
|
||||
end
|
||||
elsif what.kind_of?(Integer)
|
||||
world.items.all.binsearch(what)
|
||||
elsif what.respond_to?(:x) or what.respond_to?(:pos)
|
||||
what = what.pos if what.respond_to?(:pos)
|
||||
x, y, z = what.x, what.y, what.z
|
||||
world.items.all.find { |i| i.pos.x == x and i.pos.y == y and i.pos.z == z }
|
||||
else
|
||||
raise "what what?"
|
||||
end
|
||||
end
|
||||
|
||||
# return a map block by tile coordinates
|
||||
# you can also use find_map_block(cursor) or anything that respond to x/y/z
|
||||
def map_block_at(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
if x >= 0 and x < world.map.x_count and y >= 0 and y < world.map.y_count and z >= 0 and z < world.map.z_count
|
||||
world.map.block_index[x/16][y/16][z]
|
||||
end
|
||||
end
|
||||
|
||||
def map_designation_at(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
if b = map_block_at(x, y, z)
|
||||
b.designation[x%16][y%16]
|
||||
end
|
||||
end
|
||||
|
||||
def map_occupancy_at(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
if b = map_block_at(x, y, z)
|
||||
b.occupancy[x%16][y%16]
|
||||
end
|
||||
end
|
||||
|
||||
# yields every map block
|
||||
def each_map_block
|
||||
(0...world.map.x_count_block).each { |xb|
|
||||
xl = world.map.block_index[xb]
|
||||
(0...world.map.y_count_block).each { |yb|
|
||||
yl = xl[yb]
|
||||
(0...world.map.z_count_block).each { |z|
|
||||
p = yl[z]
|
||||
yield p if p
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# yields every map block for a given z level
|
||||
def each_map_block_z(z)
|
||||
(0...world.map.x_count_block).each { |xb|
|
||||
xl = world.map.block_index[xb]
|
||||
(0...world.map.y_count_block).each { |yb|
|
||||
p = xl[yb][z]
|
||||
yield p if p
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# return true if the argument is under the cursor
|
||||
def at_cursor?(obj)
|
||||
same_pos?(obj, cursor)
|
||||
end
|
||||
|
||||
# returns true if both arguments are at the same x/y/z
|
||||
def same_pos?(pos1, pos2)
|
||||
pos1 = pos1.pos if pos1.respond_to?(:pos)
|
||||
pos2 = pos2.pos if pos2.respond_to?(:pos)
|
||||
pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z
|
||||
end
|
||||
|
||||
# center the DF screen on something
|
||||
# updates the cursor position if visible
|
||||
def center_viewscreen(x, y=nil, z=nil)
|
||||
x = x.pos if x.respond_to?(:pos)
|
||||
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
|
||||
|
||||
# compute screen 'map' size (tiles)
|
||||
menuwidth = ui_menu_width
|
||||
# ui_menu_width shows only the 'tab' status
|
||||
menuwidth = 1 if menuwidth == 2 and ui_area_map_width == 2 and cursor.x != -30000
|
||||
menuwidth = 2 if menuwidth == 3 and cursor.x != -30000
|
||||
w_w = gps.dimx - 2
|
||||
w_h = gps.dimy - 2
|
||||
case menuwidth
|
||||
when 1; w_w -= 55
|
||||
when 2; w_w -= (ui_area_map_width == 2 ? 24 : 31)
|
||||
end
|
||||
|
||||
# center view
|
||||
w_x = x - w_w/2
|
||||
w_y = y - w_h/2
|
||||
w_z = z
|
||||
# round view coordinates (optional)
|
||||
#w_x -= w_x % 10
|
||||
#w_y -= w_y % 10
|
||||
# crop to map limits
|
||||
w_x = [[w_x, world.map.x_count - w_w].min, 0].max
|
||||
w_y = [[w_y, world.map.y_count - w_h].min, 0].max
|
||||
|
||||
self.window_x = w_x
|
||||
self.window_y = w_y
|
||||
self.window_z = w_z
|
||||
|
||||
if cursor.x != -30000
|
||||
cursor.x, cursor.y, cursor.z = x, y, z
|
||||
end
|
||||
end
|
||||
|
||||
# add an announcement
|
||||
# color = integer, bright = bool
|
||||
def add_announcement(str, color=nil, bright=nil)
|
||||
cont = false
|
||||
while str.length > 0
|
||||
rep = Report.cpp_new
|
||||
rep.color = color if color
|
||||
rep.bright = ((bright && bright != 0) ? 1 : 0) if bright != nil
|
||||
rep.year = cur_year
|
||||
rep.time = cur_year_tick
|
||||
rep.flags.continuation = cont
|
||||
cont = true
|
||||
rep.flags.announcement = true
|
||||
rep.text = str[0, 73]
|
||||
str = str[73..-1].to_s
|
||||
rep.id = world.status.next_report_id
|
||||
world.status.next_report_id += 1
|
||||
world.status.reports << rep
|
||||
world.status.announcements << rep
|
||||
world.status.display_timer = 2000
|
||||
end
|
||||
end
|
||||
|
||||
# try to match a user-specified name to one from the raws
|
||||
# uses case-switching and substring matching
|
||||
# eg match_rawname('coal', ['COAL_BITUMINOUS', 'BAUXITE']) => 'COAL_BITUMINOUS'
|
||||
def match_rawname(name, rawlist)
|
||||
rawlist.each { |r| return r if name == r }
|
||||
rawlist.each { |r| return r if name.downcase == r.downcase }
|
||||
may = rawlist.find_all { |r| r.downcase.index(name.downcase) }
|
||||
may.first if may.length == 1
|
||||
end
|
||||
|
||||
# link a job to the world
|
||||
# allocate & set job.id, allocate a JobListLink, link to job & world.job_list
|
||||
def job_link(job)
|
||||
lastjob = world.job_list
|
||||
lastjob = lastjob.next while lastjob.next
|
||||
joblink = JobListLink.cpp_new
|
||||
joblink.prev = lastjob
|
||||
joblink.item = job
|
||||
job.list_link = joblink
|
||||
job.id = df.job_next_id
|
||||
df.job_next_id += 1
|
||||
lastjob.next = joblink
|
||||
end
|
||||
|
||||
# attach an item to a job, flag item in_job
|
||||
def job_attachitem(job, item, role=:Hauled, filter_idx=-1)
|
||||
if role != :TargetContainer
|
||||
item.flags.in_job = true
|
||||
end
|
||||
|
||||
itemlink = SpecificRef.cpp_new
|
||||
itemlink.type = :JOB
|
||||
itemlink.job = job
|
||||
item.specific_refs << itemlink
|
||||
|
||||
joblink = JobItemRef.cpp_new
|
||||
joblink.item = item
|
||||
joblink.role = role
|
||||
joblink.job_item_idx = filter_idx
|
||||
job.items << joblink
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# global alias so we can write 'df.world.units.all[0]'
|
||||
def df
|
||||
DFHack
|
||||
end
|
||||
|
||||
# load user-specified startup file
|
||||
load 'ruby_custom.rb' if File.exist?('ruby_custom.rb')
|
Loading…
Reference in New Issue