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);

@ -1,176 +1,176 @@
class AutoFarm class AutoFarm
def initialize def initialize
@thresholds = Hash.new(50) @thresholds = Hash.new(50)
@lastcounts = Hash.new(0) @lastcounts = Hash.new(0)
end end
def setthreshold(id, v) def setthreshold(id, v)
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id } list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
if tok = df.match_rawname(id, list) if tok = df.match_rawname(id, list)
@thresholds[tok] = v.to_i @thresholds[tok] = v.to_i
else else
puts "No plant with id #{id}, try one of " + puts "No plant with id #{id}, try one of " +
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ') list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
end end
end end
def setdefault(v) def setdefault(v)
@thresholds.default = v.to_i @thresholds.default = v.to_i
end end
def is_plantable(plant) def is_plantable(plant)
has_seed = plant.flags[:SEED] has_seed = plant.flags[:SEED]
season = df.cur_season season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10 harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080 will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season] can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant can_plant
end end
def find_plantable_plants def find_plantable_plants
plantable = {} plantable = {}
counts = Hash.new(0) counts = Hash.new(0)
df.world.items.other[:SEEDS].each { |i| df.world.items.other[:SEEDS].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction && !i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact) !i.flags.artifact)
counts[i.mat_index] += i.stack_size counts[i.mat_index] += i.stack_size
end end
} }
counts.keys.each { |i| counts.keys.each { |i|
if df.ui.tasks.known_plants[i] if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i] plant = df.world.raws.plants.all[i]
if is_plantable(plant) if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0) plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0) plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end end
end end
} }
return plantable return plantable
end end
def set_farms(plants, farms) def set_farms(plants, farms)
return if farms.length == 0 return if farms.length == 0
if plants.length == 0 if plants.length == 0
plants = [-1] plants = [-1]
end end
season = df.cur_season season = df.cur_season
farms.each_with_index { |f, idx| farms.each_with_index { |f, idx|
f.plant_id[season] = plants[idx % plants.length] f.plant_id[season] = plants[idx % plants.length]
} }
end end
def process def process
plantable = find_plantable_plants plantable = find_plantable_plants
@lastcounts = Hash.new(0) @lastcounts = Hash.new(0)
df.world.items.other[:PLANT].each { |i| df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction && !i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index)) !i.flags.artifact && plantable.has_key?(i.mat_index))
id = df.world.raws.plants.all[i.mat_index].id id = df.world.raws.plants.all[i.mat_index].id
@lastcounts[id] += i.stack_size @lastcounts[id] += i.stack_size
end end
} }
return unless @running return unless @running
plants_s = [] plants_s = []
plants_u = [] plants_u = []
plantable.each_key { |k| plantable.each_key { |k|
plant = df.world.raws.plants.all[k] plant = df.world.raws.plants.all[k]
if (@lastcounts[plant.id] < @thresholds[plant.id]) if (@lastcounts[plant.id] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground plants_u.push(k) if plantable[k] == :Underground
end end
} }
farms_s = [] farms_s = []
farms_u = [] farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f| df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists) if (f.flags.exists)
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
farms_s.push(f) unless underground farms_s.push(f) unless underground
farms_u.push(f) if underground farms_u.push(f) if underground
end end
} }
set_farms(plants_s, farms_s) set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u) set_farms(plants_u, farms_u)
end end
def start def start
return if @running return if @running
@onupdate = df.onupdate_register('autofarm', 1200) { process } @onupdate = df.onupdate_register('autofarm', 1200) { process }
@running = true @running = true
end end
def stop def stop
df.onupdate_unregister(@onupdate) df.onupdate_unregister(@onupdate)
@running = false @running = false
end end
def status def status
stat = @running ? "Running." : "Stopped." stat = @running ? "Running." : "Stopped."
@lastcounts.each { |k,v| @lastcounts.each { |k,v|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}" stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
} }
@thresholds.each { |k,v| @thresholds.each { |k,v|
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k) stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
} }
stat << "\nDefault: #{@thresholds.default}" stat << "\nDefault: #{@thresholds.default}"
stat stat
end end
end 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
when 'end', 'stop', 'disable' when 'end', 'stop', 'disable'
$AutoFarm.stop $AutoFarm.stop
puts 'Stopped.' puts 'Stopped.'
when 'default' when 'default'
$AutoFarm.setdefault($script_args[1]) $AutoFarm.setdefault($script_args[1])
when 'threshold' when 'threshold'
t = $script_args[1] t = $script_args[1]
$script_args[2..-1].each {|i| $script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t) $AutoFarm.setthreshold(i, t)
} }
when 'delete' when 'delete'
$AutoFarm.stop $AutoFarm.stop
$AutoFarm = nil $AutoFarm = nil
when 'help', '?' when 'help', '?'
puts <<EOS puts <<EOS
Automatically handle crop selection in farm plots based on current plant stocks. Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold. Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots. Selected crops are dispatched on all farmplots.
Usage: Usage:
autofarm start autofarm start
autofarm default 30 autofarm default 30
autofarm threshold 150 helmet_plump tail_pig autofarm threshold 150 helmet_plump tail_pig
EOS EOS
else else
$AutoFarm.process $AutoFarm.process
puts $AutoFarm.status puts $AutoFarm.status
end end

@ -1,58 +1,58 @@
class AutoUnsuspend class AutoUnsuspend
def initialize def initialize
end end
def process def process
return false unless @running return false unless @running
joblist = df.world.job_list.next joblist = df.world.job_list.next
count = 0 count = 0
while joblist while joblist
job = joblist.item job = joblist.item
joblist = joblist.next joblist = joblist.next
if job.job_type == :ConstructBuilding if job.job_type == :ConstructBuilding
if (job.flags.suspend) if (job.flags.suspend)
item = job.items[0].item item = job.items[0].item
job.flags.suspend = false job.flags.suspend = false
count += 1 count += 1
end end
end end
end end
puts "Unsuspended #{count} job(s)." unless count == 0 puts "Unsuspended #{count} job(s)." unless count == 0
end end
def start def start
@onupdate = df.onupdate_register('autounsuspend', 5) { process } @onupdate = df.onupdate_register('autounsuspend', 5) { process }
@running = true @running = true
end end
def stop def stop
df.onupdate_unregister(@onupdate) df.onupdate_unregister(@onupdate)
@running = false @running = false
end end
def status def status
@running ? 'Running.' : 'Stopped.' @running ? 'Running.' : 'Stopped.'
end end
end end
case $script_args[0] case $script_args[0]
when 'start' when 'start'
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend $AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
$AutoUnsuspend.start $AutoUnsuspend.start
when 'end', 'stop' when 'end', 'stop'
$AutoUnsuspend.stop $AutoUnsuspend.stop
else else
if $AutoUnsuspend if $AutoUnsuspend
puts $AutoUnsuspend.status puts $AutoUnsuspend.status
else else
puts 'Not loaded.' puts 'Not loaded.'
end end
end end

@ -1,177 +1,177 @@
# create first necessity items under cursor # create first necessity items under cursor
category = $script_args[0] || 'help' category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list' mat_raw = $script_args[1] || 'list'
count = $script_args[2] count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help' category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help' if category == 'help'
puts <<EOS puts <<EOS
Create first necessity items under the cursor. Create first necessity items under the cursor.
Usage: Usage:
create-items [category] [raws token] [number] create-items [category] [raws token] [number]
Item categories: Item categories:
bars, boulders, plants, logs, webs, anvils bars, boulders, plants, logs, webs, anvils
Raw token: Raw token:
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category) (the missing part is autocompleted depending on the item category)
Use 'list' to show all possibilities Use 'list' to show all possibilities
Exemples: Exemples:
create-items boulders hematite 30 create-items boulders hematite 30
create-items bars CREATURE_MAT:CAT:SOAP 10 create-items bars CREATURE_MAT:CAT:SOAP 10
create-items web cave_giant create-items web cave_giant
create-items plants list create-items plants list
EOS EOS
throw :script_finished throw :script_finished
elsif mat_raw == 'list' elsif mat_raw == 'list'
# allowed with no cursor # allowed with no cursor
elsif df.cursor.x == -30000 elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere" puts "Please place the game cursor somewhere"
throw :script_finished throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor)) elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !" puts "Error: unallocated map block !"
throw :script_finished throw :script_finished
elsif !maptile.shape_passablehigh elsif !maptile.shape_passablehigh
puts "Error: impassible tile !" puts "Error: impassible tile !"
throw :script_finished throw :script_finished
end end
def match_list(tok, list) def match_list(tok, list)
if tok != 'list' if tok != 'list'
tok = df.match_rawname(tok, list) tok = df.match_rawname(tok, list)
if not tok if not tok
puts "Invalid raws token, use one in:" puts "Invalid raws token, use one in:"
tok = 'list' tok = 'list'
end end
end end
if tok == 'list' if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ') puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished throw :script_finished
end end
tok tok
end end
case category case category
when 'bars' when 'bars'
# create metal bar, eg createbar INORGANIC:IRON # create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino| list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL] ino.material.flags[:IS_METAL]
}.map { |ino| ino.id } }.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}" mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw puts mat_raw
end end
customize = lambda { |item| customize = lambda { |item|
item.dimension = 150 item.dimension = 150
item.subtype = -1 item.subtype = -1
} }
when 'boulders' when 'boulders'
cls = DFHack::ItemBoulderst cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino| list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE] ino.material.flags[:IS_STONE]
}.map { |ino| ino.id } }.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}" mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw puts mat_raw
end end
when 'plants' when 'plants'
cls = DFHack::ItemPlantst cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt| list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' } plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id } }.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL" mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw puts mat_raw
end end
when 'logs' when 'logs'
cls = DFHack::ItemWoodst cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt| list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' } plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id } }.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD" mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw puts mat_raw
end end
when 'webs' when 'webs'
cls = DFHack::ItemThreadst cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre| list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' } cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id } }.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK" mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw puts mat_raw
end end
count ||= 1 count ||= 1
customize = lambda { |item| customize = lambda { |item|
item.flags.spider_web = true item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS) item.dimension = 15000 # XXX may depend on creature (this is for GCS)
} }
when 'anvils' when 'anvils'
cls = DFHack::ItemAnvilst cls = DFHack::ItemAnvilst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino| list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL] ino.material.flags[:IS_METAL]
}.map { |ino| ino.id } }.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}" mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw puts mat_raw
end end
count ||= 1 count ||= 1
end end
mat = df.decode_mat mat_raw mat = df.decode_mat mat_raw
count ||= 20 count ||= 20
count.to_i.times { count.to_i.times {
item = cls.cpp_new item = cls.cpp_new
item.id = df.item_next_id item.id = df.item_next_id
item.stack_size = 1 item.stack_size = 1
item.mat_type = mat.mat_type item.mat_type = mat.mat_type
item.mat_index = mat.mat_index item.mat_index = mat.mat_index
customize[item] if customize customize[item] if customize
df.item_next_id += 1 df.item_next_id += 1
item.categorize(true) item.categorize(true)
df.world.items.all << item df.world.items.all << item
item.pos = df.cursor item.pos = df.cursor
item.flags.on_ground = true item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true df.map_tile_at.occupancy.item = true
} }
# move game view, so that the ui menu updates # move game view, so that the ui menu updates
if df.cursor.z > 5 if df.cursor.z > 5
df.curview.feed_keys(:CURSOR_DOWN_Z) df.curview.feed_keys(:CURSOR_DOWN_Z)
df.curview.feed_keys(:CURSOR_UP_Z) df.curview.feed_keys(:CURSOR_UP_Z)
else else
df.curview.feed_keys(:CURSOR_UP_Z) df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z) df.curview.feed_keys(:CURSOR_DOWN_Z)
end end

@ -1,67 +1,67 @@
# show death cause of a creature # show death cause of a creature
def display_death_event(e) def display_death_event(e)
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}" str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
str << " (cause: #{e.death_cause.to_s.downcase})," str << " (cause: #{e.death_cause.to_s.downcase}),"
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON 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 str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
puts str.chomp(',') + '.' puts str.chomp(',') + '.'
end end
def display_death_unit(u) def display_death_unit(u)
death_info = u.counters.death_tg death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}" str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name str << " #{u.name}" if u.name.has_name
str << " died" str << " died"
str << " in year #{death_info.event_year}" if death_info str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1 str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.' puts str.chomp(',') + '.'
end end
item = df.item_find(:selected) item = df.item_find(:selected)
unit = df.unit_find(:selected) unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent) if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end end
if item and item.kind_of?(DFHack::ItemBodyComponent) if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id hf = item.hist_figure_id
elsif unit elsif unit
hf = unit.hist_figure_id hf = unit.hist_figure_id
end end
if not hf if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1 elsif hf == -1
if unit ||= item.unit_tg if unit ||= item.unit_tg
display_death_unit(unit) display_death_unit(unit)
else else
puts "Not a historical figure, cannot death find info" puts "Not a historical figure, cannot death find info"
end end
else else
histfig = df.world.history.figures.binsearch(hf) histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !" puts "#{unit.name} is not dead yet !"
else else
events = df.world.history.events events = df.world.history.events
(0...events.length).reverse_each { |i| (0...events.length).reverse_each { |i|
e = events[i] e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e) display_death_event(e)
break break
end end
} }
end end
end end

@ -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 }

@ -1,38 +1,38 @@
# designate an area for digging according to a plan in csv format # designate an area for digging according to a plan in csv format
raise "usage: digfort <plan filename>" if not $script_args[0] raise "usage: digfort <plan filename>" if not $script_args[0]
planfile = File.read($script_args[0]) planfile = File.read($script_args[0])
if df.cursor.x == -30000 if df.cursor.x == -30000
raise "place the game cursor to the top-left corner of the design" raise "place the game cursor to the top-left corner of the design"
end end
tiles = planfile.lines.map { |l| tiles = planfile.lines.map { |l|
l.sub(/#.*/, '').split(';').map { |t| t.strip } l.sub(/#.*/, '').split(';').map { |t| t.strip }
} }
x = x0 = df.cursor.x x = x0 = df.cursor.x
y = df.cursor.y y = df.cursor.y
z = df.cursor.z z = df.cursor.z
tiles.each { |line| tiles.each { |line|
next if line.empty? or line == [''] next if line.empty? or line == ['']
line.each { |tile| line.each { |tile|
t = df.map_tile_at(x, y, z) t = df.map_tile_at(x, y, z)
s = t.shape_basic s = t.shape_basic
case tile case tile
when 'd'; t.dig(:Default) if s == :Wall when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No) when 'x'; t.dig(:No)
end end
x += 1 x += 1
} }
x = x0 x = x0
y += 1 y += 1
} }
puts 'done' puts 'done'

@ -1,11 +1,13 @@
# remove all aquifers from the map # remove all aquifers from the map
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|
end bz.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
} }
end
puts "cleared #{count} map blocks" }
puts "cleared #{count} aquifer#{'s' if count > 1}"

@ -1,112 +1,135 @@
# exterminate creatures # exterminate creatures
# race = name of the race to eradicate, use 'him' to target only the selected creature # race = name of the race to eradicate, use 'him' to target only the selected creature
# use 'undead' to target all undeads # use 'undead' to target all undeads
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]
checkunit = lambda { |u|
(u.body.blood_count != 0 or u.body.blood_max == 0) and case kill_by
not u.flags1.dead and when 'magma'
not u.flags1.caged and not u.flags1.chained and slain = 'burning'
#not u.flags1.hidden_in_ambush and when 'slaughter', 'butcher'
not df.map_designation_at(u).hidden slain = 'marked for butcher'
} when nil
slain = 'slain'
slayit = lambda { |u| else
if not magma race = 'help'
# just make them drop dead end
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of. checkunit = lambda { |u|
u.animal.vanish_countdown = 2 (u.body.blood_count != 0 or u.body.blood_max == 0) and
else not u.flags1.dead and
# it's getting hot around here not u.flags1.caged and not u.flags1.chained and
# !!WARNING!! do not call on a magma-safe creature #not u.flags1.hidden_in_ambush and
ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) { not df.map_designation_at(u).hidden
if u.flags1.dead }
df.onupdate_unregister(ouh)
else slayit = lambda { |u|
x, y, z = u.pos.x, u.pos.y, u.pos.z case kill_by
z += 1 while tile = df.map_tile_at(x, y, z+1) and when 'magma'
tile.shape_passableflow and tile.shape_passablelow # it's getting hot around here
df.map_tile_at(x, y, z).spawn_magma(7) # !!WARNING!! do not call on a magma-safe creature
end ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
} if u.flags1.dead
end df.onupdate_unregister(ouh)
} else
x, y, z = u.pos.x, u.pos.y, u.pos.z
all_races = Hash.new(0) z += 1 while tile = df.map_tile_at(x, y, z+1) and
tile.shape_passableflow and tile.shape_passablelow
df.world.units.active.map { |u| df.map_tile_at(x, y, z).spawn_magma(7)
if checkunit[u] end
if (u.enemy.undead or }
(u.curse.add_tags1.OPPOSED_TO_LIFE and not when 'butcher', 'slaughter'
u.curse.rem_tags1.OPPOSED_TO_LIFE)) # mark for slaughter at butcher's shop
all_races['Undead'] += 1 u.flags2.slaughter = true
else else
all_races[u.race_tg.creature_id] += 1 # just make them drop dead
end u.body.blood_count = 0
end # some races dont mind having no blood, ensure they are still taken care of.
} u.animal.vanish_countdown = 2
end
case race }
when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } all_races = Hash.new(0)
when 'help', '?' df.world.units.active.map { |u|
puts <<EOS if checkunit[u]
Kills all creatures of a given race. if (u.enemy.undead or
With no argument, lists possible targets with their head count. (u.curse.add_tags1.OPPOSED_TO_LIFE and not
With the special argument 'him' or 'her', kill only the currently selected creature. u.curse.rem_tags1.OPPOSED_TO_LIFE))
With the special argument 'undead', kill all undead creatures/thralls. all_races['Undead'] += 1
else
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke. all_races[u.race_tg.creature_id] += 1
end
The special final argument 'magma' will make magma rain on the targets instead. end
}
Ex: exterminate gob
exterminate elve magma case race
exterminate him when nil
EOS all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
when 'him', 'her' when 'help', '?'
if him = df.unit_find puts <<EOS
slayit[him] Kills all creatures of a given race.
else With no argument, lists possible targets with their head count.
puts "Select a target ingame" With the special argument 'him' or 'her', kill only the currently selected creature.
end With the special argument 'undead', kill all undead creatures/thralls.
when /^undead/i The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
count = 0
df.world.units.active.each { |u| The special final argument 'magma' will make magma rain on the targets instead.
if (u.enemy.undead or The special final argument 'butcher' will mark the targets for butchering instead.
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and Ex: exterminate gob
checkunit[u] exterminate elve magma
slayit[u] exterminate him
count += 1 exterminate pig butcher
end EOS
}
puts "slain #{count} undeads" when 'him', 'her', 'it', 'that'
if him = df.unit_find
else case him.race_tg.caste[him.caste].gender
raw_race = df.match_rawname(race, all_races.keys) when 0; puts 'its a she !' if race != 'her'
if not raw_race when 1; puts 'its a he !' if race != 'him'
puts "Invalid race, use one of #{all_races.keys.sort.join(' ')}" else; puts 'its an it !' if race != 'it' and race != 'that'
throw :script_finished end
end slayit[him]
else
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race } puts "Select a target ingame"
end
count = 0
df.world.units.active.each { |u| when /^undead/i
if u.race == race_nr and checkunit[u] count = 0
slayit[u] df.world.units.active.each { |u|
count += 1 if (u.enemy.undead or
end (u.curse.add_tags1.OPPOSED_TO_LIFE and not
} u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
puts "slain #{count} #{raw_race}" checkunit[u]
slayit[u]
end count += 1
end
}
puts "#{slain} #{count} undeads"
else
raw_race = df.match_rawname(race, all_races.keys)
if not raw_race
puts "Invalid race, use one of #{all_races.keys.sort.join(' ')}"
throw :script_finished
end
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
count = 0
df.world.units.active.each { |u|
if u.race == race_nr and checkunit[u]
slayit[u]
count += 1
end
}
puts "#{slain} #{count} #{raw_race}"
end

@ -1,64 +1,64 @@
# script to fix loyalty cascade, when you order your militia to kill friendly units # script to fix loyalty cascade, when you order your militia to kill friendly units
def fixunit(unit) def fixunit(unit)
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
links = unit.hist_figure_tg.entity_links links = unit.hist_figure_tg.entity_links
fixed = false fixed = false
# check if the unit is a civ renegade # check if the unit is a civ renegade
if i1 = links.index { |l| if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.civ_id l.entity_id == df.ui.civ_id
} and i2 = links.index { |l| } and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.civ_id l.entity_id == df.ui.civ_id
} }
fixed = true fixed = true
i1, i2 = i2, i1 if i1 > i2 i1, i2 = i2, i1 if i1 > i2
links.delete_at i2 links.delete_at i2
links.delete_at i1 links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100) links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again" df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
end end
# check if the unit is a group renegade # check if the unit is a group renegade
if i1 = links.index { |l| if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.group_id l.entity_id == df.ui.group_id
} and i2 = links.index { |l| } and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.group_id l.entity_id == df.ui.group_id
} }
fixed = true fixed = true
i1, i2 = i2, i1 if i1 > i2 i1, i2 = i2, i1 if i1 > i2
links.delete_at i2 links.delete_at i2
links.delete_at i1 links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100) links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again" df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
end end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed) # fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
if fixed and unit.enemy.enemy_status_slot != -1 if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1 unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache cache = df.world.enemy_status_cache
cache.slot_used[i] = false cache.slot_used[i] = false
cache.rel_map[i].map! { -1 } cache.rel_map[i].map! { -1 }
cache.rel_map.each { |a| a[i] = -1 } cache.rel_map.each { |a| a[i] = -1 }
cache.next_slot = i if cache.next_slot > i cache.next_slot = i if cache.next_slot > i
end end
# return true if we actually fixed the unit # return true if we actually fixed the unit
fixed fixed
end end
count = 0 count = 0
df.unit_citizens.each { |u| df.unit_citizens.each { |u|
count += 1 if fixunit(u) count += 1 if fixunit(u)
} }
if count > 0 if count > 0
puts "loyalty cascade fixed (#{count} dwarves)" puts "loyalty cascade fixed (#{count} dwarves)"
else else
puts "no loyalty cascade found" puts "no loyalty cascade found"
end end

@ -1,20 +1,25 @@
# 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|
# for all doors # for all doors
next if bld._rtti_classname != :building_doorst next if bld._rtti_classname != :building_doorst
# check if it is open # check if it is open
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
puts "unstuck #{count} doors" 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"

@ -1,49 +1,49 @@
# grow crops in farm plots. ex: growcrops helmet_plump 20 # grow crops in farm plots. ex: growcrops helmet_plump 20
material = $script_args[0] material = $script_args[0]
count_max = $script_args[1].to_i count_max = $script_args[1].to_i
count_max = 100 if count_max == 0 count_max = 100 if count_max == 0
# cache information from the raws # cache information from the raws
@raws_plant_name ||= {} @raws_plant_name ||= {}
@raws_plant_growdur ||= {} @raws_plant_growdur ||= {}
if @raws_plant_name.empty? if @raws_plant_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx| df.world.raws.plants.all.each_with_index { |p, idx|
@raws_plant_name[idx] = p.id @raws_plant_name[idx] = p.id
@raws_plant_growdur[idx] = p.growdur @raws_plant_growdur[idx] = p.growdur
} }
end end
inventory = Hash.new(0) inventory = Hash.new(0)
df.world.items.other[:SEEDS].each { |seed| df.world.items.other[:SEEDS].each { |seed|
next if not seed.flags.in_building next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
inventory[seed.mat_index] += 1 inventory[seed.mat_index] += 1
} }
if !material or material == 'help' or material == 'list' if !material or material == 'help' or material == 'list'
# show a list of available crop types # show a list of available crop types
inventory.sort_by { |mat, c| c }.each { |mat, c| inventory.sort_by { |mat, c| c }.each { |mat, c|
name = df.world.raws.plants.all[mat].id name = df.world.raws.plants.all[mat].id
puts " #{name} #{c}" puts " #{name} #{c}"
} }
else else
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] }) mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
unless wantmat = @raws_plant_name.index(mat) unless wantmat = @raws_plant_name.index(mat)
raise "invalid plant material #{material}" raise "invalid plant material #{material}"
end end
count = 0 count = 0
df.world.items.other[:SEEDS].each { |seed| df.world.items.other[:SEEDS].each { |seed|
next if seed.mat_index != wantmat next if seed.mat_index != wantmat
next if not seed.flags.in_building next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
seed.grow_counter = @raws_plant_growdur[seed.mat_index] seed.grow_counter = @raws_plant_growdur[seed.mat_index]
count += 1 count += 1
} }
puts "Grown #{count} #{mat}" puts "Grown #{count} #{mat}"
end end

@ -1,120 +1,120 @@
# control your levers from the dfhack console # control your levers from the dfhack console
def lever_pull_job(bld) def lever_pull_job(bld)
ref = DFHack::GeneralRefBuildingHolderst.cpp_new ref = DFHack::GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id ref.building_id = bld.id
job = DFHack::Job.cpp_new job = DFHack::Job.cpp_new
job.job_type = :PullLever job.job_type = :PullLever
job.pos = [bld.centerx, bld.centery, bld.z] job.pos = [bld.centerx, bld.centery, bld.z]
job.general_refs << ref job.general_refs << ref
bld.jobs << job bld.jobs << job
df.job_link job df.job_link job
puts lever_descr(bld) puts lever_descr(bld)
end end
def lever_pull_cheat(bld) def lever_pull_cheat(bld)
bld.linked_mechanisms.each { |i| bld.linked_mechanisms.each { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
r.building_tg.setTriggerState(bld.state) r.building_tg.setTriggerState(bld.state)
} }
} }
bld.state = (bld.state == 0 ? 1 : 0) bld.state = (bld.state == 0 ? 1 : 0)
puts lever_descr(bld) puts lever_descr(bld)
end end
def lever_descr(bld, idx=nil) def lever_descr(bld, idx=nil)
ret = [] ret = []
# lever description # lever description
descr = '' descr = ''
descr << "#{idx}: " if idx descr << "#{idx}: " if idx
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
bld.jobs.each { |j| bld.jobs.each { |j|
if j.job_type == :PullLever if j.job_type == :PullLever
flags = '' flags = ''
flags << ', repeat' if j.flags.repeat flags << ', repeat' if j.flags.repeat
flags << ', suspended' if j.flags.suspend flags << ', suspended' if j.flags.suspend
descr << " (pull order#{flags})" descr << " (pull order#{flags})"
end end
} }
bld.linked_mechanisms.map { |i| bld.linked_mechanisms.map { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
}.flatten.each { |r| }.flatten.each { |r|
# linked building description # linked building description
tg = r.building_tg tg = r.building_tg
state = '' state = ''
if tg.respond_to?(:gate_flags) if tg.respond_to?(:gate_flags)
state << (tg.gate_flags.closed ? 'closed' : 'opened') state << (tg.gate_flags.closed ? 'closed' : 'opened')
state << ", closing (#{tg.timer})" if tg.gate_flags.closing state << ", closing (#{tg.timer})" if tg.gate_flags.closing
state << ", opening (#{tg.timer})" if tg.gate_flags.opening state << ", opening (#{tg.timer})" if tg.gate_flags.opening
end end
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
# indent other links # indent other links
descr = descr.gsub(/./, ' ') descr = descr.gsub(/./, ' ')
} }
ret << descr if ret.empty? ret << descr if ret.empty?
ret ret
end end
def lever_list def lever_list
@lever_list = [] @lever_list = []
df.world.buildings.other[:TRAP].find_all { |bld| df.world.buildings.other[:TRAP].find_all { |bld|
bld.trap_type == :Lever bld.trap_type == :Lever
}.sort_by { |bld| bld.id }.each { |bld| }.sort_by { |bld| bld.id }.each { |bld|
puts lever_descr(bld, @lever_list.length) puts lever_descr(bld, @lever_list.length)
@lever_list << bld.id @lever_list << bld.id
} }
end end
case $script_args[0] case $script_args[0]
when 'pull' when 'pull'
cheat = $script_args.delete('--cheat') || $script_args.delete('--now') cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
id = $script_args[1].to_i id = $script_args[1].to_i
id = @lever_list[id] || id id = @lever_list[id] || id
bld = df.building_find(id) bld = df.building_find(id)
raise 'invalid lever id' if not bld raise 'invalid lever id' if not bld
if cheat if cheat
lever_pull_cheat(bld) lever_pull_cheat(bld)
else else
lever_pull_job(bld) lever_pull_job(bld)
end end
when 'list' when 'list'
lever_list lever_list
when /^\d+$/ when /^\d+$/
id = $script_args[0].to_i id = $script_args[0].to_i
id = @lever_list[id] || id id = @lever_list[id] || id
bld = df.building_find(id) bld = df.building_find(id)
raise 'invalid lever id' if not bld raise 'invalid lever id' if not bld
puts lever_descr(bld) puts lever_descr(bld)
else else
puts <<EOS puts <<EOS
Lever control from the dfhack console Lever control from the dfhack console
Usage: Usage:
lever list lever list
shows the list of levers in the fortress, with their id and links shows the list of levers in the fortress, with their id and links
lever pull 42 lever pull 42
order the dwarves to pull lever 42 order the dwarves to pull lever 42
lever pull 42 --cheat lever pull 42 --cheat
magically pull lever 42 immediately magically pull lever 42 immediately
EOS EOS
end end

@ -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

@ -1,40 +1,40 @@
# pit all caged creatures in a zone # pit all caged creatures in a zone
case $script_args[0] case $script_args[0]
when '?', 'help' when '?', 'help'
puts <<EOS puts <<EOS
Run this script with the cursor on top of a pit/pond activity zone, or with a zone identifier as argument. Run this script with the cursor on top of a pit/pond activity zone, or with a zone identifier as argument.
It will mark all caged creatures on tiles covered by the zone to be dumped. It will mark all caged creatures on tiles covered by the zone to be dumped.
Works best with an animal stockpile on top of the pit/pond zone. Works best with an animal stockpile on top of the pit/pond zone.
EOS EOS
throw :script_finished throw :script_finished
when /(\d+)/ when /(\d+)/
nr = $1.to_i nr = $1.to_i
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone| zone.zone_num == nr } bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone| zone.zone_num == nr }
else else
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone| bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone|
zone.zone_flags.pit_pond and zone.z == df.cursor.z and zone.zone_flags.pit_pond and zone.z == df.cursor.z and
zone.x1 <= df.cursor.x and zone.x2 >= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y zone.x1 <= df.cursor.x and zone.x2 >= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y
} }
end end
if not bld if not bld
puts "Please select a pit/pond zone" puts "Please select a pit/pond zone"
throw :script_finished throw :script_finished
end end
found = 0 found = 0
df.world.items.other[:CAGE].each { |cg| df.world.items.other[:CAGE].each { |cg|
next if not cg.flags.on_ground next if not cg.flags.on_ground
next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2 next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2
next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first
found += 1 found += 1
u = uref.unit_tg u = uref.unit_tg
puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}" puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}"
u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id) u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id)
bld.assigned_creature << u.id bld.assigned_creature << u.id
} }
puts "No creature available for pitting" if found == 0 puts "No creature available for pitting" if found == 0

@ -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 }

@ -1,43 +1,43 @@
# remove bad thoughts for the selected unit or the whole fort # remove bad thoughts for the selected unit or the whole fort
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n') dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
$script_args << 'all' if dry_run and $script_args.empty? $script_args << 'all' if dry_run and $script_args.empty?
seenbad = Hash.new(0) seenbad = Hash.new(0)
clear_mind = lambda { |u| clear_mind = lambda { |u|
u.status.recent_events.each { |e| u.status.recent_events.each { |e|
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-' next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
seenbad[e.type] += 1 seenbad[e.type] += 1
e.age = 0x1000_0000 unless dry_run e.age = 0x1000_0000 unless dry_run
} }
} }
summary = lambda { summary = lambda {
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt| seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
puts " #{thought} #{cnt}" puts " #{thought} #{cnt}"
} }
count = seenbad.values.inject(0) { |sum, cnt| sum+cnt } count = seenbad.values.inject(0) { |sum, cnt| sum+cnt }
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
} }
case $script_args[0] case $script_args[0]
when 'him' when 'him'
if u = df.unit_find if u = df.unit_find
clear_mind[u] clear_mind[u]
summary[] summary[]
else else
puts 'Please select a dwarf ingame' puts 'Please select a dwarf ingame'
end end
when 'all' when 'all'
df.unit_citizens.each { |uu| df.unit_citizens.each { |uu|
clear_mind[uu] clear_mind[uu]
} }
summary[] summary[]
else else
puts "Usage: removebadthoughts [--dry-run] <him|all>" puts "Usage: removebadthoughts [--dry-run] <him|all>"
end end

@ -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

@ -1,204 +1,204 @@
# mark stuff inside of cages for dumping. # mark stuff inside of cages for dumping.
def plural(nr, name) def plural(nr, name)
# '1 cage' / '4 cages' # '1 cage' / '4 cages'
"#{nr} #{name}#{'s' if nr > 1}" "#{nr} #{name}#{'s' if nr > 1}"
end end
def cage_dump_items(list) def cage_dump_items(list)
count = 0 count = 0
count_cage = 0 count_cage = 0
list.each { |cage| list.each { |cage|
pre_count = count pre_count = count
cage.general_refs.each { |ref| cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
next if ref.item_tg.flags.dump next if ref.item_tg.flags.dump
count += 1 count += 1
ref.item_tg.flags.dump = true ref.item_tg.flags.dump = true
} }
count_cage += 1 if pre_count != count count_cage += 1 if pre_count != count
} }
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end end
def cage_dump_armor(list) def cage_dump_armor(list)
count = 0 count = 0
count_cage = 0 count_cage = 0
list.each { |cage| list.each { |cage|
pre_count = count pre_count = count
cage.general_refs.each { |ref| cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it| ref.unit_tg.inventory.each { |it|
next if it.mode != :Worn next if it.mode != :Worn
next if it.item.flags.dump next if it.item.flags.dump
count += 1 count += 1
it.item.flags.dump = true it.item.flags.dump = true
} }
} }
count_cage += 1 if pre_count != count count_cage += 1 if pre_count != count
} }
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
end end
def cage_dump_weapons(list) def cage_dump_weapons(list)
count = 0 count = 0
count_cage = 0 count_cage = 0
list.each { |cage| list.each { |cage|
pre_count = count pre_count = count
cage.general_refs.each { |ref| cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it| ref.unit_tg.inventory.each { |it|
next if it.mode != :Weapon next if it.mode != :Weapon
next if it.item.flags.dump next if it.item.flags.dump
count += 1 count += 1
it.item.flags.dump = true it.item.flags.dump = true
} }
} }
count_cage += 1 if pre_count != count count_cage += 1 if pre_count != count
} }
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
end end
def cage_dump_all(list) def cage_dump_all(list)
count = 0 count = 0
count_cage = 0 count_cage = 0
list.each { |cage| list.each { |cage|
pre_count = count pre_count = count
cage.general_refs.each { |ref| cage.general_refs.each { |ref|
case ref case ref
when DFHack::GeneralRefContainsItemst when DFHack::GeneralRefContainsItemst
next if ref.item_tg.flags.dump next if ref.item_tg.flags.dump
count += 1 count += 1
ref.item_tg.flags.dump = true ref.item_tg.flags.dump = true
when DFHack::GeneralRefContainsUnitst when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it| ref.unit_tg.inventory.each { |it|
next if it.item.flags.dump next if it.item.flags.dump
count += 1 count += 1
it.item.flags.dump = true it.item.flags.dump = true
} }
end end
} }
count_cage += 1 if pre_count != count count_cage += 1 if pre_count != count
} }
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end end
def cage_dump_list(list) def cage_dump_list(list)
count_total = Hash.new(0) count_total = Hash.new(0)
empty_cages = 0 empty_cages = 0
list.each { |cage| list.each { |cage|
count = Hash.new(0) count = Hash.new(0)
cage.general_refs.each { |ref| cage.general_refs.each { |ref|
case ref case ref
when DFHack::GeneralRefContainsItemst when DFHack::GeneralRefContainsItemst
count[ref.item_tg._rtti_classname] += 1 count[ref.item_tg._rtti_classname] += 1
when DFHack::GeneralRefContainsUnitst when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it| ref.unit_tg.inventory.each { |it|
count[it.item._rtti_classname] += 1 count[it.item._rtti_classname] += 1
} }
# TODO vermin ? # TODO vermin ?
else else
puts "unhandled ref #{ref.inspect}" if $DEBUG puts "unhandled ref #{ref.inspect}" if $DEBUG
end end
} }
type = case cage type = case cage
when DFHack::ItemCagest; 'Cage' when DFHack::ItemCagest; 'Cage'
when DFHack::ItemAnimaltrapst; 'Animal trap' when DFHack::ItemAnimaltrapst; 'Animal trap'
else cage._rtti_classname else cage._rtti_classname
end end
if count.empty? if count.empty?
empty_cages += 1 empty_cages += 1
else else
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
end end
count.each { |k, v| count_total[k] += v } count.each { |k, v| count_total[k] += v }
} }
if list.length > 2 if list.length > 2
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
puts "with #{plural(empty_cages, 'empty cage')}" puts "with #{plural(empty_cages, 'empty cage')}"
end end
end end
# handle magic script arguments # handle magic script arguments
here_only = $script_args.delete 'here' here_only = $script_args.delete 'here'
if here_only if here_only
it = df.item_find it = df.item_find
list = [it] list = [it]
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) } list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
end end
if list.empty? if list.empty?
puts 'Please select a cage' puts 'Please select a cage'
throw :script_finished throw :script_finished
end end
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
list = [] list = []
ids.each { |id| ids.each { |id|
$script_args.delete id $script_args.delete id
if not it = df.item_find(id.to_i) if not it = df.item_find(id.to_i)
puts "Invalid item id #{id}" puts "Invalid item id #{id}"
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
puts "Item ##{id} is not a cage" puts "Item ##{id} is not a cage"
list << it list << it
else else
list << it list << it
end end
} }
if list.empty? if list.empty?
puts 'Please use a valid cage id' puts 'Please use a valid cage id'
throw :script_finished throw :script_finished
end end
else else
list = df.world.items.other[:ANY_CAGE_OR_TRAP] list = df.world.items.other[:ANY_CAGE_OR_TRAP]
end end
# act # act
case $script_args[0] case $script_args[0]
when /^it/i when /^it/i
cage_dump_items(list) cage_dump_items(list)
when /^arm/i when /^arm/i
cage_dump_armor(list) cage_dump_armor(list)
when /^wea/i when /^wea/i
cage_dump_weapons(list) cage_dump_weapons(list)
when 'all' when 'all'
cage_dump_all(list) cage_dump_all(list)
when 'list' when 'list'
cage_dump_list(list) cage_dump_list(list)
else else
puts <<EOS puts <<EOS
Marks items inside all cages for dumping. Marks items inside all cages for dumping.
Add 'here' to dump stuff only for selected cage. Add 'here' to dump stuff only for selected cage.
Add a cage id to dump stuff for this cage only. Add a cage id to dump stuff for this cage only.
See 'autodump' to actually dump stuff. See 'autodump' to actually dump stuff.
Usage: Usage:
stripcaged items stripcaged items
dump items directly in cages (eg seeds after training) dump items directly in cages (eg seeds after training)
stripcaged [armor|weapons] here stripcaged [armor|weapons] here
dump armor or weapons of caged creatures in selected cage dump armor or weapons of caged creatures in selected cage
stripcaged all 28 29 stripcaged all 28 29
dump every item in cage id 28 and 29, along with every item worn by creatures in there too dump every item in cage id 28 and 29, along with every item worn by creatures in there too
stripcaged list stripcaged list
show content of the cages show content of the cages
EOS EOS
end end

@ -1,61 +1,61 @@
# give super-dwarven speed to an unit # give super-dwarven speed to an unit
$superdwarf_onupdate ||= nil $superdwarf_onupdate ||= nil
$superdwarf_ids ||= [] $superdwarf_ids ||= []
case $script_args[0] case $script_args[0]
when 'add' when 'add'
if u = df.unit_find if u = df.unit_find
$superdwarf_ids |= [u.id] $superdwarf_ids |= [u.id]
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) { $superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
if $superdwarf_ids.empty? if $superdwarf_ids.empty?
df.onupdate_unregister($superdwarf_onupdate) df.onupdate_unregister($superdwarf_onupdate)
$superdwarf_onupdate = nil $superdwarf_onupdate = nil
else else
$superdwarf_ids.each { |id| $superdwarf_ids.each { |id|
if u = df.unit_find(id) and not u.flags1.dead if u = df.unit_find(id) and not u.flags1.dead
# faster walk/work # faster walk/work
if u.counters.job_counter > 0 if u.counters.job_counter > 0
u.counters.job_counter = 0 u.counters.job_counter = 0
end end
# no sleep # no sleep
if u.counters2.sleepiness_timer > 10000 if u.counters2.sleepiness_timer > 10000
u.counters2.sleepiness_timer = 1 u.counters2.sleepiness_timer = 1
end end
# no break # no break
if b = u.status.misc_traits.find { |t| t.id == :OnBreak } if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
b.value = 500_000 b.value = 500_000
end end
else else
$superdwarf_ids.delete id $superdwarf_ids.delete id
end end
} }
end end
} }
else else
puts "Select a creature using 'v'" puts "Select a creature using 'v'"
end end
when 'del' when 'del'
if u = df.unit_find if u = df.unit_find
$superdwarf_ids.delete u.id $superdwarf_ids.delete u.id
else else
puts "Select a creature using 'v'" puts "Select a creature using 'v'"
end end
when 'clear' when 'clear'
$superdwarf_ids.clear $superdwarf_ids.clear
when 'list' when 'list'
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
else else
puts "Usage:", puts "Usage:",
" - superdwarf add: give superspeed to currently selected creature", " - superdwarf add: give superspeed to currently selected creature",
" - superdwarf del: remove superspeed to current creature", " - superdwarf del: remove superspeed to current creature",
" - superdwarf clear: remove all superpowers", " - superdwarf clear: remove all superpowers",
" - superdwarf list: list super-dwarves" " - superdwarf list: list super-dwarves"
end end

@ -1,17 +1,17 @@
joblist = df.world.job_list.next joblist = df.world.job_list.next
count = 0 count = 0
while joblist while joblist
job = joblist.item job = joblist.item
joblist = joblist.next joblist = joblist.next
if job.job_type == :ConstructBuilding if job.job_type == :ConstructBuilding
if (job.flags.suspend && job.items && job.items[0]) if (job.flags.suspend && job.items && job.items[0])
item = job.items[0].item item = job.items[0].item
job.flags.suspend = false job.flags.suspend = false
count += 1 count += 1
end end
end end
end end
puts "Unsuspended #{count} job(s)." puts "Unsuspended #{count} job(s)."