summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS9
-rw-r--r--README.rst37
-rw-r--r--plugins/ruby/README2
-rw-r--r--plugins/ruby/building.rb96
-rwxr-xr-xplugins/ruby/codegen.pl3
-rw-r--r--plugins/ruby/item.rb2
-rw-r--r--plugins/ruby/map.rb31
-rw-r--r--plugins/ruby/plant.rb12
-rw-r--r--plugins/ruby/ruby-autogen-defs.rb103
-rw-r--r--plugins/ruby/ruby.cpp81
-rw-r--r--plugins/ruby/ruby.rb15
-rw-r--r--plugins/ruby/ui.rb14
-rw-r--r--plugins/ruby/unit.rb6
-rw-r--r--scripts/deathcause.rb37
-rw-r--r--scripts/digfort.rb38
-rw-r--r--scripts/drainaquifer.rb11
-rw-r--r--scripts/superdwarf.rb61
17 files changed, 491 insertions, 67 deletions
diff --git a/NEWS b/NEWS
index d46c47e6..8f7c407e 100644
--- a/NEWS
+++ b/NEWS
@@ -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.
diff --git a/README.rst b/README.rst
index 97eb61a8..e495b158 100644
--- a/README.rst
+++ b/README.rst
@@ -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..3f8842b4 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,47 @@ module DFHack
building_createdesign(bld, rough)
end
+ # construct an abstract building (stockpile, farmplot, ...)
+ def building_construct_abstract(bld)
+ case bld.getType
+ when :Stockpile
+ max = df.world.buildings.other[:STOCKPILE].map { |s| s.stockpile_number }.max
+ bld.stockpile_number = max.to_i + 1
+ when :Civzone
+ max = df.world.buildings.other[:ANY_ZONE].map { |z| z.zone_num }.max
+ bld.zone_num = 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..9629c3f9 100644
--- a/plugins/ruby/map.rb
+++ b/plugins/ruby/map.rb
@@ -26,7 +26,7 @@ module DFHack
end
end
- def map_tile_at(x, y=nil, z=nil)
+ def map_tile_at(x=df.cursor, 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)
b = map_block_at(x, y, z)
@@ -67,6 +67,14 @@ module DFHack
@mapblock = b
end
+ def offset(dx, dy=nil, dz=0)
+ if dx.respond_to?(:x)
+ dz = dx.z if dx.respond_to?(:z)
+ dx, dy = dx.x, dx.y
+ end
+ df.map_tile_at(@x+dx, @y+dy, @z+dz)
+ end
+
def designation
@mapblock.designation[@dx][@dy]
end
@@ -189,8 +197,25 @@ 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 (designation.hidden or 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
+ return if mode != :No and designation.dig == :No and not designation.hidden and df.world.job_list.find { |j|
+ # someone already enroute to dig here, avoid 'Inappropriate dig square' spam
+ JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self)
+ }
+ 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