diff options
| -rw-r--r-- | NEWS | 9 | ||||
| -rw-r--r-- | README.rst | 37 | ||||
| -rw-r--r-- | plugins/ruby/README | 2 | ||||
| -rw-r--r-- | plugins/ruby/building.rb | 92 | ||||
| -rwxr-xr-x | plugins/ruby/codegen.pl | 3 | ||||
| -rw-r--r-- | plugins/ruby/item.rb | 2 | ||||
| -rw-r--r-- | plugins/ruby/map.rb | 17 | ||||
| -rw-r--r-- | plugins/ruby/plant.rb | 12 | ||||
| -rw-r--r-- | plugins/ruby/ruby-autogen-defs.rb | 103 | ||||
| -rw-r--r-- | plugins/ruby/ruby.cpp | 81 | ||||
| -rw-r--r-- | plugins/ruby/ruby.rb | 15 | ||||
| -rw-r--r-- | plugins/ruby/ui.rb | 14 | ||||
| -rw-r--r-- | plugins/ruby/unit.rb | 6 | ||||
| -rw-r--r-- | scripts/deathcause.rb | 37 | ||||
| -rw-r--r-- | scripts/digfort.rb | 38 | ||||
| -rw-r--r-- | scripts/drainaquifer.rb | 11 | ||||
| -rw-r--r-- | scripts/superdwarf.rb | 61 |
17 files changed, 474 insertions, 66 deletions
@@ -33,6 +33,15 @@ DFHack v0.34.11-r2 (UNRELEASED) - siren: wakes up units, stops breaks and parties - but causes bad thoughts. - fix/population-cap: run after every migrant wave to prevent exceeding the cap. - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. + - fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill. + - deathcause: shows the circumstances of death for a given body. + - digfort: designate areas to dig from a csv file. + - drainaquifer: remove aquifers from the map. + - growcrops: cheat to make farm crops instantly grow. + - magmasource: continuously spawn magma from any map tile. + - removebadthoughts: delete all negative thoughts from your dwarves. + - slayrace: instakill all units of a given race, optionally with magma. + - superdwarf: per-creature fastdwarf. New GUI scripts: - gui/mechanisms: browse mechanism links of the current building. - gui/room-list: browse other rooms owned by the unit when assigning one. @@ -1631,6 +1631,43 @@ To remove all placed sources, call ``magmasource stop``. With no argument, this command shows an help message and list existing sources. +digfort +========= +A script to designate an area for digging according to a plan in csv format. + +This script, inspired from quickfort, can designate an area for digging. +Your plan should be stored in a .csv file like this: +:: + # this is a comment + d;d;u;d;d;skip this tile;d + d;d;d;i + +Available tile shapes are named after the 'dig' menu shortcuts: +``d`` for dig, ``u`` for upstairs, ``d`` downstairs, ``i`` updown, +``h`` channel, ``r`` upward ramp, ``x`` remove designation. +Unrecognized characters are ignored (eg the 'skip this tile' in the sample). + +Empty lines and data after a ``#`` are ignored as comments. +To skip a row in your design, use a single ``;``. + +The script takes the plan filename, starting from the root df folder. + +superdwarf +========== +Similar to fastdwarf, per-creature. + +To make any creature superfast, target it ingame using 'v' and: +:: + superdwarf add + +Other options available: ``del``, ``clear``, ``list``. + +This plugin also shortens the 'sleeping' and 'on break' periods of targets. + +drainaquifer +============ +Remove all 'aquifer' tag from the map blocks. Irreversible. + ======================= In-game interface tools ======================= diff --git a/plugins/ruby/README b/plugins/ruby/README index c9a84fb3..9246fec8 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -177,7 +177,7 @@ Symbol. This works for array indexes too. ex: df.unit_find(:selected).status.labors[:HAUL_FOOD] = true df.map_tile_at(df.cursor).designation.liquid_type = :Water -Virtual method calls are supported for C++ objects, with a maximum of 4 +Virtual method calls are supported for C++ objects, with a maximum of 6 arguments. Arguments / return value are interpreted as Compound/Enums as specified in the vmethod definition in the xmls. diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index af152e19..68229c00 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -8,6 +8,8 @@ module DFHack k.building if k.type == :Building when :BuildingItems, :QueryBuilding world.selected_building + when :Zones, :ZonesPenInfo, :ZonesPitInfo, :ZonesHospitalInfo + ui_sidebar_menus.zone.selected end elsif what.kind_of?(Integer) @@ -21,8 +23,8 @@ module DFHack if b.room.extents dx = x - b.room.x dy = y - b.room.y - dx >= 0 and dx <= b.room.width and - dy >= 0 and dy <= b.room.height and + dx >= 0 and dx < b.room.width and + dy >= 0 and dy < b.room.height and b.room.extents[ dy*b.room.width + dx ] > 0 else b.x1 <= x and b.x2 >= x and @@ -46,8 +48,11 @@ module DFHack 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 + subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop + subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace + subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone + bld.setSubtype(subtype) + bld.setCustomType(custom) case type when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0 when :Coffin; bld.initBurialFlags @@ -176,9 +181,20 @@ module DFHack # set building at position, with optional width/height def building_position(bld, pos, w=nil, h=nil) - bld.x1 = pos.x - bld.y1 = pos.y - bld.z = pos.z + if pos.respond_to?(:x1) + x, y, z = pos.x1, pos.y1, pos.z + w ||= pos.x2-pos.x1+1 if pos.respond_to?(:x2) + h ||= pos.y2-pos.y1+1 if pos.respond_to?(:y2) + elsif pos.respond_to?(:x) + x, y, z = pos.x, pos.y, pos.z + else + x, y, z = pos + end + w ||= pos.w if pos.respond_to?(:w) + h ||= pos.h if pos.respond_to?(:h) + bld.x1 = x + bld.y1 = y + bld.z = z bld.x2 = bld.x1+w-1 if w bld.y2 = bld.y1+h-1 if h building_setsize(bld) @@ -193,7 +209,7 @@ module DFHack 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 extents and 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 @@ -207,17 +223,24 @@ module DFHack } end - # link bld into other rooms if it is inside their extents + # link bld into other rooms if it is inside their extents or vice versa def 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 + next if ob.z != bld.z + if bld.is_room and bld.room.extents + next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height + next if bld.room.extents[bld.room.width*(ob.y1-bld.room.y)+(ob.x1-bld.room.x)] == 0 + ui.equipment.update.buildings = true + bld.children << ob + ob.parents << bld + elsif ob.is_room and ob.room.extents + next if bld.is_room or bld.x1 < ob.room.x or bld.x1 >= ob.room.x+ob.room.width or bld.y1 < ob.room.y or bld.y1 >= ob.room.y+ob.room.height + next if ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)].to_i == 0 + ui.equipment.update.buildings = true + ob.children << bld + bld.parents << ob + end } - ui.equipment.update.buildings = true if didstuff end # link the building into the world, set map data, link rooms, bld.id @@ -274,6 +297,43 @@ module DFHack building_createdesign(bld, rough) end + # construct an abstract building (stockpile, farmplot, ...) + def building_construct_abstract(bld) + if bld.getType == :Stockpile + max = df.world.buildings.other[:STOCKPILE].map { |s| s.stockpile_number }.max + bld.stockpile_number = max.to_i + 1 + end + building_link bld + if !bld.flags.exists + bld.flags.exists = true + bld.initFarmSeasons + end + end + + def building_setowner(bld, unit) + return unless bld.is_room + return if bld.owner == unit + + if bld.owner + if idx = bld.owner.owned_buildings.index { |ob| ob.id == bld.id } + bld.owner.owned_buildings.delete_at(idx) + end + if spouse = bld.owner.relations.spouse_tg and + idx = spouse.owned_buildings.index { |ob| ob.id == bld.id } + spouse.owned_buildings.delete_at(idx) + end + end + bld.owner = unit + if unit + unit.owned_buildings << bld + if spouse = bld.owner.relations.spouse_tg and + !spouse.owned_buildings.index { |ob| ob.id == bld.id } and + bld.canUseSpouseRoom + spouse.owned_buildings << bld + end + end + end + # creates a job to deconstruct the building def building_deconstruct(bld) job = Job.cpp_new diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 593216d7..a615ab96 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -788,6 +788,7 @@ sub render_item_number { my $subtype = $item->getAttribute('ld:subtype'); my $meta = $item->getAttribute('ld:meta'); my $initvalue = $item->getAttribute('init-value'); + $initvalue ||= -1 if $item->getAttribute('refers-to') or $item->getAttribute('ref-target'); my $typename = $item->getAttribute('type-name'); undef $typename if ($meta and $meta eq 'bitfield-type'); my $g = $global_types{$typename} if ($typename); @@ -964,7 +965,7 @@ sub render_item_bytes { my $subtype = $item->getAttribute('ld:subtype'); if ($subtype eq 'padding') { } elsif ($subtype eq 'static-string') { - my $size = $item->getAttribute('size'); + my $size = $item->getAttribute('size') || -1; push @lines_rb, "static_string($size)"; } else { print "no render bytes $subtype\n"; diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 34b40450..032c0d8c 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -26,6 +26,8 @@ module DFHack u = world.units.active[ui_selected_unit] u.inventory[ui_look_cursor].item if u and u.pos.z == cursor.z and ui_unit_view_mode.value == :Inventory and u.inventory[ui_look_cursor] + else + ui.follow_item_tg if ui.follow_item != -1 end end elsif what.kind_of?(Integer) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index dccea729..d662a343 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -189,8 +189,21 @@ module DFHack end def dig(mode=:Default) - designation.dig = mode - mapblock.flags.designated = true + if mode == :Smooth + if tilemat != :SOIL and caption !~ /smooth|pillar|fortification/i and # XXX caption.. + designation.smooth == 0 and not df.world.job_list.find { |j| + # the game removes 'smooth' designation as soon as it assigns a job, if we + # re-set it the game may queue another :DetailWall that will carve a fortification + (j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self) + } + designation.dig = :No + designation.smooth = 1 + mapblock.flags.designated = true + end + else + designation.dig = mode + mapblock.flags.designated = true if mode != :No + end end def spawn_liquid(quantity, is_magma=false, flowing=true) diff --git a/plugins/ruby/plant.rb b/plugins/ruby/plant.rb index 5d6b9d72..2f5a1c7c 100644 --- a/plugins/ruby/plant.rb +++ b/plugins/ruby/plant.rb @@ -51,7 +51,7 @@ module DFHack end SaplingToTreeAge = 120960 - def cuttrees(material=nil, count_max=100) + def cuttrees(material=nil, count_max=100, quiet=false) if !material # list trees cnt = Hash.new(0) @@ -62,7 +62,7 @@ module DFHack } cnt.sort_by { |mat, c| c }.each { |mat, c| name = @raws_tree_name[mat] - puts " #{name} #{c}" + puts " #{name} #{c}" unless quiet } else cnt = 0 @@ -78,11 +78,11 @@ module DFHack break if cnt == count_max end } - puts "Updated #{cnt} plant designations" + puts "Updated #{cnt} plant designations" unless quiet end end - def growtrees(material=nil, count_max=100) + def growtrees(material=nil, count_max=100, quiet=false) if !material # list plants cnt = Hash.new(0) @@ -93,7 +93,7 @@ module DFHack } cnt.sort_by { |mat, c| c }.each { |mat, c| name = @raws_tree_name[mat] - puts " #{name} #{c}" + puts " #{name} #{c}" unless quiet } else cnt = 0 @@ -104,7 +104,7 @@ module DFHack cnt += 1 break if cnt == count_max } - puts "Grown #{cnt} saplings" + puts "Grown #{cnt} saplings" unless quiet end end end diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 0cee6426..64c08fac 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -124,15 +124,17 @@ module DFHack 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?' + else _field_names.each { |n| send("#{n}=", h.send(n)) } 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 _raw_rtti_classname ; df.get_rtti_classname(df.get_vtable_ptr(@_memaddr)) if self.class._rtti_classname ; end def _sizeof ; self.class._sizeof ; end + def ==(o) ; o.kind_of?(Compound) and o._memaddr == _memaddr ; end + @@inspecting = {} # avoid infinite recursion on mutually-referenced objects def inspect cn = self.class.name.sub(/^DFHack::/, '') @@ -289,12 +291,12 @@ module DFHack # 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) + case v + when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) + when Integer; DFHack.memory_write_int32(@_memaddr, v) + when nil; DFHack.memory_write_int32(@_memaddr, 0) + else _get._set(v) end end @@ -328,6 +330,16 @@ module DFHack self end + def _set(v) + case v + when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) + when Integer; DFHack.memory_write_int32(@_memaddr, v) + when nil; DFHack.memory_write_int32(@_memaddr, 0) + else raise "cannot PointerAry._set(#{v.inspect})" + end + end + def [](i) addr = _getp(i) return if addr == 0 @@ -411,11 +423,20 @@ module DFHack def initialize(length) @_length = length end + def length + if @_length == -1 + maxlen = 4096 - (@_memaddr & 0xfff) + maxlen += 4096 until len = DFHack.memory_read(@_memaddr, maxlen).index(?\0) + len + else + @_length + end + end def _get - DFHack.memory_read(@_memaddr, @_length) + DFHack.memory_read(@_memaddr, length) end def _set(v) - DFHack.memory_write(@_memaddr, v[0, @_length]) + DFHack.memory_write(@_memaddr, v[0, length]) end end @@ -464,7 +485,7 @@ module DFHack def []=(idx, v) idx += length if idx < 0 if idx >= length - insert_at(idx, 0) + insert_at(length, 0) while idx >= length elsif idx < 0 raise 'index out of bounds' end @@ -698,8 +719,8 @@ module DFHack return if not addr @_tg._at(addr)._get end - alias next= _next= - alias prev= _prev= + alias next= _next= + alias prev= _prev= include Enumerable def each @@ -747,6 +768,52 @@ module DFHack end end + class StlSet + attr_accessor :_memaddr, :_enum + def self.cpp_new(init=nil, enum=nil) + ret = new DFHack.memory_stlset_new, enum + init.each { |k| ret.set(k) } if init + ret + end + + def initialize(addr, enum=nil) + addr = nil if addr == 0 + @_memaddr = addr + @_enum = enum + end + + def isset(key) + raise unless @_memaddr + key = @_enum.int(key) if _enum + DFHack.memory_stlset_isset(@_memaddr, key) + end + alias is_set? isset + + def set(key) + raise unless @_memaddr + key = @_enum.int(key) if _enum + DFHack.memory_stlset_set(@_memaddr, key) + end + + def delete(key) + raise unless @_memaddr + key = @_enum.int(key) if _enum + DFHack.memory_stlset_deletekey(@_memaddr, key) + end + + def clear + raise unless @_memaddr + DFHack.memory_stlset_clear(@_memaddr) + end + + def _cpp_delete + raise unless @_memaddr + DFHack.memory_stlset_delete(@_memaddr) + @_memaddr = nil + end + end + + # cpp rtti name -> rb class @rtti_n2c = {} @rtti_c2n = {} @@ -795,8 +862,12 @@ module DFHack 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)) + def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0, a5=0) + this = obj._memaddr + vt = df.get_vtable_ptr(this) + fptr = df.memory_read_int32(vt + voff) & 0xffffffff + vmethod_do_call(this, fptr, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), + vmethod_arg(a3), vmethod_arg(a4), vmethod_arg(a5)) end def self.vmethod_arg(arg) @@ -805,7 +876,7 @@ module DFHack when true; 1 when Integer; arg #when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer - when MemHack::Compound; arg._memaddr + when MemHack::Compound, StlSet; arg._memaddr else raise "bad vmethod arg #{arg.class}" end end diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 482714d2..e291a770 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -632,7 +632,7 @@ static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) bool ret; ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr), - rb_string_value_ptr(&raw), strlen); + rb_string_value_ptr(&raw), strlen); return ret ? Qtrue : Qfalse; } @@ -835,11 +835,54 @@ static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE v return Qtrue; } +// add basic support for std::set<uint32> used for passing keyboard keys to viewscreens +#include <set> +static VALUE rb_dfmemory_set_new(VALUE self) +{ + std::set<unsigned long> *ptr = new std::set<unsigned long>; + return rb_uint2inum((uint32_t)ptr); +} + +static VALUE rb_dfmemory_set_delete(VALUE self, VALUE set) +{ + std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set); + if (ptr) + delete ptr; + return Qtrue; +} + +static VALUE rb_dfmemory_set_set(VALUE self, VALUE set, VALUE key) +{ + std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set); + ptr->insert(rb_num2ulong(key)); + return Qtrue; +} + +static VALUE rb_dfmemory_set_isset(VALUE self, VALUE set, VALUE key) +{ + std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set); + return ptr->count(rb_num2ulong(key)) ? Qtrue : Qfalse; +} + +static VALUE rb_dfmemory_set_deletekey(VALUE self, VALUE set, VALUE key) +{ + std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set); + ptr->erase(rb_num2ulong(key)); + return Qtrue; +} + +static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set) +{ + std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set); + ptr->clear(); + return Qtrue; +} + /* call an arbitrary object virtual method */ #ifdef WIN32 -__declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3) +__declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, + unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { // __thiscall requires that the callee cleans up the stack // here we dont know how many arguments it will take, so @@ -848,6 +891,8 @@ __declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned push ebp mov ebp, esp + push a5 + push a4 push a3 push a2 push a1 @@ -855,9 +900,7 @@ __declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned mov ecx, that - mov eax, [ecx] - add eax, voff - call [eax] + call fptr mov esp, ebp pop ebp @@ -865,25 +908,25 @@ __declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned } } #else -static int raw_vcall(char **that, unsigned long voff, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3) +static int raw_vcall(void *that, void *fptr, unsigned long a0, + unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { - int (*fptr)(char **me, int, int, int, int); - fptr = (decltype(fptr))*(void**)(*that + voff); - return fptr(that, a0, a1, a2, a3); + int (*t_fptr)(void *me, int, int, int, int, int, int); + t_fptr = (decltype(t_fptr))fptr; + return t_fptr(that, a0, a1, a2, a3, a4, a5); } #endif // call an arbitrary vmethod, convert args/ret to native values for raw_vcall -static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE a1, VALUE a2, VALUE a3) +static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE fptr, VALUE a0, VALUE a1, VALUE a2, VALUE a3, VALUE a4, VALUE a5) { - return rb_int2inum(raw_vcall((char**)rb_num2ulong(cppobj), rb_num2ulong(cppvoff), + return rb_int2inum(raw_vcall((void*)rb_num2ulong(cppobj), (void*)rb_num2ulong(fptr), rb_num2ulong(a0), rb_num2ulong(a1), - rb_num2ulong(a2), rb_num2ulong(a3))); + rb_num2ulong(a2), rb_num2ulong(a3), + rb_num2ulong(a4), rb_num2ulong(a5))); } - // define module DFHack and its methods static void ruby_bind_dfhack(void) { rb_cDFHack = rb_define_module("DFHack"); @@ -902,7 +945,7 @@ static void ruby_bind_dfhack(void) { 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_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); 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); @@ -950,4 +993,10 @@ static void ruby_bind_dfhack(void) { 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); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_new", RUBY_METHOD_FUNC(rb_dfmemory_set_new), 0); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_delete", RUBY_METHOD_FUNC(rb_dfmemory_set_delete), 1); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_set", RUBY_METHOD_FUNC(rb_dfmemory_set_set), 2); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_isset", RUBY_METHOD_FUNC(rb_dfmemory_set_isset), 2); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_deletekey", RUBY_METHOD_FUNC(rb_dfmemory_set_deletekey), 2); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_clear", RUBY_METHOD_FUNC(rb_dfmemory_set_clear), 1); } diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 8c2c9796..aeae101d 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -25,11 +25,11 @@ end module DFHack class OnupdateCallback attr_accessor :callback, :timelimit, :minyear, :minyeartick - def initialize(cb, tl) + def initialize(cb, tl, initdelay=0) @callback = cb @ticklimit = tl @minyear = (tl ? df.cur_year : 0) - @minyeartick = (tl ? df.cur_year_tick : 0) + @minyeartick = (tl ? df.cur_year_tick+initdelay : 0) end # run callback if timedout @@ -61,9 +61,9 @@ module DFHack # 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(ticklimit=nil, &b) + def onupdate_register(ticklimit=nil, initialtickdelay=0, &b) @onupdate_list ||= [] - @onupdate_list << OnupdateCallback.new(b, ticklimit) + @onupdate_list << OnupdateCallback.new(b, ticklimit, initialtickdelay) DFHack.onupdate_active = true if onext = @onupdate_list.sort.first DFHack.onupdate_minyear = onext.minyear @@ -81,6 +81,13 @@ module DFHack end end + # same as onupdate_register, but remove the callback once it returns true + def onupdate_register_once(*a) + handle = onupdate_register(*a) { + onupdate_unregister(handle) if yield + } + end + TICKS_PER_YEAR = 1200*28*12 # this method is called by dfhack every 'onupdate' if onupdate_active is true def onupdate diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index 9dded66c..a9dd0543 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -66,11 +66,12 @@ module DFHack world.status.reports << rep world.status.announcements << rep world.status.display_timer = 2000 + yield rep if block_given? end end - # add an announcement to display in a game popup message - # (eg "the megabeast foobar arrived") + # add an announcement to display in a game popup message + # (eg "the megabeast foobar arrived") def popup_announcement(str, color=nil, bright=nil) pop = PopupMessage.cpp_new(:text => str) pop.color = color if color @@ -78,4 +79,13 @@ module DFHack world.status.popups << pop end end + + class Viewscreen + def feed_keys(*keys) + keyset = StlSet.cpp_new(keys, InterfaceKey) + ret = feed(keyset) + keyset._cpp_delete + ret + end + end end diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 1a619c5c..5289229a 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -24,6 +24,8 @@ module DFHack when :LookAround k = ui_look_list.items[ui_look_cursor] k.unit if k.type == :Unit + else + ui.follow_unit_tg if ui.follow_unit != -1 end end elsif what.kind_of?(Integer) @@ -47,7 +49,7 @@ module DFHack end def unit_iscitizen(u) - u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and + u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and !u.flags1.forest 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 @@ -104,7 +106,7 @@ module DFHack end class LanguageName - def to_s(english=true) + def to_s(english=false) df.translate_name(self, english) end end diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb new file mode 100644 index 00000000..178ebbc8 --- /dev/null +++ b/scripts/deathcause.rb @@ -0,0 +1,37 @@ +# show death cause of a creature + +def display_event(e) + p e if $DEBUG + + str = "#{e.victim_tg.name} died in year #{e.year}" + str << " of #{e.death_cause}" if false + str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_tg.name}" if e.slayer != -1 + str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON + str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON + + puts str + '.' +end + +item = df.item_find(:selected) + +if !item or !item.kind_of?(DFHack::ItemBodyComponent) + item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } +end + +if !item or !item.kind_of?(DFHack::ItemBodyComponent) + puts "Please select a corpse in the loo'k' menu" +else + hfig = item.hist_figure_id + if hfig == -1 + puts "Not a historical figure, cannot find info" + else + events = df.world.history.events + (0...events.length).reverse_each { |i| + if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim == hfig + display_event(events[i]) + break + end + } + end +end + diff --git a/scripts/digfort.rb b/scripts/digfort.rb new file mode 100644 index 00000000..6d609f9a --- /dev/null +++ b/scripts/digfort.rb @@ -0,0 +1,38 @@ +# designate an area for digging according to a plan in csv format + +raise "usage: digfort <plan filename>" if not $script_args[0] +planfile = File.read($script_args[0]) + +if df.cursor.x == -30000 + raise "place the game cursor to the top-left corner of the design" +end + +tiles = planfile.lines.map { |l| + l.sub(/#.*/, '').split(';').map { |t| t.strip } +} + +x = x0 = df.cursor.x +y = df.cursor.y +z = df.cursor.z + +tiles.each { |line| + next if line.empty? or line == [''] + line.each { |tile| + t = df.map_tile_at(x, y, z) + s = t.shape_basic + case tile + when 'd'; t.dig(:Default) if s == :Wall + when 'u'; t.dig(:UpStair) if s == :Wall + when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor + when 'i'; t.dig(:UpDownStair) if s == :Wall + when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor + when 'r'; t.dig(:Ramp) if s == :Wall + when 'x'; t.dig(:No) + end + x += 1 + } + x = x0 + y += 1 +} + +puts 'done' diff --git a/scripts/drainaquifer.rb b/scripts/drainaquifer.rb new file mode 100644 index 00000000..4e2ae4ff --- /dev/null +++ b/scripts/drainaquifer.rb @@ -0,0 +1,11 @@ +# remove all aquifers from the map + +count = 0 +df.each_map_block { |b| + if b.designation[0][0].water_table or b.designation[15][15].water_table + count += 1 + b.designation.each { |dx| dx.each { |dy| dy.water_table = false } } + end +} + +puts "cleared #{count} map blocks" diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb new file mode 100644 index 00000000..7f5296b7 --- /dev/null +++ b/scripts/superdwarf.rb @@ -0,0 +1,61 @@ +# give super-dwarven speed to an unit + +$superdwarf_onupdate ||= nil +$superdwarf_ids ||= [] + +case $script_args[0] +when 'add' + if u = df.unit_find + $superdwarf_ids |= [u.id] + + $superdwarf_onupdate ||= df.onupdate_register(1) { + if $superdwarf_ids.empty? + df.onupdate_unregister($superdwarf_onupdate) + $superdwarf_onupdate = nil + else + $superdwarf_ids.each { |id| + if u = df.unit_find(id) and not u.flags1.dead + # faster walk/work + if u.counters.job_counter > 0 + u.counters.job_counter = 0 + end + + # no sleep + if u.counters2.sleepiness_timer > 10000 + u.counters2.sleepiness_timer = 1 + end + + # no break + if b = u.status.misc_traits.find { |t| t.id == :OnBreak } + b.value = 500_000 + end + else + $superdwarf_ids.delete id + end + } + end + } + else + puts "Select a creature using 'v'" + end + +when 'del' + if u = df.unit_find + $superdwarf_ids.delete u.id + else + puts "Select a creature using 'v'" + end + +when 'clear' + $superdwarf_ids.clear + +when 'list' + puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } + +else + puts "Usage:", + " - superdwarf add: give superspeed to currently selected creature", + " - superdwarf del: remove superspeed to current creature", + " - superdwarf clear: remove all superpowers", + " - superdwarf list: list super-dwarves" +end |
