Petr Mrázek 2013-04-24 16:24:14 +02:00
commit df6355a092
33 changed files with 1625 additions and 1362 deletions

@ -63,6 +63,23 @@ extra options.
You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui
program. program.
Fixing the libstdc++ version bug
================================
When compiling dfhack yourself, it builds against your system libc.
When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which
is usually way older, and incompatible with your dfhack. This manifests with
the error message::
./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version
`GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so)
To fix this, simply remove the libstdc++ shipped with DF, it will fall back
to your system lib and everything will work fine::
cd /path/to/DF/
rm libs/libstdc++.so.6
======== ========
Mac OS X Mac OS X
======== ========

@ -5,8 +5,13 @@ DFHack future
- restrictice - Restrict traffic on squares above visible ice. - restrictice - Restrict traffic on squares above visible ice.
New scripts: New scripts:
- masspit: designate caged creatures in a zone for pitting - masspit: designate caged creatures in a zone for pitting
- locate_ore: scan the map for unmined ore veins
- multicmd: run a sequence of dfhack commands, separated by ';'
Misc improvements: Misc improvements:
- exterminate: renamed from slayrace, add help message - exterminate: renamed from slayrace, add help message, add butcher mode
- autoSyndrome: disable by default
- ruby: add df.dfhack_run "somecommand"
- magmasource: rename to source, allow water/magma sources/drains
DFHack v0.34.11-r3 DFHack v0.34.11-r3

@ -1857,7 +1857,7 @@ use in your farming plots.
With a seed type, the script will grow 100 of these seeds, ready to be With a seed type, the script will grow 100 of these seeds, ready to be
harvested. You can change the number with a 2nd argument. harvested. You can change the number with a 2nd argument.
For exemple, to grow 40 plump helmet spawn: For example, to grow 40 plump helmet spawn:
:: ::
growcrops plump 40 growcrops plump 40
@ -1900,9 +1900,10 @@ such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command, An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the ``magma``. In this case, a column of 7/7 magma is generated on top of the
targets until they die (Warning: do not call on magma-safe creatures. Also, targets until they die (Warning: do not call on magma-safe creatures. Also,
using this mode for birds is not recommanded.) using this mode on birds is not recommanded.)
Will target any unit on a revealed tile of the map, including ambushers. Will target any unit on a revealed tile of the map, including ambushers,
but ignore caged/chained creatures.
Ex:: Ex::
@ -1917,24 +1918,32 @@ To purify all elves on the map with fire (may have side-effects)::
exterminate elve magma exterminate elve magma
magmasource source
=========== ======
Create an infinite magma source on a tile. Create an infinite magma or water source or drain on a tile.
This script registers a map tile as a magma source, and every 12 game ticks This script registers a map tile as a liquid source, and every 12 game ticks
that tile receives 1 new unit of flowing magma. that tile receives or remove 1 new unit of flow based on the configuration.
Place the game cursor where you want to create the source (must be a Place the game cursor where you want to create the source (must be a
flow-passable tile, and not too high in the sky) and call:: flow-passable tile, and not too high in the sky) and call::
magmasource here source add [magma|water] [0-7]
To add more than 1 unit everytime, call the command again. The number argument is the target liquid level (0 = drain, 7 = source).
To delete one source, place the cursor over its tile and use ``delete-here``. To add more than 1 unit everytime, call the command again on the same spot.
To remove all placed sources, call ``magmasource stop``.
With no argument, this command shows an help message and list existing sources. To delete one source, place the cursor over its tile and use ``delete``.
To remove all existing sources, call ``source clear``.
The ``list`` argument shows all existing sources.
Ex::
source add water - water source
source add magma 7 - magma source
source add water 0 - water drain
masspit masspit
======= =======
@ -2066,7 +2075,7 @@ Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that. boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials. However the ``list`` mode will only show 'normal' materials.
Exemples:: Examples::
create-items boulders COAL_BITUMINOUS 12 create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig create-items plant tail_pig
@ -2075,6 +2084,20 @@ Exemples::
create-items bar CREATURE:CAT:SOAP create-items bar CREATURE:CAT:SOAP
create-items bar adamantine create-items bar adamantine
locate-ore
==========
Scan the map for metal ores.
Finds and designate for digging one tile of a specific metal ore.
Only works for native metal ores, does not handle reaction stuff (eg STEEL).
When invoked with the ``list`` argument, lists metal ores available on the map.
Examples::
locate-ore list
locate-ore hematite
locate-ore iron
soundsense-season soundsense-season
================= =================
@ -2086,6 +2109,15 @@ This script registers a hook that prints the appropriate string
to gamelog.txt on every map load to fix this. For best results to gamelog.txt on every map load to fix this. For best results
call the script from ``dfhack.init``. call the script from ``dfhack.init``.
multicmd
========
Run multiple dfhack commands. The argument is split around the
character ; and all parts are run sequencially as independent
dfhack commands. Useful for hotkeys.
Example::
multicmd locate-ore iron ; digv
======================= =======================
In-game interface tools In-game interface tools
======================= =======================

@ -137,6 +137,9 @@ tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring # remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training tweak military-training
# enable autoSyndrome
autoSyndrome enable
########### ###########
# Scripts # # Scripts #
########### ###########
@ -149,23 +152,21 @@ fix/cloth-stockpile enable
####################################################### #######################################################
# Apply binary patches at runtime # # Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
####################################################### #######################################################
# Bug 5994 - items teleported when removing a construction # Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items # Bug 4406 - hospital overstocking on all items
#binpatch apply hospital-overstocking binpatch apply hospital-overstocking
# Bug 808 - custom reactions completely using up all of their reagents # Bug 808 - custom reactions completely using up all of their reagents
#binpatch apply custom-reagent-size binpatch apply custom-reagent-size
# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo # Bug 4530 - marksdwarves not training when quiver full of combat-only ammo
#binpatch apply training-ammo binpatch apply training-ammo
# Bug 1445 - weapon racks broken, armor stand capacity too low # Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity binpatch apply armorstand-capacity

@ -98,7 +98,7 @@ reaction_duck
Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. Next, start a new fort in a new world, build a duck workshop, then have someone become a duck.
*/ */
bool enabled = true; bool enabled = false;
DFHACK_PLUGIN("autoSyndrome"); DFHACK_PLUGIN("autoSyndrome");
@ -279,6 +279,9 @@ void processJob(color_ostream& out, void* jobPtr) {
} }
} }
if ( workerId == -1 )
return;
int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId);
if ( workerIndex < 0 ) { if ( workerIndex < 0 ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);

@ -55,7 +55,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" Also follows the stone between z-levels with stairs, like 'digl x' would.\n" " Also follows the stone between z-levels with stairs, like 'digl x' would.\n"
)); ));
commands.push_back(PluginCommand("digexp","Select or designate an exploratory pattern. Use 'digexp ?' for help.",digexp)); commands.push_back(PluginCommand("digexp","Select or designate an exploratory pattern. Use 'digexp ?' for help.",digexp));
commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow) with given radius.",digcircle)); commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow) with given diameter.",digcircle));
//commands.push_back(PluginCommand("digauto","Mark a tile for continuous digging.",autodig)); //commands.push_back(PluginCommand("digauto","Mark a tile for continuous digging.",autodig));
commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey, commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey,
"For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated.\n" "For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated.\n"
@ -120,7 +120,7 @@ bool dig (MapExtras::MapCache & MCache,
if(tileMaterial(tt) == tiletype_material::CONSTRUCTION && !des.bits.hidden) if(tileMaterial(tt) == tiletype_material::CONSTRUCTION && !des.bits.hidden)
return false; return false;
df::tiletype_shape ts = tileShape(tt); df::tiletype_shape ts = tileShape(tt);
if (ts == tiletype_shape::EMPTY) if (ts == tiletype_shape::EMPTY && !des.bits.hidden)
return false; return false;
if(!des.bits.hidden) if(!des.bits.hidden)
{ {
@ -293,7 +293,7 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
"\n" "\n"
"After you have set the options, the command called with no options\n" "After you have set the options, the command called with no options\n"
"repeats with the last selected parameters:\n" "repeats with the last selected parameters:\n"
"'digcircle filled 3' = Dig a filled circle with radius = 3.\n" "'digcircle filled 3' = Dig a filled circle with diameter = 3.\n"
"'digcircle' = Do it again.\n" "'digcircle' = Do it again.\n"
); );
return CR_OK; return CR_OK;

@ -54,6 +54,19 @@ The help string displayed in dfhack 'ls' command is the first line of the
script, if it is a comment (ie starts with '# '). script, if it is a comment (ie starts with '# ').
Calling dfhack commands
-----------------------
The ruby plugin allows the calling of arbitrary dfhack commands, as if typed
directly on the dfhack prompt.
However due to locks and stuff, the dfhack command is delayed until the current
ruby command is finished, so it is restricted to interactive uses.
It is possible to call the method many times, this will queue dfhack commands
to be run in order.
df.dfhack_run "reveal"
Ruby helper functions Ruby helper functions
--------------------- ---------------------
@ -107,7 +120,7 @@ eg 'gob' for 'GOBLIN' or 'coal' for 'COAL_BITUMINOUS', hence the name.
df.building_construct(bld, item_list) df.building_construct(bld, item_list)
Allocates a new building in DF memory, define its position / dimensions, and Allocates a new building in DF memory, define its position / dimensions, and
create a dwarf job to construct it from the given list of items. create a dwarf job to construct it from the given list of items.
See buildings.rb/buildbed for an exemple. See buildings.rb/buildbed for an example.
df.each_tree(material) { |t| } df.each_tree(material) { |t| }
Iterates over every tree of the given material (eg 'maple'). Iterates over every tree of the given material (eg 'maple').
@ -137,13 +150,17 @@ To stop being called, use:
The same mechanism is available for 'onstatechange', but the The same mechanism is available for 'onstatechange', but the
SC_BEGIN_UNLOAD event is not propagated to the ruby handler. SC_BEGIN_UNLOAD event is not propagated to the ruby handler.
Available states:
:WORLD_LOADED, :WORLD_UNLOADED, :MAP_LOADED, :MAP_UNLOADED,
:VIEWSCREEN_CHANGED, :CORE_INITIALIZED, :PAUSED, :UNPAUSED
C++ object manipulation C++ object manipulation
----------------------- -----------------------
The ruby classes defined in ruby-autogen.rb are accessors to the underlying The ruby classes defined in ruby-autogen.rb are accessors to the underlying
df C++ objects in-memory. To allocate a new C++ object for use in DF, use the df C++ objects in-memory. To allocate a new C++ object for use in DF, use the
RubyClass.cpp_new method (see buildings.rb for exemples), works for Compounds RubyClass.cpp_new method (see buildings.rb for examples), works for Compounds
only. only.
A special Compound DFHack::StlString is available for allocating a single c++ A special Compound DFHack::StlString is available for allocating a single c++
stl::string, so that you can call vmethods that take a string pointer argument stl::string, so that you can call vmethods that take a string pointer argument
@ -186,10 +203,10 @@ Pointer fields are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. NULL pointers yield the 'nil' value. Units will yield Units directly. NULL pointers yield the 'nil' value.
Exemples Examples
-------- --------
For more complex exemples, check the dfhack/scripts/*.rb files. For more complex examples, check the dfhack/scripts/*.rb files.
Show info on the currently selected unit ('v' or 'k' DF menu) Show info on the currently selected unit ('v' or 'k' DF menu)
p df.unit_find.flags1 p df.unit_find.flags1
@ -240,7 +257,7 @@ which differ between Windows and Linux. Linux and Macosx are the same, as they
both use gcc). both use gcc).
It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb) It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb)
For exemple, For example,
<ld:global-type ld:meta="struct-type" type-name="unit"> <ld:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/> <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 name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>

@ -48,6 +48,8 @@ module DFHack
raise "invalid building type #{type.inspect}" if not cls raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new bld = cls.cpp_new
bld.race = ui.race_id bld.race = ui.race_id
subtype = ConstructionType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Construction
subtype = SiegeengineType.int(subtype) if subtype.kind_of?(::Symbol) and type == :SiegeEngine
subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop 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 = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace
subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone

@ -149,7 +149,21 @@ module DFHack
def vein def vein
# last vein wins # last vein wins
all_veins.reverse.find { |v| all_veins.reverse.find { |v|
(v.tile_bitmask.bits[@dy] & (1 << @dx)) > 0 v.tile_bitmask.bits[@dy][@dx] > 0
}
end
# return the first BlockBurrow this tile is in (nil if none)
def burrow
mapblock.block_burrows.find { |b|
b.tile_bitmask.bits[@dy][@dx] > 0
}
end
# return the array of BlockBurrow this tile is in
def all_burrows
mapblock.block_burrows.find_all { |b|
b.tile_bitmask.bits[@dy][@dx] > 0
} }
end end

@ -122,7 +122,8 @@ module DFHack
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init } _fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end end
def _cpp_delete def _cpp_delete
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_delete } # cannot call delete on compound members (would call free on member address)
#_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_delete }
DFHack.free(@_memaddr) DFHack.free(@_memaddr)
@_memaddr = nil # turn future segfaults in harmless ruby exceptions @_memaddr = nil # turn future segfaults in harmless ruby exceptions
end end
@ -642,7 +643,7 @@ module DFHack
@_tg = tg @_tg = tg
end end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every # 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. # possible struct size, like for StlVector. Just ignore it for now, deques are rare enough.
def inspect ; "#<StlDeque>" ; end def inspect ; "#<StlDeque>" ; end
end end
@ -676,7 +677,7 @@ module DFHack
def inspect def inspect
out = "#<DfFlagarray" out = "#<DfFlagarray"
each_with_index { |e, idx| each_with_index { |e, idx|
out << " #{_indexenum.sym(idx)}" if e out << " #{_indexenum ? _indexenum.sym(idx) : idx}" if e
} }
out << '>' out << '>'
end end

@ -41,6 +41,7 @@ static tthread::thread *r_thread;
static int onupdate_active; static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1; static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1;
static color_ostream_proxy *console_proxy; static color_ostream_proxy *console_proxy;
static std::vector<std::string> *dfhack_run_queue;
DFHACK_PLUGIN("ruby") DFHACK_PLUGIN("ruby")
@ -63,6 +64,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
// lock this before anything, and release when everything is done // lock this before anything, and release when everything is done
m_mutex = new tthread::mutex(); m_mutex = new tthread::mutex();
// list of dfhack commands to run when the current ruby run is done (once locks are released)
dfhack_run_queue = new std::vector<std::string>;
r_type = RB_INIT; r_type = RB_INIT;
// create the dedicated ruby thread // create the dedicated ruby thread
@ -84,6 +88,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
"Ruby interpreter. Eval() a ruby string.", "Ruby interpreter. Eval() a ruby string.",
df_rubyeval)); df_rubyeval));
commands.push_back(PluginCommand("rb",
"Ruby interpreter. Eval() a ruby string (alias for rb_eval).",
df_rubyeval));
return CR_OK; return CR_OK;
} }
@ -111,6 +119,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
// we can release m_mutex, other users will check r_thread // we can release m_mutex, other users will check r_thread
m_mutex->unlock(); m_mutex->unlock();
delete m_mutex; delete m_mutex;
delete dfhack_run_queue;
// dlclose libruby // dlclose libruby
df_unloadruby(); df_unloadruby();
@ -152,6 +161,8 @@ static command_result do_plugin_eval_ruby(color_ostream &out, const char *comman
// send a single ruby line to be evaluated by the ruby thread // send a single ruby line to be evaluated by the ruby thread
DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command)
{ {
command_result ret;
// if dlopen failed // if dlopen failed
if (!r_thread) if (!r_thread)
return CR_FAILURE; return CR_FAILURE;
@ -160,14 +171,24 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c
// debug only! // debug only!
// run ruby commands without locking the main thread // run ruby commands without locking the main thread
// useful when the game is frozen after a segfault // useful when the game is frozen after a segfault
return do_plugin_eval_ruby(out, command+7); ret = do_plugin_eval_ruby(out, command+7);
} else { } else {
// wrap all ruby code inside a suspend block // wrap all ruby code inside a suspend block
// if we dont do that and rely on ruby code doing it, we'll deadlock in // if we dont do that and rely on ruby code doing it, we'll deadlock in
// onupdate // onupdate
CoreSuspender suspend; CoreSuspender suspend;
return do_plugin_eval_ruby(out, command); ret = do_plugin_eval_ruby(out, command);
} }
// if any dfhack command is queued for run, do it now
while (!dfhack_run_queue->empty()) {
std::string cmd = dfhack_run_queue->at(0);
// delete before running the command, which may be ruby and cause infinite loops
dfhack_run_queue->erase(dfhack_run_queue->begin());
Core::getInstance().runCommand(out, cmd);
}
return ret;
} }
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
@ -550,18 +571,10 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
// run a dfhack command, as if typed from the dfhack console // run a dfhack command, as if typed from the dfhack console
static VALUE rb_dfhack_run(VALUE self, VALUE cmd) static VALUE rb_dfhack_run(VALUE self, VALUE cmd)
{ {
if (!r_console) // XXX
return Qnil;
std::string s; std::string s;
int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0));
s.assign(rb_string_value_ptr(&cmd), strlen); s.assign(rb_string_value_ptr(&cmd), strlen);
dfhack_run_queue->push_back(s);
// allow the target command to suspend
// FIXME
//CoreSuspendClaimer suspend(true);
Core::getInstance().runCommand(*r_console, s);
return Qtrue; return Qtrue;
} }
@ -1045,7 +1058,7 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 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_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, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
//rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); 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, "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, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);

@ -137,7 +137,7 @@ end
$AutoFarm ||= AutoFarm.new $AutoFarm ||= AutoFarm.new
case $script_args[0] case $script_args[0]
when 'start' when 'start', 'enable'
$AutoFarm.start $AutoFarm.start
puts $AutoFarm.status puts $AutoFarm.status

@ -0,0 +1,10 @@
# list indexes in world.item.other[] where current selected item appears
tg = df.item_find
raise 'select an item' if not tg
o = df.world.items.other
# discard ANY/BAD
o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k|
puts k if o[k].find { |i| i == tg }
}

@ -0,0 +1,3 @@
# unforbid all items
df.world.items.all.each { |i| i.flags.forbid = false }

@ -2,10 +2,12 @@
count = 0 count = 0
df.each_map_block { |b| df.each_map_block { |b|
if b.designation[0][0].water_table or b.designation[15][15].water_table if b.designation[0][0].water_table or b.designation[8][8].water_table
count += 1 count += 1
b.designation.each { |dx| dx.each { |dy| dy.water_table = false } } df.each_map_block_z(b.map_pos.z) { |bz|
bz.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
}
end end
} }
puts "cleared #{count} map blocks" puts "cleared #{count} aquifer#{'s' if count > 1}"

@ -5,7 +5,19 @@
race = $script_args[0] race = $script_args[0]
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death # if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
magma = ($script_args[1] == 'magma') # if it is 'butcher' mark all units for butchering (wont work with hostiles)
kill_by = $script_args[1]
case kill_by
when 'magma'
slain = 'burning'
when 'slaughter', 'butcher'
slain = 'marked for butcher'
when nil
slain = 'slain'
else
race = 'help'
end
checkunit = lambda { |u| checkunit = lambda { |u|
(u.body.blood_count != 0 or u.body.blood_max == 0) and (u.body.blood_count != 0 or u.body.blood_max == 0) and
@ -16,12 +28,8 @@ checkunit = lambda { |u|
} }
slayit = lambda { |u| slayit = lambda { |u|
if not magma case kill_by
# just make them drop dead when 'magma'
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of.
u.animal.vanish_countdown = 2
else
# it's getting hot around here # it's getting hot around here
# !!WARNING!! do not call on a magma-safe creature # !!WARNING!! do not call on a magma-safe creature
ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) { ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
@ -34,6 +42,14 @@ slayit = lambda { |u|
df.map_tile_at(x, y, z).spawn_magma(7) df.map_tile_at(x, y, z).spawn_magma(7)
end end
} }
when 'butcher', 'slaughter'
# mark for slaughter at butcher's shop
u.flags2.slaughter = true
else
# just make them drop dead
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of.
u.animal.vanish_countdown = 2
end end
} }
@ -65,14 +81,21 @@ With the special argument 'undead', kill all undead creatures/thralls.
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke. The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
The special final argument 'magma' will make magma rain on the targets instead. The special final argument 'magma' will make magma rain on the targets instead.
The special final argument 'butcher' will mark the targets for butchering instead.
Ex: exterminate gob Ex: exterminate gob
exterminate elve magma exterminate elve magma
exterminate him exterminate him
exterminate pig butcher
EOS EOS
when 'him', 'her' when 'him', 'her', 'it', 'that'
if him = df.unit_find if him = df.unit_find
case him.race_tg.caste[him.caste].gender
when 0; puts 'its a she !' if race != 'her'
when 1; puts 'its a he !' if race != 'him'
else; puts 'its an it !' if race != 'it' and race != 'that'
end
slayit[him] slayit[him]
else else
puts "Select a target ingame" puts "Select a target ingame"
@ -89,7 +112,7 @@ when /^undead/i
count += 1 count += 1
end end
} }
puts "slain #{count} undeads" puts "#{slain} #{count} undeads"
else else
raw_race = df.match_rawname(race, all_races.keys) raw_race = df.match_rawname(race, all_races.keys)
@ -107,6 +130,6 @@ else
count += 1 count += 1
end end
} }
puts "slain #{count} #{raw_race}" puts "#{slain} #{count} #{raw_race}"
end end

@ -1,7 +1,7 @@
# fix doors that are frozen in 'open' state # fix doors that are frozen in 'open' state
# door is stuck in open state if the map occupancy flag incorrectly indicates # this may happen after people mess with the game by (incorrectly) teleporting units or items
# that an unit is present (and creatures will prone to pass through) # a door may stick open if the map occupancy flags are wrong
count = 0 count = 0
df.world.buildings.all.each { |bld| df.world.buildings.all.each { |bld|
@ -11,10 +11,15 @@ df.world.buildings.all.each { |bld|
next if bld.close_timer == 0 next if bld.close_timer == 0
# check if occupancy is set # check if occupancy is set
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
next if not occ.unit if (occ.unit or occ.unit_grounded) and not
# check if an unit is present # check if an unit is present
next if df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z } df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
count += 1 count += 1
occ.unit = false occ.unit = occ.unit_grounded = false
end
if occ.item and not df.world.items.all.find { |i| i.pos.x == bld.x1 and i.pos.y == bld.y1 and i.pos.z == bld.z }
count += 1
occ.item = false
end
} }
puts "unstuck #{count} doors" puts "unstuck #{count} doors"

@ -0,0 +1,84 @@
# scan the map for ore veins
target_ore = $script_args[0]
def find_all_ore_veins
puts 'scanning map...'
$ore_veins = {}
seen_mat = {}
df.each_map_block { |block|
block.block_events.grep(DFHack::BlockSquareEventMineralst).each { |vein|
mat_index = vein.inorganic_mat
if not seen_mat[mat_index] or $ore_veins[mat_index]
seen_mat[mat_index] = true
if df.world.raws.inorganics[mat_index].flags[:METAL_ORE]
$ore_veins[mat_index] ||= []
$ore_veins[mat_index] << [block.map_pos.x, block.map_pos.y, block.map_pos.z]
end
end
}
}
df.onstatechange_register_once { |st|
if st == :MAP_LOADED
$ore_veins = nil # invalidate veins cache
true
end
}
$ore_veins
end
$ore_veins ||= find_all_ore_veins
if not target_ore or target_ore == 'help'
puts <<EOS
Scan the map to find one random tile of unmined ore.
It will center the game view on that tile and mark it for digging.
Only works with metal ores.
Usage:
locate_ore list list all existing vein materials (including mined ones)
locate_ore hematite find one tile of unmined hematite ore
locate_ore iron find one tile of unmined ore you can smelt into iron
EOS
elsif target_ore and mats = $ore_veins.keys.find_all { |k|
ino = df.world.raws.inorganics[k]
ino.id =~ /#{target_ore}/i or ino.metal_ore.mat_index.find { |m|
df.world.raws.inorganics[m].id =~ /#{target_ore}/i
}
} and not mats.empty?
pos = nil
dxs = (0..15).sort_by { rand }
dys = (0..15).sort_by { rand }
if found_mat = mats.sort_by { rand }.find { |mat|
$ore_veins[mat].sort_by { rand }.find { |bx, by, bz|
dys.find { |dy|
dxs.find { |dx|
tile = df.map_tile_at(bx+dx, by+dy, bz)
if tile.tilemat == :MINERAL and tile.designation.dig == :No and tile.shape == :WALL and
tile.mat_index_vein == mat and
# ignore map borders
bx+dx > 0 and bx+dx < df.world.map.x_count-1 and by+dy > 0 and by+dy < df.world.map.y_count-1
pos = [bx+dx, by+dy, bz]
end
}
}
}
}
df.center_viewscreen(*pos)
df.map_tile_at(*pos).dig
puts "Here is some #{df.world.raws.inorganics[found_mat].id}"
else
puts "Cannot find unmined #{mats.map { |mat| df.world.raws.inorganics[mat].id }.join(', ')}"
end
else
puts "Available ores:", $ore_veins.sort_by { |mat, pos| pos.length }.map { |mat, pos|
ore = df.world.raws.inorganics[mat]
metals = ore.metal_ore.mat_index.map { |m| df.world.raws.inorganics[m] }
' ' + ore.id.downcase + ' (' + metals.map { |m| m.id.downcase }.join(', ') + ')'
}
end

@ -1,56 +0,0 @@
# create an infinite magma source at the cursor
$magma_sources ||= []
case $script_args[0]
when 'here'
$magma_onupdate ||= df.onupdate_register('magmasource', 12) {
# called every 12 game ticks (100x a dwarf day)
if $magma_sources.empty?
df.onupdate_unregister($magma_onupdate)
$magma_onupdate = nil
end
$magma_sources.each { |x, y, z|
if tile = df.map_tile_at(x, y, z) and tile.shape_passableflow
des = tile.designation
tile.spawn_magma(des.flow_size + 1) if des.flow_size < 7
end
}
}
if df.cursor.x != -30000
if tile = df.map_tile_at(df.cursor)
if tile.shape_passableflow
$magma_sources << [df.cursor.x, df.cursor.y, df.cursor.z]
else
puts "Impassable tile: I'm afraid I can't do that, Dave"
end
else
puts "Unallocated map block - build something here first"
end
else
puts "Please put the game cursor where you want a magma source"
end
when 'delete-here'
$magma_sources.delete [df.cursor.x, df.cursor.y, df.cursor.z]
when 'stop'
$magma_sources.clear
else
puts <<EOS
Creates a new infinite magma source at the cursor.
Arguments:
here - create a new source at the current cursor position
(call multiple times for higher flow)
delete-here - delete the source under the cursor
stop - delete all created magma sources
EOS
if $magma_sources.first
puts '', 'Current magma sources:', $magma_sources.map { |s| " #{s.inspect}" }
end
end

@ -0,0 +1,4 @@
# run many dfhack commands separated by ;
# ex: multicmd locate-ore IRON ; digv ; digcircle 16
$script_args.join(' ').split(/\s*;\s*/).each { |cmd| df.dfhack_run cmd }

@ -0,0 +1,83 @@
# create an infinite magma/water source/drain at the cursor
$sources ||= []
cur_source = {
:liquid => 'water',
:amount => 7,
:pos => [df.cursor.x, df.cursor.y, df.cursor.z]
}
cmd = 'help'
$script_args.each { |a|
case a.downcase
when 'water', 'magma'
cur_source[:liquid] = a.downcase
when /^\d+$/
cur_source[:amount] = a.to_i
when 'add', 'del', 'delete', 'clear', 'help', 'list'
cmd = a.downcase
else
puts "source: unhandled argument #{a}"
end
}
case cmd
when 'add'
$sources_onupdate ||= df.onupdate_register('sources', 12) {
# called every 12 game ticks (100x a dwarf day)
$sources.each { |s|
if tile = df.map_tile_at(*s[:pos]) and tile.shape_passableflow
# XXX does not check current liquid_type
des = tile.designation
cur = des.flow_size
if cur != s[:amount]
tile.spawn_liquid((cur > s[:amount] ? cur-1 : cur+1), s[:liquid] == 'magma')
end
end
}
if $sources.empty?
df.onupdate_unregister($sources_onupdate)
$sources_onupdate = nil
end
}
if cur_source[:pos][0] >= 0
if tile = df.map_tile_at(*cur_source[:pos])
if tile.shape_passableflow
$sources << cur_source
else
puts "Impassable tile: I'm afraid I can't do that, Dave"
end
else
puts "Unallocated map block - build something here first"
end
else
puts "Please put the game cursor where you want a source"
end
when 'del', 'delete'
$sources.delete_if { |s| s[:pos] == cur_source[:pos] }
when 'clear'
$sources.clear
when 'list'
puts "Source list:", $sources.map { |s|
" #{s[:pos].inspect} #{s[:liquid]} #{s[:amount]}"
}
puts "Current cursor pos: #{[df.cursor.x, df.cursor.y, df.cursor.z].inspect}" if df.cursor.x >= 0
else
puts <<EOS
Creates a new infinite liquid source at the cursor.
Examples:
source add water - create a water source under cursor
source add water 0 - create a water drain
source add magma 5 - create a magma source, up to 5/7 deep
source delete - delete source under cursor
source clear - remove all sources
source list
EOS
end