diff --git a/Compile.rst b/Compile.rst index ce5d93ddf..ddff30aeb 100644 --- a/Compile.rst +++ b/Compile.rst @@ -63,6 +63,23 @@ extra options. You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui 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 ======== diff --git a/NEWS b/NEWS index 8b6cf82dd..4813ea265 100644 --- a/NEWS +++ b/NEWS @@ -5,8 +5,13 @@ DFHack future - restrictice - Restrict traffic on squares above visible ice. New scripts: - 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: - - 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 diff --git a/Readme.rst b/Readme.rst index 53ae24389..cd9101aab 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1857,7 +1857,7 @@ use in your farming plots. 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. -For exemple, to grow 40 plump helmet spawn: +For example, to grow 40 plump helmet spawn: :: 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, ``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, -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:: @@ -1917,24 +1918,32 @@ To purify all elves on the map with fire (may have side-effects):: exterminate elve magma -magmasource -=========== -Create an infinite magma source on a tile. +source +====== +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 -that tile receives 1 new unit of flowing magma. +This script registers a map tile as a liquid source, and every 12 game ticks +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 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 remove all placed sources, call ``magmasource stop``. +To add more than 1 unit everytime, call the command again on the same spot. -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 ======= @@ -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. However the ``list`` mode will only show 'normal' materials. -Exemples:: +Examples:: create-items boulders COAL_BITUMINOUS 12 create-items plant tail_pig @@ -2075,6 +2084,20 @@ Exemples:: create-items bar CREATURE:CAT:SOAP 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 ================= @@ -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 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 ======================= diff --git a/dfhack.init-example b/dfhack.init-example index ddf93de16..2dd380a01 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -137,6 +137,9 @@ tweak military-color-assigned # remove inverse dependency of squad training speed on unit list size and use more sparring tweak military-training +# enable autoSyndrome +autoSyndrome enable + ########### # Scripts # ########### @@ -149,23 +152,21 @@ fix/cloth-stockpile enable ####################################################### # Apply binary patches at runtime # -# # -# Commented out by default; enable the ones you want. # ####################################################### # Bug 5994 - items teleported when removing a construction -#binpatch apply deconstruct-teleport -#binpatch apply deconstruct-heapfall +binpatch apply deconstruct-teleport +binpatch apply deconstruct-heapfall # 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 -#binpatch apply custom-reagent-size +binpatch apply custom-reagent-size # 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 -#binpatch apply weaponrack-unassign -#binpatch apply armorstand-capacity +binpatch apply weaponrack-unassign +binpatch apply armorstand-capacity diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 3cee68589..43b0b47fb 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -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. */ -bool enabled = true; +bool enabled = false; 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); if ( workerIndex < 0 ) { out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 5e2bb9f89..bbfc15058 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -55,7 +55,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) "\n" "After you have set the options, the command called with no options\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" ); return CR_OK; diff --git a/plugins/ruby/README b/plugins/ruby/README index 9bead57d4..767844414 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -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 '# '). +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 --------------------- @@ -107,7 +120,7 @@ eg 'gob' for 'GOBLIN' or 'coal' for 'COAL_BITUMINOUS', hence the name. df.building_construct(bld, item_list) 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. -See buildings.rb/buildbed for an exemple. +See buildings.rb/buildbed for an example. df.each_tree(material) { |t| } 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 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 ----------------------- 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 -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. 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 @@ -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. -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) 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). It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb) -For exemple, +For example, diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 37b91d166..5378aaa6e 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -48,6 +48,8 @@ module DFHack raise "invalid building type #{type.inspect}" if not cls bld = cls.cpp_new 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 = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index e9500efa3..647a52849 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -149,7 +149,21 @@ module DFHack def vein # last vein wins 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 diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index ffd68bf1e..c8303128d 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -122,7 +122,8 @@ module DFHack _fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init } end 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) @_memaddr = nil # turn future segfaults in harmless ruby exceptions end @@ -642,7 +643,7 @@ module DFHack @_tg = tg end # XXX DF uses stl::deque, 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 ; "#" ; end end @@ -676,7 +677,7 @@ module DFHack def inspect out = "#' end diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 187b90e3d..69b9b15c9 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -41,6 +41,7 @@ static tthread::thread *r_thread; static int onupdate_active; static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1; static color_ostream_proxy *console_proxy; +static std::vector *dfhack_run_queue; DFHACK_PLUGIN("ruby") @@ -63,6 +64,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector ; + r_type = RB_INIT; // create the dedicated ruby thread @@ -84,6 +88,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector unlock(); delete m_mutex; + delete dfhack_run_queue; // dlclose libruby 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 DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) { + command_result ret; + // if dlopen failed if (!r_thread) return CR_FAILURE; @@ -160,14 +171,24 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c // debug only! // run ruby commands without locking the main thread // 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 { // wrap all ruby code inside a suspend block // if we dont do that and rely on ruby code doing it, we'll deadlock in // onupdate 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 ) @@ -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 static VALUE rb_dfhack_run(VALUE self, VALUE cmd) { - if (!r_console) // XXX - return Qnil; - std::string s; int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); s.assign(rb_string_value_ptr(&cmd), strlen); - - // allow the target command to suspend - // FIXME - //CoreSuspendClaimer suspend(true); - Core::getInstance().runCommand(*r_console, s); - + dfhack_run_queue->push_back(s); 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_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, "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_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index b8a6871e9..89b9ca703 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -1,176 +1,176 @@ -class AutoFarm - - def initialize - @thresholds = Hash.new(50) - @lastcounts = Hash.new(0) - end - - def setthreshold(id, v) - list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id } - if tok = df.match_rawname(id, list) - @thresholds[tok] = v.to_i - else - puts "No plant with id #{id}, try one of " + - list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ') - end - end - - def setdefault(v) - @thresholds.default = v.to_i - end - - def is_plantable(plant) - has_seed = plant.flags[:SEED] - season = df.cur_season - harvest = df.cur_season_tick + plant.growdur * 10 - will_finish = harvest < 10080 - can_plant = has_seed && plant.flags[season] - can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) - can_plant - end - - def find_plantable_plants - plantable = {} - counts = Hash.new(0) - - df.world.items.other[:SEEDS].each { |i| - if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && - !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && - !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact) - counts[i.mat_index] += i.stack_size - end - } - - counts.keys.each { |i| - if df.ui.tasks.known_plants[i] - plant = df.world.raws.plants.all[i] - if is_plantable(plant) - 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) - end - end - } - - return plantable - end - - def set_farms(plants, farms) - return if farms.length == 0 - if plants.length == 0 - plants = [-1] - end - - season = df.cur_season - - farms.each_with_index { |f, idx| - f.plant_id[season] = plants[idx % plants.length] - } - end - - def process - plantable = find_plantable_plants - @lastcounts = Hash.new(0) - - df.world.items.other[:PLANT].each { |i| - if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && - !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && - !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact && plantable.has_key?(i.mat_index)) - id = df.world.raws.plants.all[i.mat_index].id - @lastcounts[id] += i.stack_size - end - } - - return unless @running - - plants_s = [] - plants_u = [] - - plantable.each_key { |k| - plant = df.world.raws.plants.all[k] - if (@lastcounts[plant.id] < @thresholds[plant.id]) - plants_s.push(k) if plantable[k] == :Surface - plants_u.push(k) if plantable[k] == :Underground - end - } - - farms_s = [] - farms_u = [] - df.world.buildings.other[:FARM_PLOT].each { |f| - if (f.flags.exists) - underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean - farms_s.push(f) unless underground - farms_u.push(f) if underground - end - } - - set_farms(plants_s, farms_s) - set_farms(plants_u, farms_u) - end - - def start - return if @running - @onupdate = df.onupdate_register('autofarm', 1200) { process } - @running = true - end - - def stop - df.onupdate_unregister(@onupdate) - @running = false - end - - def status - stat = @running ? "Running." : "Stopped." - @lastcounts.each { |k,v| - stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}" - } - @thresholds.each { |k,v| - stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k) - } - stat << "\nDefault: #{@thresholds.default}" - stat - end - -end - -$AutoFarm ||= AutoFarm.new - -case $script_args[0] -when 'start' - $AutoFarm.start - puts $AutoFarm.status - -when 'end', 'stop', 'disable' - $AutoFarm.stop - puts 'Stopped.' - -when 'default' - $AutoFarm.setdefault($script_args[1]) - -when 'threshold' - t = $script_args[1] - $script_args[2..-1].each {|i| - $AutoFarm.setthreshold(i, t) - } - -when 'delete' - $AutoFarm.stop - $AutoFarm = nil - -when 'help', '?' - puts < 0 || plant.underground_depth_max > 0) + end + end + } + + return plantable + end + + def set_farms(plants, farms) + return if farms.length == 0 + if plants.length == 0 + plants = [-1] + end + + season = df.cur_season + + farms.each_with_index { |f, idx| + f.plant_id[season] = plants[idx % plants.length] + } + end + + def process + plantable = find_plantable_plants + @lastcounts = Hash.new(0) + + df.world.items.other[:PLANT].each { |i| + if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && + !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && + !i.flags.trader && !i.flags.in_building && !i.flags.construction && + !i.flags.artifact && plantable.has_key?(i.mat_index)) + id = df.world.raws.plants.all[i.mat_index].id + @lastcounts[id] += i.stack_size + end + } + + return unless @running + + plants_s = [] + plants_u = [] + + plantable.each_key { |k| + plant = df.world.raws.plants.all[k] + if (@lastcounts[plant.id] < @thresholds[plant.id]) + plants_s.push(k) if plantable[k] == :Surface + plants_u.push(k) if plantable[k] == :Underground + end + } + + farms_s = [] + farms_u = [] + df.world.buildings.other[:FARM_PLOT].each { |f| + if (f.flags.exists) + underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean + farms_s.push(f) unless underground + farms_u.push(f) if underground + end + } + + set_farms(plants_s, farms_s) + set_farms(plants_u, farms_u) + end + + def start + return if @running + @onupdate = df.onupdate_register('autofarm', 1200) { process } + @running = true + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + stat = @running ? "Running." : "Stopped." + @lastcounts.each { |k,v| + stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}" + } + @thresholds.each { |k,v| + stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k) + } + stat << "\nDefault: #{@thresholds.default}" + stat + end + +end + +$AutoFarm ||= AutoFarm.new + +case $script_args[0] +when 'start', 'enable' + $AutoFarm.start + puts $AutoFarm.status + +when 'end', 'stop', 'disable' + $AutoFarm.stop + puts 'Stopped.' + +when 'default' + $AutoFarm.setdefault($script_args[1]) + +when 'threshold' + t = $script_args[1] + $script_args[2..-1].each {|i| + $AutoFarm.setthreshold(i, t) + } + +when 'delete' + $AutoFarm.stop + $AutoFarm = nil + +when 'help', '?' + puts < 5 - df.curview.feed_keys(:CURSOR_DOWN_Z) - df.curview.feed_keys(:CURSOR_UP_Z) -else - df.curview.feed_keys(:CURSOR_UP_Z) - df.curview.feed_keys(:CURSOR_DOWN_Z) -end +# create first necessity items under cursor + +category = $script_args[0] || 'help' +mat_raw = $script_args[1] || 'list' +count = $script_args[2] + + +category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help' + +if category == 'help' + puts < 5 + df.curview.feed_keys(:CURSOR_DOWN_Z) + df.curview.feed_keys(:CURSOR_UP_Z) +else + df.curview.feed_keys(:CURSOR_UP_Z) + df.curview.feed_keys(:CURSOR_DOWN_Z) +end diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index 84877765d..557d39eb6 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -1,67 +1,67 @@ -# show death cause of a creature - -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 << " (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 << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON - str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON - - puts str.chomp(',') + '.' -end - -def display_death_unit(u) - death_info = u.counters.death_tg - killer = death_info.killer_tg if death_info - - str = "The #{u.race_tg.name[0]}" - str << " #{u.name}" if u.name.has_name - str << " died" - 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 << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer - - puts str.chomp(',') + '.' -end - -item = df.item_find(:selected) -unit = df.unit_find(:selected) - -if !item or !item.kind_of?(DFHack::ItemBodyComponent) - item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } -end - -if item and item.kind_of?(DFHack::ItemBodyComponent) - hf = item.hist_figure_id -elsif unit - hf = unit.hist_figure_id -end - -if not hf - puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" - -elsif hf == -1 - if unit ||= item.unit_tg - display_death_unit(unit) - else - puts "Not a historical figure, cannot death find info" - end - -else - histfig = df.world.history.figures.binsearch(hf) - unit = histfig ? df.unit_find(histfig.unit_id) : nil - if unit and not unit.flags1.dead and not unit.flags3.ghostly - puts "#{unit.name} is not dead yet !" - - else - events = df.world.history.events - (0...events.length).reverse_each { |i| - e = events[i] - if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf - display_death_event(e) - break - end - } - end -end - +# show death cause of a creature + +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 << " (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 << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON + str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON + + puts str.chomp(',') + '.' +end + +def display_death_unit(u) + death_info = u.counters.death_tg + killer = death_info.killer_tg if death_info + + str = "The #{u.race_tg.name[0]}" + str << " #{u.name}" if u.name.has_name + str << " died" + 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 << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer + + puts str.chomp(',') + '.' +end + +item = df.item_find(:selected) +unit = df.unit_find(:selected) + +if !item or !item.kind_of?(DFHack::ItemBodyComponent) + item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } +end + +if item and item.kind_of?(DFHack::ItemBodyComponent) + hf = item.hist_figure_id +elsif unit + hf = unit.hist_figure_id +end + +if not hf + puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" + +elsif hf == -1 + if unit ||= item.unit_tg + display_death_unit(unit) + else + puts "Not a historical figure, cannot death find info" + end + +else + histfig = df.world.history.figures.binsearch(hf) + unit = histfig ? df.unit_find(histfig.unit_id) : nil + if unit and not unit.flags1.dead and not unit.flags3.ghostly + puts "#{unit.name} is not dead yet !" + + else + events = df.world.history.events + (0...events.length).reverse_each { |i| + e = events[i] + if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf + display_death_event(e) + break + end + } + end +end + diff --git a/scripts/devel/scanitemother.rb b/scripts/devel/scanitemother.rb new file mode 100644 index 000000000..e026f926b --- /dev/null +++ b/scripts/devel/scanitemother.rb @@ -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 } +} diff --git a/scripts/devel/unforbidall.rb b/scripts/devel/unforbidall.rb new file mode 100644 index 000000000..565f46a58 --- /dev/null +++ b/scripts/devel/unforbidall.rb @@ -0,0 +1,3 @@ +# unforbid all items + +df.world.items.all.each { |i| i.flags.forbid = false } diff --git a/scripts/digfort.rb b/scripts/digfort.rb index 6d609f9af..5def15c47 100644 --- a/scripts/digfort.rb +++ b/scripts/digfort.rb @@ -1,38 +1,38 @@ -# designate an area for digging according to a plan in csv format - -raise "usage: digfort " if not $script_args[0] -planfile = File.read($script_args[0]) - -if df.cursor.x == -30000 - raise "place the game cursor to the top-left corner of the design" -end - -tiles = planfile.lines.map { |l| - l.sub(/#.*/, '').split(';').map { |t| t.strip } -} - -x = x0 = df.cursor.x -y = df.cursor.y -z = df.cursor.z - -tiles.each { |line| - next if line.empty? or line == [''] - line.each { |tile| - t = df.map_tile_at(x, y, z) - s = t.shape_basic - case tile - when 'd'; t.dig(:Default) if s == :Wall - when 'u'; t.dig(:UpStair) if s == :Wall - when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor - when 'i'; t.dig(:UpDownStair) if s == :Wall - when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor - when 'r'; t.dig(:Ramp) if s == :Wall - when 'x'; t.dig(:No) - end - x += 1 - } - x = x0 - y += 1 -} - -puts 'done' +# designate an area for digging according to a plan in csv format + +raise "usage: digfort " if not $script_args[0] +planfile = File.read($script_args[0]) + +if df.cursor.x == -30000 + raise "place the game cursor to the top-left corner of the design" +end + +tiles = planfile.lines.map { |l| + l.sub(/#.*/, '').split(';').map { |t| t.strip } +} + +x = x0 = df.cursor.x +y = df.cursor.y +z = df.cursor.z + +tiles.each { |line| + next if line.empty? or line == [''] + line.each { |tile| + t = df.map_tile_at(x, y, z) + s = t.shape_basic + case tile + when 'd'; t.dig(:Default) if s == :Wall + when 'u'; t.dig(:UpStair) if s == :Wall + when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor + when 'i'; t.dig(:UpDownStair) if s == :Wall + when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor + when 'r'; t.dig(:Ramp) if s == :Wall + when 'x'; t.dig(:No) + end + x += 1 + } + x = x0 + y += 1 +} + +puts 'done' diff --git a/scripts/drainaquifer.rb b/scripts/drainaquifer.rb index 4e2ae4ff1..169ea94fe 100644 --- a/scripts/drainaquifer.rb +++ b/scripts/drainaquifer.rb @@ -1,11 +1,13 @@ -# remove all aquifers from the map - -count = 0 -df.each_map_block { |b| - if b.designation[0][0].water_table or b.designation[15][15].water_table - count += 1 - b.designation.each { |dx| dx.each { |dy| dy.water_table = false } } - end -} - -puts "cleared #{count} map blocks" +# remove all aquifers from the map + +count = 0 +df.each_map_block { |b| + if b.designation[0][0].water_table or b.designation[8][8].water_table + count += 1 + df.each_map_block_z(b.map_pos.z) { |bz| + bz.designation.each { |dx| dx.each { |dy| dy.water_table = false } } + } + end +} + +puts "cleared #{count} aquifer#{'s' if count > 1}" diff --git a/scripts/exterminate.rb b/scripts/exterminate.rb index 72d1a4b4a..9fedeb48b 100644 --- a/scripts/exterminate.rb +++ b/scripts/exterminate.rb @@ -1,112 +1,135 @@ -# exterminate creatures - -# race = name of the race to eradicate, use 'him' to target only the selected creature -# use 'undead' to target all undeads -race = $script_args[0] - -# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death -magma = ($script_args[1] == 'magma') - -checkunit = lambda { |u| - (u.body.blood_count != 0 or u.body.blood_max == 0) and - not u.flags1.dead and - not u.flags1.caged and not u.flags1.chained and - #not u.flags1.hidden_in_ambush and - not df.map_designation_at(u).hidden -} - -slayit = lambda { |u| - if not magma - # 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 - else - # it's getting hot around here - # !!WARNING!! do not call on a magma-safe creature - ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) { - if u.flags1.dead - df.onupdate_unregister(ouh) - else - x, y, z = u.pos.x, u.pos.y, u.pos.z - z += 1 while tile = df.map_tile_at(x, y, z+1) and - tile.shape_passableflow and tile.shape_passablelow - df.map_tile_at(x, y, z).spawn_magma(7) - end - } - end -} - -all_races = Hash.new(0) - -df.world.units.active.map { |u| - if checkunit[u] - if (u.enemy.undead or - (u.curse.add_tags1.OPPOSED_TO_LIFE and not - u.curse.rem_tags1.OPPOSED_TO_LIFE)) - all_races['Undead'] += 1 - else - all_races[u.race_tg.creature_id] += 1 - end - end -} - -case race -when nil - all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } - -when 'help', '?' - puts < i2 - links.delete_at i2 - links.delete_at i1 - 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" - end - - # check if the unit is a group renegade - if i1 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and - l.entity_id == df.ui.group_id - } and i2 = links.index { |l| - l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and - l.entity_id == df.ui.group_id - } - fixed = true - i1, i2 = i2, i1 if i1 > i2 - links.delete_at i2 - links.delete_at i1 - 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" - end - - # 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 - i = unit.enemy.enemy_status_slot - unit.enemy.enemy_status_slot = -1 - cache = df.world.enemy_status_cache - cache.slot_used[i] = false - cache.rel_map[i].map! { -1 } - cache.rel_map.each { |a| a[i] = -1 } - cache.next_slot = i if cache.next_slot > i - end - - # return true if we actually fixed the unit - fixed -end - -count = 0 -df.unit_citizens.each { |u| - count += 1 if fixunit(u) -} - -if count > 0 - puts "loyalty cascade fixed (#{count} dwarves)" -else - puts "no loyalty cascade found" -end +# script to fix loyalty cascade, when you order your militia to kill friendly units + +def fixunit(unit) + return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id + links = unit.hist_figure_tg.entity_links + fixed = false + + # check if the unit is a civ renegade + if i1 = links.index { |l| + l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and + l.entity_id == df.ui.civ_id + } and i2 = links.index { |l| + l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and + l.entity_id == df.ui.civ_id + } + fixed = true + i1, i2 = i2, i1 if i1 > i2 + links.delete_at i2 + links.delete_at i1 + 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" + end + + # check if the unit is a group renegade + if i1 = links.index { |l| + l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and + l.entity_id == df.ui.group_id + } and i2 = links.index { |l| + l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and + l.entity_id == df.ui.group_id + } + fixed = true + i1, i2 = i2, i1 if i1 > i2 + links.delete_at i2 + links.delete_at i1 + 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" + end + + # 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 + i = unit.enemy.enemy_status_slot + unit.enemy.enemy_status_slot = -1 + cache = df.world.enemy_status_cache + cache.slot_used[i] = false + cache.rel_map[i].map! { -1 } + cache.rel_map.each { |a| a[i] = -1 } + cache.next_slot = i if cache.next_slot > i + end + + # return true if we actually fixed the unit + fixed +end + +count = 0 +df.unit_citizens.each { |u| + count += 1 if fixunit(u) +} + +if count > 0 + puts "loyalty cascade fixed (#{count} dwarves)" +else + puts "no loyalty cascade found" +end diff --git a/scripts/fix/stuckdoors.rb b/scripts/fix/stuckdoors.rb index 619ceeeb6..5c4adada1 100644 --- a/scripts/fix/stuckdoors.rb +++ b/scripts/fix/stuckdoors.rb @@ -1,20 +1,25 @@ -# fix doors that are frozen in 'open' state - -# door is stuck in open state if the map occupancy flag incorrectly indicates -# that an unit is present (and creatures will prone to pass through) - -count = 0 -df.world.buildings.all.each { |bld| - # for all doors - next if bld._rtti_classname != :building_doorst - # check if it is open - next if bld.close_timer == 0 - # check if occupancy is set - occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) - next if not occ.unit - # 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 } - count += 1 - occ.unit = false -} -puts "unstuck #{count} doors" +# fix doors that are frozen in 'open' state + +# this may happen after people mess with the game by (incorrectly) teleporting units or items +# a door may stick open if the map occupancy flags are wrong + +count = 0 +df.world.buildings.all.each { |bld| + # for all doors + next if bld._rtti_classname != :building_doorst + # check if it is open + next if bld.close_timer == 0 + # check if occupancy is set + occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) + if (occ.unit or occ.unit_grounded) and not + # check if an unit is present + 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 + 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" diff --git a/scripts/growcrops.rb b/scripts/growcrops.rb index c591dff87..ed370030e 100644 --- a/scripts/growcrops.rb +++ b/scripts/growcrops.rb @@ -1,49 +1,49 @@ -# grow crops in farm plots. ex: growcrops helmet_plump 20 - -material = $script_args[0] -count_max = $script_args[1].to_i -count_max = 100 if count_max == 0 - -# cache information from the raws -@raws_plant_name ||= {} -@raws_plant_growdur ||= {} -if @raws_plant_name.empty? - df.world.raws.plants.all.each_with_index { |p, idx| - @raws_plant_name[idx] = p.id - @raws_plant_growdur[idx] = p.growdur - } -end - -inventory = Hash.new(0) -df.world.items.other[:SEEDS].each { |seed| - next if not seed.flags.in_building - 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] - inventory[seed.mat_index] += 1 -} - -if !material or material == 'help' or material == 'list' - # show a list of available crop types - inventory.sort_by { |mat, c| c }.each { |mat, c| - name = df.world.raws.plants.all[mat].id - puts " #{name} #{c}" - } - -else - - mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] }) - unless wantmat = @raws_plant_name.index(mat) - raise "invalid plant material #{material}" - end - - count = 0 - df.world.items.other[:SEEDS].each { |seed| - next if seed.mat_index != wantmat - next if not seed.flags.in_building - 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] - seed.grow_counter = @raws_plant_growdur[seed.mat_index] - count += 1 - } - puts "Grown #{count} #{mat}" -end +# grow crops in farm plots. ex: growcrops helmet_plump 20 + +material = $script_args[0] +count_max = $script_args[1].to_i +count_max = 100 if count_max == 0 + +# cache information from the raws +@raws_plant_name ||= {} +@raws_plant_growdur ||= {} +if @raws_plant_name.empty? + df.world.raws.plants.all.each_with_index { |p, idx| + @raws_plant_name[idx] = p.id + @raws_plant_growdur[idx] = p.growdur + } +end + +inventory = Hash.new(0) +df.world.items.other[:SEEDS].each { |seed| + next if not seed.flags.in_building + 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] + inventory[seed.mat_index] += 1 +} + +if !material or material == 'help' or material == 'list' + # show a list of available crop types + inventory.sort_by { |mat, c| c }.each { |mat, c| + name = df.world.raws.plants.all[mat].id + puts " #{name} #{c}" + } + +else + + mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] }) + unless wantmat = @raws_plant_name.index(mat) + raise "invalid plant material #{material}" + end + + count = 0 + df.world.items.other[:SEEDS].each { |seed| + next if seed.mat_index != wantmat + next if not seed.flags.in_building + 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] + seed.grow_counter = @raws_plant_growdur[seed.mat_index] + count += 1 + } + puts "Grown #{count} #{mat}" +end diff --git a/scripts/lever.rb b/scripts/lever.rb index 43aa29b04..7d9eef833 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -1,120 +1,120 @@ -# control your levers from the dfhack console - -def lever_pull_job(bld) - ref = DFHack::GeneralRefBuildingHolderst.cpp_new - ref.building_id = bld.id - - job = DFHack::Job.cpp_new - job.job_type = :PullLever - job.pos = [bld.centerx, bld.centery, bld.z] - job.general_refs << ref - bld.jobs << job - df.job_link job - - puts lever_descr(bld) -end - -def lever_pull_cheat(bld) - bld.linked_mechanisms.each { |i| - i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| - r.building_tg.setTriggerState(bld.state) - } - } - - bld.state = (bld.state == 0 ? 1 : 0) - - puts lever_descr(bld) -end - -def lever_descr(bld, idx=nil) - ret = [] - - # lever description - descr = '' - descr << "#{idx}: " if idx - descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" - bld.jobs.each { |j| - if j.job_type == :PullLever - flags = '' - flags << ', repeat' if j.flags.repeat - flags << ', suspended' if j.flags.suspend - descr << " (pull order#{flags})" - end - } - - bld.linked_mechanisms.map { |i| - i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) - }.flatten.each { |r| - # linked building description - tg = r.building_tg - state = '' - if tg.respond_to?(:gate_flags) - state << (tg.gate_flags.closed ? 'closed' : 'opened') - state << ", closing (#{tg.timer})" if tg.gate_flags.closing - state << ", opening (#{tg.timer})" if tg.gate_flags.opening - end - - ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") - - # indent other links - descr = descr.gsub(/./, ' ') - } - - ret << descr if ret.empty? - - ret -end - -def lever_list - @lever_list = [] - df.world.buildings.other[:TRAP].find_all { |bld| - bld.trap_type == :Lever - }.sort_by { |bld| bld.id }.each { |bld| - puts lever_descr(bld, @lever_list.length) - @lever_list << bld.id - } -end - -case $script_args[0] -when 'pull' - cheat = $script_args.delete('--cheat') || $script_args.delete('--now') - - id = $script_args[1].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld - - if cheat - lever_pull_cheat(bld) - else - lever_pull_job(bld) - end - -when 'list' - lever_list - -when /^\d+$/ - id = $script_args[0].to_i - id = @lever_list[id] || id - bld = df.building_find(id) - raise 'invalid lever id' if not bld - - puts lever_descr(bld) - -else - puts < 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 diff --git a/scripts/magmasource.rb b/scripts/magmasource.rb deleted file mode 100644 index c20199c2a..000000000 --- a/scripts/magmasource.rb +++ /dev/null @@ -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 <= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y - } - -end - -if not bld - puts "Please select a pit/pond zone" - throw :script_finished -end - -found = 0 -df.world.items.other[:CAGE].each { |cg| - 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 not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first - found += 1 - u = uref.unit_tg - puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}" - u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id) - bld.assigned_creature << u.id -} -puts "No creature available for pitting" if found == 0 +# pit all caged creatures in a zone + +case $script_args[0] +when '?', 'help' + puts <= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y + } + +end + +if not bld + puts "Please select a pit/pond zone" + throw :script_finished +end + +found = 0 +df.world.items.other[:CAGE].each { |cg| + 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 not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first + found += 1 + u = uref.unit_tg + puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}" + u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id) + bld.assigned_creature << u.id +} +puts "No creature available for pitting" if found == 0 diff --git a/scripts/multicmd.rb b/scripts/multicmd.rb new file mode 100644 index 000000000..d92e25cde --- /dev/null +++ b/scripts/multicmd.rb @@ -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 } diff --git a/scripts/removebadthoughts.rb b/scripts/removebadthoughts.rb index ff98f8f65..76aa903fe 100644 --- a/scripts/removebadthoughts.rb +++ b/scripts/removebadthoughts.rb @@ -1,43 +1,43 @@ -# remove bad thoughts for the selected unit or the whole fort - -dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n') - -$script_args << 'all' if dry_run and $script_args.empty? - -seenbad = Hash.new(0) - -clear_mind = lambda { |u| - u.status.recent_events.each { |e| - next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-' - seenbad[e.type] += 1 - e.age = 0x1000_0000 unless dry_run - } -} - -summary = lambda { - seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt| - puts " #{thought} #{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 -} - -case $script_args[0] -when 'him' - if u = df.unit_find - clear_mind[u] - summary[] - else - puts 'Please select a dwarf ingame' - end - -when 'all' - df.unit_citizens.each { |uu| - clear_mind[uu] - } - summary[] - -else - puts "Usage: removebadthoughts [--dry-run] " - -end +# remove bad thoughts for the selected unit or the whole fort + +dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n') + +$script_args << 'all' if dry_run and $script_args.empty? + +seenbad = Hash.new(0) + +clear_mind = lambda { |u| + u.status.recent_events.each { |e| + next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-' + seenbad[e.type] += 1 + e.age = 0x1000_0000 unless dry_run + } +} + +summary = lambda { + seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt| + puts " #{thought} #{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 +} + +case $script_args[0] +when 'him' + if u = df.unit_find + clear_mind[u] + summary[] + else + puts 'Please select a dwarf ingame' + end + +when 'all' + df.unit_citizens.each { |uu| + clear_mind[uu] + } + summary[] + +else + puts "Usage: removebadthoughts [--dry-run] " + +end diff --git a/scripts/source.rb b/scripts/source.rb new file mode 100644 index 000000000..204afc70c --- /dev/null +++ b/scripts/source.rb @@ -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 < 1}" -end - -def cage_dump_items(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) - next if ref.item_tg.flags.dump - count += 1 - ref.item_tg.flags.dump = true - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_armor(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) - ref.unit_tg.inventory.each { |it| - next if it.mode != :Worn - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_weapons(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) - ref.unit_tg.inventory.each { |it| - next if it.mode != :Weapon - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" -end - -def cage_dump_all(list) - count = 0 - count_cage = 0 - list.each { |cage| - pre_count = count - cage.general_refs.each { |ref| - case ref - when DFHack::GeneralRefContainsItemst - next if ref.item_tg.flags.dump - count += 1 - ref.item_tg.flags.dump = true - when DFHack::GeneralRefContainsUnitst - ref.unit_tg.inventory.each { |it| - next if it.item.flags.dump - count += 1 - it.item.flags.dump = true - } - end - } - count_cage += 1 if pre_count != count - } - - puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" -end - - -def cage_dump_list(list) - count_total = Hash.new(0) - empty_cages = 0 - list.each { |cage| - count = Hash.new(0) - - cage.general_refs.each { |ref| - case ref - when DFHack::GeneralRefContainsItemst - count[ref.item_tg._rtti_classname] += 1 - when DFHack::GeneralRefContainsUnitst - ref.unit_tg.inventory.each { |it| - count[it.item._rtti_classname] += 1 - } - # TODO vermin ? - else - puts "unhandled ref #{ref.inspect}" if $DEBUG - end - } - - type = case cage - when DFHack::ItemCagest; 'Cage' - when DFHack::ItemAnimaltrapst; 'Animal trap' - else cage._rtti_classname - end - - if count.empty? - empty_cages += 1 - else - puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } - end - - count.each { |k, v| count_total[k] += v } - } - - if list.length > 2 - puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } - puts "with #{plural(empty_cages, 'empty cage')}" - end -end - - -# handle magic script arguments -here_only = $script_args.delete 'here' -if here_only - it = df.item_find - list = [it] - 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) } - end - if list.empty? - puts 'Please select a cage' - throw :script_finished - end - -elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first - list = [] - ids.each { |id| - $script_args.delete id - if not it = df.item_find(id.to_i) - puts "Invalid item id #{id}" - elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) - puts "Item ##{id} is not a cage" - list << it - else - list << it - end - } - if list.empty? - puts 'Please use a valid cage id' - throw :script_finished - end - -else - list = df.world.items.other[:ANY_CAGE_OR_TRAP] -end - - -# act -case $script_args[0] -when /^it/i - cage_dump_items(list) -when /^arm/i - cage_dump_armor(list) -when /^wea/i - cage_dump_weapons(list) -when 'all' - cage_dump_all(list) -when 'list' - cage_dump_list(list) -else - puts < 1}" +end + +def cage_dump_items(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_armor(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Worn + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_weapons(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Weapon + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_all(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + end + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + + +def cage_dump_list(list) + count_total = Hash.new(0) + empty_cages = 0 + list.each { |cage| + count = Hash.new(0) + + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + count[ref.item_tg._rtti_classname] += 1 + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + count[it.item._rtti_classname] += 1 + } + # TODO vermin ? + else + puts "unhandled ref #{ref.inspect}" if $DEBUG + end + } + + type = case cage + when DFHack::ItemCagest; 'Cage' + when DFHack::ItemAnimaltrapst; 'Animal trap' + else cage._rtti_classname + end + + if count.empty? + empty_cages += 1 + else + puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + end + + count.each { |k, v| count_total[k] += v } + } + + if list.length > 2 + puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + puts "with #{plural(empty_cages, 'empty cage')}" + end +end + + +# handle magic script arguments +here_only = $script_args.delete 'here' +if here_only + it = df.item_find + list = [it] + 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) } + end + if list.empty? + puts 'Please select a cage' + throw :script_finished + end + +elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first + list = [] + ids.each { |id| + $script_args.delete id + if not it = df.item_find(id.to_i) + puts "Invalid item id #{id}" + elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) + puts "Item ##{id} is not a cage" + list << it + else + list << it + end + } + if list.empty? + puts 'Please use a valid cage id' + throw :script_finished + end + +else + list = df.world.items.other[:ANY_CAGE_OR_TRAP] +end + + +# act +case $script_args[0] +when /^it/i + cage_dump_items(list) +when /^arm/i + cage_dump_armor(list) +when /^wea/i + cage_dump_weapons(list) +when 'all' + cage_dump_all(list) +when 'list' + cage_dump_list(list) +else + puts < 0 - u.counters.job_counter = 0 - end - - # no sleep - if u.counters2.sleepiness_timer > 10000 - u.counters2.sleepiness_timer = 1 - end - - # no break - if b = u.status.misc_traits.find { |t| t.id == :OnBreak } - b.value = 500_000 - end - else - $superdwarf_ids.delete id - end - } - end - } - else - puts "Select a creature using 'v'" - end - -when 'del' - if u = df.unit_find - $superdwarf_ids.delete u.id - else - puts "Select a creature using 'v'" - end - -when 'clear' - $superdwarf_ids.clear - -when 'list' - puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } - -else - puts "Usage:", - " - superdwarf add: give superspeed to currently selected creature", - " - superdwarf del: remove superspeed to current creature", - " - superdwarf clear: remove all superpowers", - " - superdwarf list: list super-dwarves" -end +# give super-dwarven speed to an unit + +$superdwarf_onupdate ||= nil +$superdwarf_ids ||= [] + +case $script_args[0] +when 'add' + if u = df.unit_find + $superdwarf_ids |= [u.id] + + $superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) { + if $superdwarf_ids.empty? + df.onupdate_unregister($superdwarf_onupdate) + $superdwarf_onupdate = nil + else + $superdwarf_ids.each { |id| + if u = df.unit_find(id) and not u.flags1.dead + # faster walk/work + if u.counters.job_counter > 0 + u.counters.job_counter = 0 + end + + # no sleep + if u.counters2.sleepiness_timer > 10000 + u.counters2.sleepiness_timer = 1 + end + + # no break + if b = u.status.misc_traits.find { |t| t.id == :OnBreak } + b.value = 500_000 + end + else + $superdwarf_ids.delete id + end + } + end + } + else + puts "Select a creature using 'v'" + end + +when 'del' + if u = df.unit_find + $superdwarf_ids.delete u.id + else + puts "Select a creature using 'v'" + end + +when 'clear' + $superdwarf_ids.clear + +when 'list' + puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } + +else + puts "Usage:", + " - superdwarf add: give superspeed to currently selected creature", + " - superdwarf del: remove superspeed to current creature", + " - superdwarf clear: remove all superpowers", + " - superdwarf list: list super-dwarves" +end diff --git a/scripts/unsuspend.rb b/scripts/unsuspend.rb index f8ae3a277..0d5a10be4 100644 --- a/scripts/unsuspend.rb +++ b/scripts/unsuspend.rb @@ -1,17 +1,17 @@ -joblist = df.world.job_list.next -count = 0 - -while joblist - job = joblist.item - joblist = joblist.next - - if job.job_type == :ConstructBuilding - if (job.flags.suspend && job.items && job.items[0]) - item = job.items[0].item - job.flags.suspend = false - count += 1 - end - end -end - -puts "Unsuspended #{count} job(s)." +joblist = df.world.job_list.next +count = 0 + +while joblist + job = joblist.item + joblist = joblist.next + + if job.job_type == :ConstructBuilding + if (job.flags.suspend && job.items && job.items[0]) + item = job.items[0].item + job.flags.suspend = false + count += 1 + end + end +end + +puts "Unsuspended #{count} job(s)."