turn down Ruby support

develop
Myk Taylor 2022-12-06 06:40:39 -08:00
parent 17bcdb1f56
commit 7cf703ef23
No known key found for this signature in database
32 changed files with 24 additions and 5658 deletions

@ -21,7 +21,7 @@ DFHack commands can be implemented in any of three ways:
same version of DFHack. They are less flexible than scripts,
but used for complex or ongoing tasks because they run faster.
:scripts: are Ruby or Lua scripts stored in ``hack/scripts/`` or other
:scripts: are Lua scripts stored in ``hack/scripts/`` or other
directories in the `script-paths`. Because they don't need to
be compiled, scripts are more flexible about versions, and
they are easier to distribute. Most third-party DFHack addons

@ -128,10 +128,17 @@ longer necessary.
resume
======
Allowed you to resume suspended jobs and displayed an overlay indicating
suspended building construction jobs. Replaced by `unsuspend` script.
.. _ruby:
.. _rb:
ruby
====
Support for the Ruby language in DFHack scripts was removed due to the issues
the Ruby library causes when used as an embedded language.
.. _warn-stuck-trees:
warn-stuck-trees

@ -66,6 +66,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Removed
- ``fix-job-postings`` from the `workflow` plugin is now obsolete since affected savegames can no longer be loaded
- Ruby is no longer a supported DFHack scripting language
# 0.47.05-r8

@ -68,10 +68,9 @@ Run `plug` at the DFHack prompt for a list of all plugins included in DFHack.
Scripts
-------
DFHack scripts can currently be written in Lua or Ruby. The `Lua API <lua-api>`
is more complete and currently better-documented, however. Referring to existing
scripts as well as the API documentation can be helpful when developing new
scripts.
DFHack scripts are written in Lua, with a `well-documented library <lua-api>`.
Referring to existing scripts as well as the API documentation can be helpful
when developing new scripts.
Scripts included in DFHack live in a separate
:source-scripts:`scripts repository <>`. This can be found in the ``scripts``

@ -289,22 +289,15 @@ it should be written in plain text. Any reStructuredText markup will not be proc
if present, will be shown verbatim to the player (which is probably not what you want).
For external scripts, the short description comes from a comment on the first line
(the comment marker and extra whitespace is stripped). For Lua, this would look like:
(the comment marker and extra whitespace is stripped):
.. code-block:: lua
-- A short description of my cool script.
and for Ruby scripts it would look like:
.. code-block:: ruby
# A short description of my cool script.
The main help text for an external script needs to appear between two markers. For
Lua, these markers are ``[====[`` and ``]====]``, and for Ruby they are ``=begin`` and
``=end``. The documentation standards above still apply to external tools, but there is
no need to include backticks for links or monospaced fonts. Here is a Lua example for an
The main help text for an external script needs to appear between two markers -- ``[====[``
and ``]====]``. The documentation standards above still apply to external tools, but there is
no need to include backticks for links or monospaced fonts. Here is an example for an
entire script header::
-- Inventory management for adventurers.

@ -5678,9 +5678,9 @@ General script API
Note that the ``dfhack.run_script()`` function allows Lua errors to propagate to the caller.
To run other types of commands (such as built-in commands, plugin commands, or
Ruby scripts), see ``dfhack.run_command()``. Note that this is slightly slower
than ``dfhack.run_script()`` for Lua scripts.
To run other types of commands (i.e. built-in commands or commands provided by plugins),
see ``dfhack.run_command()``. Note that this is slightly slower than ``dfhack.run_script()``
when running Lua scripts.
* ``dfhack.script_help([name, [extension]])``

@ -1,31 +0,0 @@
.. _rb:
ruby
====
.. dfhack-tool::
:summary: Allow Ruby scripts to be executed as DFHack commands.
:tags: dev
:no-command:
.. dfhack-command:: rb
:summary: Eval() a ruby string.
.. dfhack-command:: rb_eval
:summary: Eval() a ruby string.
Usage
-----
::
enable ruby
rb "ruby expression"
rb_eval "ruby expression"
:rb ruby expression
Example
-------
``:rb puts df.unit_find(:selected).name``
Print the name of the selected unit.

@ -341,30 +341,6 @@ static command_result enableLuaScript(color_ostream &out, std::string name, bool
return ok ? CR_OK : CR_FAILURE;
}
static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string filename, vector<string> &args)
{
if (!plug_mgr->ruby || !plug_mgr->ruby->is_enabled())
return CR_FAILURE;
// ugly temporary patch for https://github.com/DFHack/dfhack/issues/1146
string cwd = Filesystem::getcwd();
if (filename.find(cwd) == 0)
{
filename = filename.substr(cwd.size());
while (!filename.empty() && (filename[0] == '/' || filename[0] == '\\'))
filename = filename.substr(1);
}
std::string rbcmd = "$script_args = [";
for (size_t i = 0; i < args.size(); i++)
rbcmd += "'" + args[i] + "', ";
rbcmd += "]\n";
rbcmd += "catch(:script_finished) { load '" + filename + "' }";
return plug_mgr->ruby->eval_ruby(out, rbcmd.c_str());
}
command_result Core::runCommand(color_ostream &out, const std::string &command)
{
if (!command.empty())
@ -901,7 +877,6 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
con << parts[0];
bool builtin = is_builtin(con, parts[0]);
string lua_path = findScript(parts[0] + ".lua");
string ruby_path = findScript(parts[0] + ".rb");
Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (builtin)
{
@ -920,10 +895,6 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{
con << " is a Lua script: " << lua_path << std::endl;
}
else if (ruby_path.size())
{
con << " is a Ruby script: " << ruby_path << std::endl;
}
else
{
con << " is not a recognized command." << std::endl;
@ -1233,8 +1204,6 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
}
if ( lua )
res = runLuaScript(con, first, parts);
else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() )
res = runRubyScript(con, plug_mgr, filename, parts);
else if ( try_autocomplete(con, first, completed) )
res = CR_NOT_IMPLEMENTED;
else

@ -199,7 +199,6 @@ Plugin::Plugin(Core * core, const std::string & path,
plugin_is_enabled = 0;
plugin_save_data = 0;
plugin_load_data = 0;
plugin_eval_ruby = 0;
state = PS_UNLOADED;
access = new RefLock();
}
@ -357,7 +356,6 @@ bool Plugin::load(color_ostream &con)
plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled");
plugin_save_data = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_save_data");
plugin_load_data = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_load_data");
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby");
index_lua(plug);
plugin_lib = plug;
commands.clear();
@ -826,7 +824,6 @@ PluginManager::PluginManager(Core * core) : core(core)
{
plugin_mutex = new tthread::recursive_mutex();
cmdlist_mutex = new tthread::mutex();
ruby = NULL;
}
PluginManager::~PluginManager()
@ -1035,8 +1032,6 @@ void PluginManager::registerCommands( Plugin * p )
}
command_map[name] = p;
}
if (p->plugin_eval_ruby)
ruby = p;
cmdlist_mutex->unlock();
}
@ -1048,8 +1043,6 @@ void PluginManager::unregisterCommands( Plugin * p )
{
command_map.erase(cmds[i].name);
}
if (p->plugin_eval_ruby)
ruby = NULL;
cmdlist_mutex->unlock();
}

@ -201,12 +201,6 @@ namespace DFHack
void open_lua(lua_State *state, int table);
command_result eval_ruby(color_ostream &out, const char* cmd) {
if (!plugin_eval_ruby || !is_enabled())
return CR_FAILURE;
return plugin_eval_ruby(out, cmd);
}
private:
RefLock * access;
std::vector <PluginCommand> commands;
@ -244,7 +238,6 @@ namespace DFHack
command_result (*plugin_onstatechange)(color_ostream &, state_change_event);
command_result (*plugin_enable)(color_ostream &, bool);
RPCService* (*plugin_rpcconnect)(color_ostream &);
command_result (*plugin_eval_ruby)(color_ostream &, const char*);
command_result (*plugin_save_data)(color_ostream &);
command_result (*plugin_load_data)(color_ostream &);
};
@ -282,7 +275,6 @@ namespace DFHack
bool CanInvokeHotkey(const std::string &command, df::viewscreen *top);
Plugin* operator[] (const std::string name);
std::size_t size();
Plugin *ruby;
std::map<std::string, Plugin*>::iterator begin();
std::map<std::string, Plugin*>::iterator end();

@ -25,8 +25,6 @@ local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt'
-- used when reading help text embedded in script sources
local SCRIPT_DOC_BEGIN = '[====['
local SCRIPT_DOC_END = ']====]'
local SCRIPT_DOC_BEGIN_RUBY = '=begin'
local SCRIPT_DOC_END_RUBY = '=end'
-- enums
local ENTRY_TYPES = {
@ -274,11 +272,10 @@ local function make_script_entry(old_entry, entry_name, kwargs)
if not ok then
return entry
end
local is_rb = source_path:endswith('.rb')
update_entry(entry, lines,
{begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN),
end_marker=(is_rb and SCRIPT_DOC_END_RUBY or SCRIPT_DOC_END),
first_line_is_short_help=(is_rb and '#' or '%-%-')})
{begin_marker=SCRIPT_DOC_BEGIN,
end_marker=SCRIPT_DOC_END,
first_line_is_short_help='%-%-'})
return entry
end
@ -364,7 +361,7 @@ local function scan_scripts(old_db)
if not files then goto skip_path end
for _,f in ipairs(files) do
if f.isdir or
(not f.path:endswith('.lua') and not f.path:endswith('.rb')) or
not f.path:endswith('.lua') or
f.path:startswith('test/') or
f.path:startswith('internal/') then
goto continue

@ -24,11 +24,6 @@ if(BUILD_DEV_PLUGINS)
#add_subdirectory(devel)
endif()
option(BUILD_RUBY "Build ruby binding." ON)
if(BUILD_RUBY)
#add_subdirectory(ruby)
endif()
install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION}/plugins
FILES_MATCHING PATTERN "*.lua")

@ -1,93 +0,0 @@
# Allow build system to turn off downloading of libruby.so.
option(DOWNLOAD_RUBY "Download prebuilt libruby.so for ruby plugin." ON)
if(DOWNLOAD_RUBY)
if(APPLE)
set(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/osx${DFHACK_BUILD_ARCH}/libruby.dylib)
set(RUBYLIB_INSTALL_NAME "libruby.dylib")
if(${DFHACK_BUILD_ARCH} STREQUAL 64)
# message("No ruby lib for 64-bit OS X yet")
else()
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-libruby187.dylib.gz"
"gz"
${RUBYLIB}.gz
"e9bc4263557e652121b055a46abb4f97"
${RUBYLIB}
"3ee5356759f764a440be5b5b44649826")
endif()
elseif(UNIX)
set(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/linux${DFHACK_BUILD_ARCH}/libruby.so)
set(RUBYLIB_INSTALL_NAME "libruby.so")
if(${DFHACK_BUILD_ARCH} STREQUAL 64)
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux64-libruby187.so.gz"
"gz"
${RUBYLIB}.gz
"8eb757bb9ada08608914d8ca8906c427"
${RUBYLIB}
"e8c36a06f031cfbf02def28169bb5f1f")
else()
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/linux32-libruby187.so.gz"
"gz"
${RUBYLIB}.gz
"2d06f5069ff07ea934ecd40db55a4ac5"
${RUBYLIB}
"b00d8d7086cb39f6fde793f9d89cb2d7")
endif()
else()
set(RUBYLIB ${CMAKE_CURRENT_SOURCE_DIR}/win${DFHACK_BUILD_ARCH}/libruby.dll)
set(RUBYLIB_INSTALL_NAME "libruby.dll")
if(${DFHACK_BUILD_ARCH} STREQUAL 64)
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-libruby200.dll.gz"
"gz"
${RUBYLIB}.gz
"81db54a8b8b3090c94c6ae2147d30b8f"
${RUBYLIB}
"8a8564418aebddef3dfee1e96690e713")
else()
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-libruby187.dll.gz"
"gz"
${RUBYLIB}.gz
"ffc0f1b5b33748e2a36128e90c97f6b2"
${RUBYLIB}
"482c1c418f4ee1a5f04203eee1cda0ef")
endif()
endif()
endif()
if(APPLE OR UNIX)
set(RUBYAUTOGEN ruby-autogen-gcc.rb)
else(APPLE OR UNIX)
set(RUBYAUTOGEN ruby-autogen-win.rb)
endif(APPLE OR UNIX)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN}
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} ${CMAKE_SYSTEM_NAME} ${DFHACK_BUILD_ARCH}
# cmake quirk: depending on codegen.out.xml or generate_headers only is not enough, needs both
# test by manually patching any library/xml/moo.xml, run make ruby-autogen-rb -j2, and check build/plugins/ruby/ruby-autogen.rb for patched xml data
DEPENDS generate_headers ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
COMMENT ${RUBYAUTOGEN}
)
add_custom_target(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN})
include_directories("${dfhack_SOURCE_DIR}/depends/tthread")
dfhack_plugin(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
add_dependencies(ruby ruby-autogen-rb)
if(EXISTS ${RUBYLIB})
install(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME})
else()
# Only fire this warning if DOWNLOAD_RUBY was set.
if(NOT(APPLE AND ${DFHACK_BUILD_ARCH} STREQUAL 64) AND DOWNLOAD_RUBY)
message(WARNING "Ruby library not found at ${RUBYLIB} - will not be installed")
endif()
endif()
install(DIRECTORY .
DESTINATION hack/ruby
FILES_MATCHING PATTERN "*.rb")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} DESTINATION hack/ruby)

@ -1,279 +0,0 @@
This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).
The plugin maps all the structures available in library/xml/ to ruby objects.
These objects are described in ruby-autogen.rb, they are all in the DFHack
module. The toplevel 'df' method is a shortcut to the DFHack module.
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
access to the raw DF data structures in memory is provided.
Some library methods are stored in the various .rb file, e.g. shortcuts to read
a map block, find an unit or an item, etc.
Global dfhack objects are accessible through the 'df' accessor (eg 'df.world').
DFHack structures are renamed in CamelCase in the ruby namespace.
For a list of the structures and their methods, grep the ruby-autogen.rb file.
All ruby code runs while the main DF process and other plugins are suspended.
DFHack console
--------------
The ruby plugin defines one new dfhack console command:
rb_eval <ruby expression> ; evaluate a ruby expression and show the result in
the console. Ex: rb_eval df.unit_find().name.first_name
You can use single-quotes for strings ; avoid double-quotes that are parsed
and removed by the dfhack console code.
Text output from ruby code, through the standard 'puts', 'p' or 'raise' are
redirected to the dfhack console window.
If dfhack reports 'rb_eval is not a recognized command', check stderr.log. You
need a valid 32-bit ruby library to work, and ruby1.8 is prefered (ruby1.9 may
crash DF on startup for now). Install the library in the df root folder (or
df/hack/ on linux), the library should be named 'libruby.dll' (.so on linux).
You can download a tested version at http://github.com/jjyg/dfhack/downloads/
Ruby scripts
------------
The ruby plugin allows the creation of '.rb' scripts in df/hack/scripts/.
If you create such a script, e.g. 'test.rb', that will add a new dfhack console
command 'test'.
The script can access the console command arguments through the global variable
'$script_args', which is an array of ruby Strings.
To exit early from a script, use 'throw :script_finished'
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
---------------------
This is an excerpt of the functions defined in dfhack/plugins/ruby/*.rb. Check
the files and the comments for a complete list.
df.same_pos?(obj1, obj2)
Returns true if both objects are at the same game coordinates.
obj1 and 2 should respond to #pos and #x #y #z.
df.map_block_at(pos) / map_block_at(x, y, z)
Returns the MapBlock for the coordinates or nil.
df.map_tile_at(pos)
Returns a MapTile, holding all informations wrt the map tile (read&write).
This class is a ruby specific extention, to facilitate interaction with the
DF map data. Check out hack/ruby/map.rb.
df.each_map_block { |b| }
df.each_map_block_z(zlevel) { |b| }
Iterates over every map block (opt. on a single z-level).
df.center_viewscreen(coords)
Centers the DF view on the given coordinates. Accepts x/y/z arguments, or a
single argument responding to pos/x/y/z, eg an Unit, Item, ...
df.unit_find(arg)
Returns an Unit.
With no arg, returns the currently selected unit (through the (v) or (k) menus)
With a number, returns the unit with this ID
With something else, returns the first unit at the same game coordinates
df.unit_workers
Returns a list of worker citizen: units of your race & civilization, adults,
not dead, crazy, ghosts or nobles exempted of work.
df.unit_entitypositions(unit)
Returns the list of EntityPosition occupied by the unit.
Check the 'code' field for a readable name (MANAGER, CHIEF_MEDICAL_DWARF, ...)
df.match_rawname(name, list)
String fuzzy matching. Returns the list entry most similar to 'name'.
First searches for an exact match, then for a case-insensitive match, and
finally for a case-insensitive substring.
Returns the element from list if there is only one match, or nil.
Most useful to allow the user to specify a raw-defined name,
eg 'gob' for 'GOBLIN' or 'coal' for 'COAL_BITUMINOUS', hence the name.
df.building_alloc(type, subtype, customtype)
df.building_position(bld, pos, w, h)
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 example.
df.each_tree(material) { |t| }
Iterates over every tree of the given material (eg 'maple').
df.translate_name(name, in_english=true, only_lastpart=false)
Decode the LanguageName structure as a String as displayed in the game UI.
A shortcut is available through name.to_s
df.decode_mat(obj)
Returns a MaterialInfo definition for the given object, using its mat_type
and mat_index fields. Also works with a token string argument ('STONE:DOLOMITE')
DFHack callbacks
----------------
The plugin interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use:
handle = df.onupdate_register('log') { puts 'i love flooding the console' }
You can also rate-limit when your callback is called to a number of game ticks:
handle = df.onupdate_register('myname', 10) { puts '10 more in-game ticks elapsed' }
In this case, the callback is called immediately, and then every X in-game
ticks (advances only when the game is unpaused).
To stop being called, use:
df.onupdate_unregister handle
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 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
(eg getName).
ex: s = DFHack::StlString.cpp_new ; df.building_find.getName(s) ; p s.str
Deallocation may work, using the compound method _cpp_delete. Use with caution,
may crash your DF session. It may be simpler to just leak the memory.
_cpp_delete will try to free all memory directly used by the compound, eg
strings and vectors. It will *not* call the class destructor, and will not free
stuff behind pointers.
C++ std::string fields may be directly re-allocated using standard ruby strings,
e.g. some_unit.name.nickname = 'moo'
More subtle string manipulation, e.g. changing a single character, are not
supported. Read the whole string, manipulate it in ruby, and re-assign it
instead.
C++ std::vector<> can be iterated as standard ruby Enumerable objects, using
each/map/etc.
To append data to a vector, use vector << newelement or vector.push(newelement)
To insert at a given pos, vector.insert_at(index, value)
To delete an element, vector.delete_at(index)
You can binary search an element in a vector for a given numeric field value:
df.world.unit.all.binsearch(42, :id)
will find the entry whose 'id' field is 42 (needs the vector to be initially
sorted by this field). The binsearch 2nd argument defaults to :id.
Any numeric field defined as being an enum value will be converted to a ruby
Symbol. This works for array indexes too.
ex: df.unit_find(:selected).status.labors[:HAUL_FOOD] = true
df.map_tile_at(df.cursor).designation.liquid_type = :Water
Virtual method calls are supported for C++ objects, with a maximum of 6
arguments. Arguments / return value are interpreted as Compound/Enums as
specified in the vmethod definition in the xmls.
Pointer fields are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. NULL pointers yield the 'nil' value.
Examples
--------
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
Set a custom nickname to unit with id '123'
df.unit_find(123).name.nickname = 'moo'
Show current unit profession
p df.unit_find.profession
Change current unit profession
df.unit_find.profession = :MASON
Center the screen on unit ID '123'
df.center_viewscreen(df.unit_find(123))
Find an item under the game cursor and show its C++ classname
p df.item_find(df.cursor)._rtti_classname
Find the raws name of the plant under cursor
plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) }
p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor
df.map_tile_at(df.cursor).dig(:Channel)
Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet'
hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' }
df.map_tile_at(hot).spawn_magma(2)
Plugin compilation
------------------
The plugin consists of the main ruby.cpp native plugin and the *.rb files.
The native plugin handles only low-level ruby-to-df interaction (eg raw memory
read/write, and dfhack integration), and the .rb files hold end-user helper
functions.
On dfhack start, the native plugin will initialize the ruby interpreter, and
load hack/ruby/ruby.rb. This one then loads all other .rb files.
The DF internal structures are described in ruby-autogen.rb .
It is output by ruby/codegen.pl, from dfhack/library/include/df/codegen.out.xml
It contains architecture-specific data (eg DF internal structures field offsets,
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 example,
<ld:global-type ld:meta="struct-type" type-name="unit">
<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 ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
Will generate
class Unit < MemHack::Compound
field(:name, 0) { global :LanguageName }
field(:custom_profession, 60) { stl_string }
field(:profession, 64) { number 16, true }
The syntax for the 'field' method in ruby-autogen.rb is:
1st argument = name of the method
2nd argument = offset of this field from the beginning of the current struct.
This field depends on the compiler used by Toady to generate DF.
The block argument describes the type of the field: uint32, ptr to global...
Primitive type access is done through native methods from ruby.cpp (vector length,
raw memory access, etc)

@ -1,368 +0,0 @@
module DFHack
class << self
def building_find(what=:selected, y=nil, z=nil)
if what == :selected
return world.buildings.all.binsearch(df.get_selected_building_id)
elsif what.kind_of?(Integer)
# search by building.id
return world.buildings.all.binsearch(what) if not z
# search by coordinates
x = what
world.buildings.all.find { |b|
b.z == z and
if b.room.extents
dx = x - b.room.x
dy = y - b.room.y
dx >= 0 and dx < b.room.width and
dy >= 0 and dy < b.room.height and
b.room.extents[ dy*b.room.width + dx ] > 0
else
b.x1 <= x and b.x2 >= x and
b.y1 <= y and b.y2 >= y
end
}
elsif what.respond_to?(:x) or what.respond_to?(:pos)
# find the building at the same position
what = what.pos if what.respond_to?(:pos)
building_find(what.x, what.y, what.z)
else
raise "what what?"
end
end
# allocate a new building object
def building_alloc(type, subtype=-1, custom=-1)
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
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
subtype = TrapType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Trap
bld.setSubtype(subtype)
bld.setCustomType(custom)
case type
when :Well; bld.bucket_z = bld.z
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
when :Coffin; bld.initBurialFlags
when :Trap; bld.ready_timeout = 500 if bld.trap_type == :PressurePlate
when :Floodgate; bld.gate_flags.closed = true
when :GrateWall; bld.gate_flags.closed = true
when :GrateFloor; bld.gate_flags.closed = true
when :BarsVertical; bld.gate_flags.closed = true
when :BarsFloor; bld.gate_flags.closed = true
end
bld
end
# used by building_setsize
def building_check_bridge_support(bld)
x1 = bld.x1-1
x2 = bld.x2+1
y1 = bld.y1-1
y2 = bld.y2+1
z = bld.z
(x1..x2).each { |x|
(y1..y2).each { |y|
next if ((x == x1 or x == x2) and
(y == y1 or y == y2))
if mb = map_block_at(x, y, z) and tile = mb.tiletype[x%16][y%16] and TiletypeShape::BasicShape[Tiletype::Shape[tile]] != :Open
bld.gate_flags.has_support = true
return
end
}
}
bld.gate_flags.has_support = false
end
# sets x2/centerx/y2/centery from x1/y1/bldtype
# x2/y2 preserved for :FarmPlot etc
def building_setsize(bld)
bld.x2 = bld.x1 if bld.x1 > bld.x2
bld.y2 = bld.y1 if bld.y1 > bld.y2
case bld.getType
when :Bridge
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
building_check_bridge_support(bld)
when :FarmPlot, :RoadDirt, :RoadPaved, :Stockpile, :Civzone
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
when :TradeDepot, :Shop
bld.x2 = bld.x1+4
bld.y2 = bld.y1+4
bld.centerx = bld.x1+2
bld.centery = bld.y1+2
when :SiegeEngine, :Windmill, :Wagon
bld.x2 = bld.x1+2
bld.y2 = bld.y1+2
bld.centerx = bld.x1+1
bld.centery = bld.y1+1
when :AxleHorizontal
if bld.is_vertical == 1
bld.x2 = bld.centerx = bld.x1
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
else
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
bld.y2 = bld.centery = bld.y1
end
when :WaterWheel
if bld.is_vertical == 1
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.y1+2
bld.centery = bld.y1+1
else
bld.x2 = bld.x1+2
bld.centerx = bld.x1+1
bld.y2 = bld.centery = bld.y1
end
when :Workshop, :Furnace
# Furnace = Custom or default case only
case bld.type
when :Quern, :Millstone, :Tool
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
when :Siege, :Kennels
bld.x2 = bld.x1+4
bld.y2 = bld.y1+4
bld.centerx = bld.x1+2
bld.centery = bld.y1+2
when :Custom
if bdef = world.raws.buildings.all.binsearch(bld.getCustomType)
bld.x2 = bld.x1 + bdef.dim_x - 1
bld.y2 = bld.y1 + bdef.dim_y - 1
bld.centerx = bld.x1 + bdef.workloc_x
bld.centery = bld.y1 + bdef.workloc_y
end
else
bld.x2 = bld.x1+2
bld.y2 = bld.y1+2
bld.centerx = bld.x1+1
bld.centery = bld.y1+1
end
when :ScrewPump
case bld.direction
when :FromEast
bld.x2 = bld.centerx = bld.x1+1
bld.y2 = bld.centery = bld.y1
when :FromSouth
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1+1
when :FromWest
bld.x2 = bld.x1+1
bld.y2 = bld.centery = bld.y1
bld.centerx = bld.x1
else
bld.x2 = bld.x1+1
bld.y2 = bld.centery = bld.y1
bld.centerx = bld.x1
end
when :Well
bld.bucket_z = bld.z
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
when :Construction
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
bld.setMaterialAmount(1)
return
else
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
end
bld.setMaterialAmount((bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1)
end
# set building at position, with optional width/height
def building_position(bld, pos, w=nil, h=nil)
if pos.respond_to?(:x1)
x, y, z = pos.x1, pos.y1, pos.z
w ||= pos.x2-pos.x1+1 if pos.respond_to?(:x2)
h ||= pos.y2-pos.y1+1 if pos.respond_to?(:y2)
elsif pos.respond_to?(:x)
x, y, z = pos.x, pos.y, pos.z
else
x, y, z = pos
end
w ||= pos.w if pos.respond_to?(:w)
h ||= pos.h if pos.respond_to?(:h)
bld.x1 = x
bld.y1 = y
bld.z = z
bld.x2 = bld.x1+w-1 if w
bld.y2 = bld.y1+h-1 if h
building_setsize(bld)
end
# set map occupancy/stockpile/etc for a building
def building_setoccupancy(bld)
stockpile = (bld.getType == :Stockpile)
complete = (bld.getBuildStage >= bld.getMaxBuildStage)
extents = (bld.room.extents and bld.isExtentShaped)
z = bld.z
(bld.x1..bld.x2).each { |x|
(bld.y1..bld.y2).each { |y|
next if extents and bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0
next if not mb = map_block_at(x, y, z)
des = mb.designation[x%16][y%16]
des.pile = stockpile
des.dig = :No
if complete
bld.updateOccupancy(x, y)
else
mb.occupancy[x%16][y%16].building = :Planned
end
}
}
end
# link bld into other rooms if it is inside their extents or vice versa
def building_linkrooms(bld)
world.buildings.other[:IN_PLAY].each { |ob|
next if ob.z != bld.z
if bld.is_room and bld.room.extents
next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height
next if bld.room.extents[bld.room.width*(ob.y1-bld.room.y)+(ob.x1-bld.room.x)] == 0
ui.equipment.update.buildings = true
bld.children << ob
ob.parents << bld
elsif ob.is_room and ob.room.extents
next if bld.is_room or bld.x1 < ob.room.x or bld.x1 >= ob.room.x+ob.room.width or bld.y1 < ob.room.y or bld.y1 >= ob.room.y+ob.room.height
next if ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)].to_i == 0
ui.equipment.update.buildings = true
ob.children << bld
bld.parents << ob
end
}
end
# link the building into the world, set map data, link rooms, bld.id
def building_link(bld)
bld.id = df.building_next_id
df.building_next_id += 1
world.buildings.all << bld
bld.categorize(true)
building_setoccupancy(bld) if bld.isSettingOccupancy
building_linkrooms(bld)
end
# set a design for the building
def building_createdesign(bld, rough=true)
job = bld.jobs[0]
job.mat_type = bld.mat_type
job.mat_index = bld.mat_index
if bld.needsDesign
bld.design = BuildingDesign.cpp_new
bld.design.flags.rough = rough
end
end
# creates a job to build bld, return it
def building_linkforconstruct(bld)
building_link bld
ref = GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id
job = Job.cpp_new
job.job_type = :ConstructBuilding
job.pos = [bld.centerx, bld.centery, bld.z]
job.general_refs << ref
bld.jobs << job
job_link job
job
end
# construct a building with items or JobItems
def building_construct(bld, items)
job = building_linkforconstruct(bld)
rough = false
items.each { |item|
if items.kind_of?(JobItem)
item.quantity = (bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1 if item.quantity < 0
job.job_items << item
else
job_attachitem(job, item, :Hauled)
end
rough = true if item.getType == :BOULDER
bld.mat_type = item.getMaterial if bld.mat_type == -1
bld.mat_index = item.getMaterialIndex if bld.mat_index == -1
}
building_createdesign(bld, rough)
end
# construct an abstract building (stockpile, farmplot, ...)
def building_construct_abstract(bld)
case bld.getType
when :Stockpile
max = df.world.buildings.other[:STOCKPILE].map { |s| s.stockpile_number }.max
bld.stockpile_number = max.to_i + 1
when :Civzone
max = df.world.buildings.other[:ANY_ZONE].map { |z| z.zone_num }.max
bld.zone_num = max.to_i + 1
end
building_link bld
if !bld.flags.exists
bld.flags.exists = true
bld.initFarmSeasons
end
end
def building_setowner(bld, unit)
return unless bld.is_room
return if bld.owner == unit
if bld.owner
if idx = bld.owner.owned_buildings.index { |ob| ob.id == bld.id }
bld.owner.owned_buildings.delete_at(idx)
end
if spouse = bld.owner.relations.spouse_tg and
idx = spouse.owned_buildings.index { |ob| ob.id == bld.id }
spouse.owned_buildings.delete_at(idx)
end
end
bld.owner = unit
if unit
unit.owned_buildings << bld
if spouse = bld.owner.relations.spouse_tg and
!spouse.owned_buildings.index { |ob| ob.id == bld.id } and
bld.canUseSpouseRoom
spouse.owned_buildings << bld
end
end
end
# creates a job to deconstruct the building
def building_deconstruct(bld)
job = Job.cpp_new
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
job.job_type = :DestroyBuilding
refbuildingholder.building_id = bld.id
job.general_refs << refbuildingholder
bld.jobs << job
job_link job
job
end
# exemple usage
def buildbed(pos=cursor)
raise 'where to ?' if pos.x < 0
item = world.items.all.find { |i|
i.kind_of?(ItemBedst) and
item_isfree(i)
}
raise 'no free bed, build more !' if not item
bld = building_alloc(:Bed)
building_position(bld, pos)
building_construct(bld, [item])
end
end
end

File diff suppressed because it is too large Load Diff

@ -1,44 +0,0 @@
module DFHack
class << self
# return an Item
# arg similar to unit.rb/unit_find; no arg = 'k' menu
def item_find(what=:selected, y=nil, z=nil)
if what == :selected
return world.items.all.binsearch(df.get_selected_item_id)
elsif what.kind_of?(Integer)
# search by id
return world.items.all.binsearch(what) if not z
# search by position
x = what
world.items.all.find { |i| i.pos.x == x and i.pos.y == y and i.pos.z == z }
elsif what.respond_to?(:x) or what.respond_to?(:pos)
world.items.all.find { |i| same_pos?(what, i) }
else
raise "what what?"
end
end
# check item flags to see if it is suitable for use as a job input material
def item_isfree(i, check_empty=true)
!i.flags.trader and
!i.flags.in_job and
!i.flags.construction and
!i.flags.removed and
!i.flags.forbid and
!i.flags.dump and
!i.flags.owned and
!i.flags.in_chest and # used as hospital supply ?
(!i.flags.container or not check_empty or
!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefContainsItemst) }) and
(!i.flags.in_inventory or
(!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefUnitHolderst) and # allow hauled items TODO check if holder is a thief
ir.unit_tg.inventory.find { |ii| ii.item == i and ii.mode != :Hauled } } and
!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefContainedInItemst) and
!item_isfree(ir.item_tg, false) })) and
(!i.flags.in_building or
!i.general_refs.find { |ir| ir.kind_of?(DFHack::GeneralRefBuildingHolderst) and
ir.building_tg.contained_items.find { |bi| bi.use_mode == 2 and bi.item == i } }) and
(!i.flags.on_ground or !df.map_tile_at(i).designation.hidden) # i.flags.unk11?
end
end
end

@ -1,35 +0,0 @@
module DFHack
class << self
# link a job to the world
# allocate & set job.id, allocate a JobListLink, link to job & world.jobs.list
def job_link(job)
lastjob = world.jobs.list
lastjob = lastjob.next while lastjob.next
joblink = JobListLink.cpp_new
joblink.prev = lastjob
joblink.item = job
job.list_link = joblink
job.id = df.job_next_id
df.job_next_id += 1
lastjob.next = joblink
end
# attach an item to a job, flag item in_job
def job_attachitem(job, item, role=:Hauled, filter_idx=-1)
if role != :TargetContainer
item.flags.in_job = true
end
itemlink = SpecificRef.cpp_new
itemlink.type = :JOB
itemlink.job = job
item.specific_refs << itemlink
joblink = JobItemRef.cpp_new
joblink.item = item
joblink.role = role
joblink.job_item_idx = filter_idx
job.items << joblink
end
end
end

@ -1 +0,0 @@
libruby*

@ -1 +0,0 @@
libruby*

@ -1,344 +0,0 @@
module DFHack
class << self
# return a map block by tile coordinates
# you can also use find_map_block(cursor) or anything that respond to x/y/z
def map_block_at(x, y=nil, z=nil)
x = x.pos if x.respond_to?(:pos)
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
if x >= 0 and x < world.map.x_count and y >= 0 and y < world.map.y_count and z >= 0 and z < world.map.z_count
world.map.block_index[x/16][y/16][z]
end
end
def map_designation_at(x, y=nil, z=nil)
x = x.pos if x.respond_to?(:pos)
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
if b = map_block_at(x, y, z)
b.designation[x%16][y%16]
end
end
def map_occupancy_at(x, y=nil, z=nil)
x = x.pos if x.respond_to?(:pos)
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
if b = map_block_at(x, y, z)
b.occupancy[x%16][y%16]
end
end
def map_tile_at(x=df.cursor, y=nil, z=nil)
x = x.pos if x.respond_to?(:pos)
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
b = map_block_at(x, y, z)
MapTile.new(b, x, y, z) if b
end
# yields every map block
def each_map_block
(0...world.map.x_count_block).each { |xb|
xl = world.map.block_index[xb]
(0...world.map.y_count_block).each { |yb|
yl = xl[yb]
(0...world.map.z_count_block).each { |z|
p = yl[z]
yield p if p
}
}
}
end
# yields every map block for a given z level
def each_map_block_z(z)
(0...world.map.x_count_block).each { |xb|
xl = world.map.block_index[xb]
(0...world.map.y_count_block).each { |yb|
p = xl[yb][z]
yield p if p
}
}
end
end
class MapTile
attr_accessor :x, :y, :z, :dx, :dy, :mapblock
def initialize(b, x, y, z)
@x, @y, @z = x, y, z
@dx, @dy = @x&15, @y&15
@mapblock = b
end
def offset(dx, dy=nil, dz=0)
if dx.respond_to?(:x)
dz = dx.z if dx.respond_to?(:z)
dx, dy = dx.x, dx.y
end
df.map_tile_at(@x+dx, @y+dy, @z+dz)
end
def designation
@mapblock.designation[@dx][@dy]
end
def occupancy
@mapblock.occupancy[@dx][@dy]
end
def tiletype
@mapblock.tiletype[@dx][@dy]
end
def tiletype=(t)
@mapblock.tiletype[@dx][@dy] = t
end
def caption
Tiletype::Caption[tiletype]
end
def shape
Tiletype::Shape[tiletype]
end
def tilemat
Tiletype::Material[tiletype]
end
def variant
Tiletype::Variant[tiletype]
end
def special
Tiletype::Special[tiletype]
end
def direction
Tiletype::Direction[tiletype]
end
def shape_caption
TiletypeShape::Caption[shape]
end
def shape_basic
TiletypeShape::BasicShape[shape]
end
def shape_passablelow
TiletypeShape::PassableLow[shape]
end
def shape_passablehigh
TiletypeShape::PassableHigh[shape]
end
def shape_passableflow
TiletypeShape::PassableFlow[shape]
end
def shape_walkable
TiletypeShape::Walkable[shape]
end
# return all veins for current mapblock
def all_veins
mapblock.block_events.grep(BlockSquareEventMineralst)
end
# return the vein applicable to current tile
def vein
# last vein wins
all_veins.reverse.find { |v|
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
# return the mat_index for the current tile (if in vein)
def mat_index_vein
v = vein
v.inorganic_mat if v
end
# return the RegionMapEntry (from designation.biome)
def region_map_entry
b = mapblock.region_offset[designation.biome]
wd = df.world.world_data
# region coords + [[-1, -1], [0, -1], ..., [1, 1]][b]
# clipped to world dimensions
rx = df.world.map.region_x/16
rx -= 1 if b % 3 == 0 and rx > 0
rx += 1 if b % 3 == 2 and rx < wd.world_width-1
ry = df.world.map.region_y/16
ry -= 1 if b < 3 and ry > 0
ry += 1 if b > 5 and ry < wd.world_height-1
wd.region_map[rx][ry]
end
# return the world_data.geo_biome for current tile
def geo_biome
df.world.world_data.geo_biomes[ region_map_entry.geo_index ]
end
# return the world_data.geo_biome.layer for current tile
def stone_layer
geo_biome.layers[designation.geolayer_index]
end
# MaterialInfo: token for current tile, based on tilemat (vein, soil, plant, lava_stone...)
def mat_info
case tilemat
when :SOIL
base = stone_layer
if !df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY]
base = geo_biome.layers.find_all { |l| df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }.last
end
mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length))
MaterialInfo.new(0, mat_index)
when :STONE
base = stone_layer
if df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY]
base = geo_biome.layers.find { |l| !df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }
end
mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length))
MaterialInfo.new(0, mat_index)
when :MINERAL
mat_index = (mat_index_vein || stone_layer.mat_index)
MaterialInfo.new(0, mat_index)
when :LAVA_STONE
# XXX this is wrong
# maybe should search world.region_details.pos == biome_region_pos ?
idx = mapblock.region_offset[designation.biome]
mat_index = df.world.world_data.region_details[idx].lava_stone
MaterialInfo.new(0, mat_index)
when :FEATURE
if designation.feature_local
mx = mapblock.region_pos.x
my = mapblock.region_pos.y
df.decode_mat(df.world.world_data.feature_map[mx/16][my/16].features.feature_init[mx%16][my%16][mapblock.local_feature])
elsif designation.feature_global
df.decode_mat(df.world.world_data.underground_regions[mapblock.global_feature].feature_init)
else
MaterialInfo.new(-1, -1)
end
when :FROZEN_LIQUID
MaterialInfo.new('WATER')
# TODO
#when :PLANT
#when :GRASS_DARK, :GRASS_DEAD, :GRASS_DRY, :GRASS_LIGHT
#when :CONSTRUCTION
else # AIR ASHES BROOK CAMPFIRE DRIFTWOOD FIRE HFS MAGMA POOL RIVER
MaterialInfo.new(-1, -1)
end
end
def mat_type
mat_info.mat_type
end
def mat_index
mat_info.mat_index
end
def inspect
"#<MapTile pos=[#@x, #@y, #@z] shape=#{shape} tilemat=#{tilemat} material=#{mat_info.token}>"
end
def dig(mode=:Default)
if mode == :Smooth
if (tilemat == :STONE or tilemat == :MINERAL) and caption !~ /smooth|pillar|fortification/i and # XXX caption..
designation.smooth == 0 and (designation.hidden or not df.world.jobs.list.find { |j|
# the game removes 'smooth' designation as soon as it assigns a job, if we
# re-set it the game may queue another :DetailWall that will carve a fortification
(j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self)
})
designation.dig = :No
designation.smooth = 1
mapblock.flags.designated = true
end
else
return if mode != :No and designation.dig == :No and not designation.hidden and df.world.jobs.list.find { |j|
# someone already enroute to dig here, avoid 'Inappropriate dig square' spam
JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self)
}
designation.dig = mode
mapblock.flags.designated = true if mode != :No
end
end
def spawn_liquid(quantity, is_magma=false, flowing=true)
designation.flow_size = quantity
designation.liquid_type = (is_magma ? :Magma : :Water)
designation.flow_forbid = true if is_magma or quantity >= 4
if flowing
mapblock.flags.update_liquid = true
mapblock.flags.update_liquid_twice = true
zf = df.world.map_extras.z_level_flags[z]
zf.update = true
zf.update_twice = true
end
end
def spawn_water(quantity=7)
spawn_liquid(quantity)
end
def spawn_magma(quantity=7)
spawn_liquid(quantity, true)
end
# yield a serie of tiles until the block returns true, returns the matching tile
# the yielded tiles form a (squared) spiral centered here in the current zlevel
# eg for radius 4, yields (-4, -4), (-4, -3), .., (-4, 3),
# (-4, 4), (-3, 4), .., (4, 4), .., (4, -4), .., (-3, -4)
# then move on to radius 5
def spiral_search(maxradius=100, minradius=0, step=1)
if minradius == 0
return self if yield self
minradius += step
end
sides = [[0, 1], [1, 0], [0, -1], [-1, 0]]
(minradius..maxradius).step(step) { |r|
sides.length.times { |s|
dxr, dyr = sides[(s-1) % sides.length]
dx, dy = sides[s]
(-r...r).step(step) { |v|
t = offset(dxr*r + dx*v, dyr*r + dy*v)
return t if t and yield t
}
}
}
nil
end
# returns dx^2+dy^2+dz^2
def distance_to(ot)
(x-ot.x)**2 + (y-ot.y)**2 + (z-ot.z)**2
end
end
end

@ -1,203 +0,0 @@
module DFHack
class MaterialInfo
attr_accessor :mat_type, :mat_index
attr_accessor :mode, :material, :creature, :figure, :plant, :inorganic
def initialize(what, idx=nil)
case what
when Integer
@mat_type, @mat_index = what, idx
decode_type_index
when String
decode_string(what)
else
@mat_type, @mat_index = what.mat_type, what.mat_index
decode_type_index
end
end
CREATURE_BASE = 19
FIGURE_BASE = CREATURE_BASE+200
PLANT_BASE = FIGURE_BASE+200
END_BASE = PLANT_BASE+200
# interpret the mat_type and mat_index fields
def decode_type_index
if @mat_index < 0 or @mat_type >= END_BASE
@mode = :Builtin
@material = df.world.raws.mat_table.builtin[@mat_type]
elsif @mat_type >= PLANT_BASE
@mode = :Plant
@plant = df.world.raws.plants.all[@mat_index]
@material = @plant.material[@mat_type-PLANT_BASE] if @plant
elsif @mat_type >= FIGURE_BASE
@mode = :Figure
@figure = df.world.history.figures.binsearch(@mat_index)
@creature = df.world.raws.creatures.all[@figure.race] if @figure
@material = @creature.material[@mat_type-FIGURE_BASE] if @creature
elsif @mat_type >= CREATURE_BASE
@mode = :Creature
@creature = df.world.raws.creatures.all[@mat_index]
@material = @creature.material[@mat_type-CREATURE_BASE] if @creature
elsif @mat_type > 0
@mode = :Builtin
@material = df.world.raws.mat_table.builtin[@mat_type]
elsif @mat_type == 0
@mode = :Inorganic
@inorganic = df.world.raws.inorganics[@mat_index]
@material = @inorganic.material if @inorganic
end
end
def decode_string(str)
parts = str.split(':')
case parts[0].chomp('_MAT')
when 'INORGANIC', 'STONE', 'METAL'
decode_string_inorganic(parts)
when 'PLANT'
decode_string_plant(parts)
when 'CREATURE'
if parts[3] and parts[3] != 'NONE'
decode_string_figure(parts)
else
decode_string_creature(parts)
end
when 'INVALID'
@mat_type = parts[1].to_i
@mat_index = parts[2].to_i
else
decode_string_builtin(parts)
end
end
def decode_string_inorganic(parts)
@@inorganics_index ||= (0...df.world.raws.inorganics.length).inject({}) { |h, i| h.update df.world.raws.inorganics[i].id => i }
@mode = :Inorganic
@mat_type = 0
if parts[1] and parts[1] != 'NONE'
@mat_index = @@inorganics_index[parts[1]]
raise "invalid inorganic token #{parts.join(':').inspect}" if not @mat_index
@inorganic = df.world.raws.inorganics[@mat_index]
@material = @inorganic.material
end
end
def decode_string_builtin(parts)
@@builtins_index ||= (1...df.world.raws.mat_table.builtin.length).inject({}) { |h, i| b = df.world.raws.mat_table.builtin[i] ; b ? h.update(b.id => i) : h }
@mode = :Builtin
@mat_index = -1
@mat_type = @@builtins_index[parts[0]]
raise "invalid builtin token #{parts.join(':').inspect}" if not @mat_type
@material = df.world.raws.mat_table.builtin[@mat_type]
if parts[0] == 'COAL' and parts[1]
@mat_index = ['COKE', 'CHARCOAL'].index(parts[1]) || -1
end
end
def decode_string_creature(parts)
@@creatures_index ||= (0...df.world.raws.creatures.all.length).inject({}) { |h, i| h.update df.world.raws.creatures.all[i].creature_id => i }
@mode = :Creature
if parts[1] and parts[1] != 'NONE'
@mat_index = @@creatures_index[parts[1]]
raise "invalid creature token #{parts.join(':').inspect}" if not @mat_index
@creature = df.world.raws.creatures.all[@mat_index]
end
if @creature and parts[2] and parts[2] != 'NONE'
@mat_type = @creature.material.index { |m| m.id == parts[2] }
@material = @creature.material[@mat_type]
@mat_type += CREATURE_BASE
end
end
def decode_string_figure(parts)
@mode = :Figure
@mat_index = parts[3].to_i
@figure = df.world.history.figures.binsearch(@mat_index)
raise "invalid creature histfig #{parts.join(':').inspect}" if not @figure
@creature = df.world.raws.creatures.all[@figure.race]
if parts[1] and parts[1] != 'NONE'
raise "invalid histfig race #{parts.join(':').inspect}" if @creature.creature_id != parts[1]
end
if @creature and parts[2] and parts[2] != 'NONE'
@mat_type = @creature.material.index { |m| m.id == parts[2] }
@material = @creature.material[@mat_type]
@mat_type += FIGURE_BASE
end
end
def decode_string_plant(parts)
@@plants_index ||= (0...df.world.raws.plants.all.length).inject({}) { |h, i| h.update df.world.raws.plants.all[i].id => i }
@mode = :Plant
if parts[1] and parts[1] != 'NONE'
@mat_index = @@plants_index[parts[1]]
raise "invalid plant token #{parts.join(':').inspect}" if not @mat_index
@plant = df.world.raws.plants.all[@mat_index]
end
if @plant and parts[2] and parts[2] != 'NONE'
@mat_type = @plant.material.index { |m| m.id == parts[2] }
raise "invalid plant type #{parts.join(':').inspect}" if not @mat_type
@material = @plant.material[@mat_type]
@mat_type += PLANT_BASE
end
end
# delete the caches of raws id => index used in decode_string
def self.flush_raws_cache
@@inorganics_index = @@plants_index = @@creatures_index = @@builtins_index = nil
end
def token
out = []
case @mode
when :Builtin
out << (@material ? @material.id : 'NONE')
out << (['COKE', 'CHARCOAL'][@mat_index] || 'NONE') if @material and @material.id == 'COAL' and @mat_index >= 0
when :Inorganic
out << 'INORGANIC'
out << @inorganic.id if @inorganic
when :Plant
out << 'PLANT_MAT'
out << @plant.id if @plant
out << @material.id if @plant and @material
when :Creature, :Figure
out << 'CREATURE_MAT'
out << @creature.creature_id if @creature
out << @material.id if @creature and @material
out << @figure.id.to_s if @creature and @material and @figure
else
out << 'INVALID'
out << @mat_type.to_s
out << @mat_index.to_s
end
out.join(':')
end
def to_s ; token ; end
def ===(other)
other.mat_index == mat_index and other.mat_type == mat_type
end
end
class << self
def decode_mat(what, idx=nil)
MaterialInfo.new(what, idx)
end
end
end

@ -1 +0,0 @@
libruby*

@ -1 +0,0 @@
libruby*

@ -1,111 +0,0 @@
module DFHack
class << self
# return a Plant
# arg similar to unit.rb/unit_find, no menu
def plant_find(what=cursor)
if what.kind_of?(Integer)
world.items.all.binsearch(what)
elsif what.respond_to?(:x) or what.respond_to?(:pos)
world.plants.all.find { |p| same_pos?(what, p) }
else
raise "what what?"
end
end
def each_tree(material=:any)
@raws_tree_name ||= {}
if @raws_tree_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_tree_name[idx] = p.id if p.flags[:TREE]
}
end
if material != :any
mat = match_rawname(material, @raws_tree_name.values)
unless wantmat = @raws_tree_name.index(mat)
raise "invalid tree material #{material}"
end
end
world.plants.all.each { |plant|
next if not @raws_tree_name[plant.material]
next if wantmat and plant.material != wantmat
yield plant
}
end
def each_shrub(material=:any)
@raws_shrub_name ||= {}
if @raws_tree_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_shrub_name[idx] = p.id if not p.flags[:GRASS] and not p.flags[:TREE]
}
end
if material != :any
mat = match_rawname(material, @raws_shrub_name.values)
unless wantmat = @raws_shrub_name.index(mat)
raise "invalid shrub material #{material}"
end
end
end
SaplingToTreeAge = 120960
def cuttrees(material=nil, count_max=100, quiet=false)
if !material
# list trees
cnt = Hash.new(0)
each_tree { |plant|
next if plant.grow_counter < SaplingToTreeAge
next if map_designation_at(plant).hidden
cnt[plant.material] += 1
}
cnt.sort_by { |mat, c| c }.each { |mat, c|
name = @raws_tree_name[mat]
puts " #{name} #{c}" unless quiet
}
else
cnt = 0
each_tree(material) { |plant|
next if plant.grow_counter < SaplingToTreeAge
b = map_block_at(plant)
d = b.designation[plant.pos.x%16][plant.pos.y%16]
next if d.hidden
if d.dig == :No
d.dig = :Default
b.flags.designated = true
cnt += 1
break if cnt == count_max
end
}
puts "Updated #{cnt} plant designations" unless quiet
end
end
def growtrees(material=nil, count_max=100, quiet=false)
if !material
# list plants
cnt = Hash.new(0)
each_tree { |plant|
next if plant.grow_counter >= SaplingToTreeAge
next if map_designation_at(plant).hidden
cnt[plant.material] += 1
}
cnt.sort_by { |mat, c| c }.each { |mat, c|
name = @raws_tree_name[mat]
puts " #{name} #{c}" unless quiet
}
else
cnt = 0
each_tree(material) { |plant|
next if plant.grow_counter >= SaplingToTreeAge
next if map_designation_at(plant).hidden
plant.grow_counter = SaplingToTreeAge
cnt += 1
break if cnt == count_max
}
puts "Grown #{cnt} saplings" unless quiet
end
end
end
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,258 +0,0 @@
# redefine standard i/o methods to use the dfhack console
module Kernel
def puts(*a)
a.flatten.each { |l|
# XXX looks like print_str crashes with strings longer than 4096... maybe not nullterminated ?
# this workaround fixes it
s = l.to_s.chomp + "\n"
while s.length > 0
DFHack.print_str(s[0, 4000])
s[0, 4000] = ''
end
}
nil
end
def puts_err(*a)
a.flatten.each { |l|
s = l.to_s.chomp + "\n"
while s.length > 0
DFHack.print_err(s[0, 4000])
s[0, 4000] = ''
end
}
nil
end
def p(*a)
a.each { |e|
puts_err e.inspect
}
nil
end
end
module DFHack
VERSION = version
class OnupdateCallback
attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description
def initialize(descr, cb, tl, initdelay=0)
@description = descr
@callback = cb
@ticklimit = tl
@minyear = (tl ? df.cur_year : 0)
@minyeartick = (tl ? df.cur_year_tick+initdelay : 0)
end
# run callback if timedout
def check_run(year, yeartick, yearlen)
if @ticklimit
return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick)
@minyear = year
@minyeartick = yeartick + @ticklimit
if @minyeartick > yearlen
@minyear += 1
@minyeartick -= yearlen
end
end
# t0 = Time.now
@callback.call
# dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1
rescue Exception
df.onupdate_unregister self
puts_err "onupdate #@description unregistered: #$!", $!.backtrace
end
def <=>(o)
[@minyear, @minyeartick] <=> [o.minyear, o.minyeartick]
end
end
class << self
attr_accessor :onupdate_list, :onstatechange_list
# register a callback to be called every gframe or more
# ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 }
# if ticklimit is given, do not call unless this much game ticks have passed. Handles advmode time stretching.
def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b)
raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String)
@onupdate_list ||= []
@onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay)
DFHack.onupdate_active = true
if onext = @onupdate_list.min
DFHack.onupdate_minyear = onext.minyear
DFHack.onupdate_minyeartick = onext.minyeartick
end
@onupdate_list.last
end
# delete the callback for onupdate ; use the value returned by onupdate_register or the description
def onupdate_unregister(b)
b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String)
@onupdate_list.delete b
if @onupdate_list.empty?
DFHack.onupdate_active = false
DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1
end
end
# same as onupdate_register, but remove the callback once it returns true
def onupdate_register_once(*a)
handle = onupdate_register(*a) {
onupdate_unregister(handle) if yield
}
end
TICKS_PER_YEAR = 1200*28*12
# this method is called by ruby.cpp if df.onupdate_active is true
def onupdate
@onupdate_list ||= []
y = yt = 0
y = cur_year rescue 0
ytmax = TICKS_PER_YEAR
if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode)
yt = cur_year_tick_advmode
ytmax *= 144
else
yt = cur_year_tick rescue 0
end
@onupdate_list.each { |o|
o.check_run(y, yt, ytmax)
}
if onext = @onupdate_list.min
DFHack.onupdate_minyear = onext.minyear
if ytmax > TICKS_PER_YEAR
DFHack.onupdate_minyeartick = -1
DFHack.onupdate_minyeartickadv = onext.minyeartick
else
DFHack.onupdate_minyeartick = onext.minyeartick
DFHack.onupdate_minyeartickadv = -1
end
end
end
# register a callback to be called every gframe or more
# ex: DFHack.onstatechange_register { |newstate| puts "state changed to #{newstate}" }
def onstatechange_register(&b)
@onstatechange_list ||= []
@onstatechange_list << b
@onstatechange_list.last
end
# delete the callback for onstatechange ; use the value returned by onstatechange_register
def onstatechange_unregister(b)
@onstatechange_list.delete b
end
# same as onstatechange_register, but auto-unregisters if the block returns true
def onstatechange_register_once
handle = onstatechange_register { |st|
onstatechange_unregister(handle) if yield(st)
}
end
# this method is called by dfhack every 'onstatechange'
def onstatechange(newstate)
@onstatechange_list ||= []
@onstatechange_list.each { |cb| cb.call(newstate) }
end
# return true if the argument is under the cursor
def at_cursor?(obj)
same_pos?(obj, cursor)
end
# returns true if both arguments are at the same x/y/z
def same_pos?(pos1, pos2)
pos1 = pos1.pos if pos1.respond_to?(:pos)
pos2 = pos2.pos if pos2.respond_to?(:pos)
pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z
end
# try to match a user-specified name to one from the raws
# uses case-switching and substring matching
# eg match_rawname('coal', ['COAL_BITUMINOUS', 'BAUXITE']) => 'COAL_BITUMINOUS'
def match_rawname(name, rawlist)
rawlist.each { |r| return r if name == r }
rawlist.each { |r| return r if name.downcase == r.downcase }
may = rawlist.find_all { |r| r.downcase.index(name.downcase) }
may.first if may.length == 1
end
def translate_name(name, english=true, onlylastpart=false)
out = []
if not onlylastpart
out << name.first_name if name.first_name != ''
if name.nickname != ''
case respond_to?(:d_init) && d_init.nickname[gametype]
when :REPLACE_ALL; return "`#{name.nickname}'"
when :REPLACE_FIRST; out.pop
end
out << "`#{name.nickname}'"
end
end
return out.join(' ') unless name.words.find { |w| w >= 0 }
if not english
tsl = world.raws.language.translations[name.language]
if name.words[0] >= 0 or name.words[1] >= 0
out << ''
out.last << tsl.words[name.words[0]] if name.words[0] >= 0
out.last << tsl.words[name.words[1]] if name.words[1] >= 0
end
if name.words[5] >= 0
out << ''
(2..5).each { |i| out.last << tsl.words[name.words[i]] if name.words[i] >= 0 }
end
if name.words[6] >= 0
out << tsl.words[name.words[6]]
end
else
wl = world.raws.language
if name.words[0] >= 0 or name.words[1] >= 0
out << ''
out.last << wl.words[name.words[0]].forms[name.parts_of_speech[0]] if name.words[0] >= 0
out.last << wl.words[name.words[1]].forms[name.parts_of_speech[1]] if name.words[1] >= 0
end
if name.words[5] >= 0
out << 'the'
out.last.capitalize! if out.length == 1
out << wl.words[name.words[2]].forms[name.parts_of_speech[2]] if name.words[2] >= 0
out << wl.words[name.words[3]].forms[name.parts_of_speech[3]] if name.words[3] >= 0
if name.words[4] >= 0
out << wl.words[name.words[4]].forms[name.parts_of_speech[4]]
out.last << '-'
else
out << ''
end
out.last << wl.words[name.words[5]].forms[name.parts_of_speech[5]]
end
if name.words[6] >= 0
out << 'of'
out.last.capitalize! if out.length == 1
out << wl.words[name.words[6]].forms[name.parts_of_speech[6]]
end
end
out.join(' ')
end
end
end
# global alias so we can write 'df.world.units.all[0]'
def df
DFHack
end
# load autogenned file
require './hack/ruby/ruby-autogen-defs'
require(RUBY_PLATFORM =~ /mswin|mingw|cygwin/i ? './hack/ruby/ruby-autogen-win' : './hack/ruby/ruby-autogen-gcc')
# load all modules
Dir['./hack/ruby/*.rb'].each { |m| require m.chomp('.rb') if m !~ /ruby-autogen/ }

@ -1,91 +0,0 @@
# df user-interface related methods
module DFHack
class << self
# returns the current active viewscreen
def curview
ret = gview.view
ret = ret.child while ret.child
ret
end
# center the DF screen on something
# updates the cursor position if visible
def center_viewscreen(x, y=nil, z=nil)
x = x.pos if x.respond_to?(:pos)
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
# compute screen 'map' size (tiles)
menuwidth = ui_menu_width[0]
# ui_menu_width shows only the 'tab' status
menuwidth = 1 if menuwidth == 2 and ui_menu_width[1] == 2 and cursor.x != -30000
menuwidth = 2 if menuwidth == 3 and cursor.x != -30000
w_w = gps.dimx - 2
w_h = gps.dimy - 2
case menuwidth
when 1; w_w -= 55
when 2; w_w -= (ui_menu_width[1] == 2 ? 24 : 31)
end
# center view
w_x = x - w_w/2
w_y = y - w_h/2
w_z = z
# round view coordinates (optional)
#w_x -= w_x % 10
#w_y -= w_y % 10
# crop to map limits
w_x = [[w_x, world.map.x_count - w_w].min, 0].max
w_y = [[w_y, world.map.y_count - w_h].min, 0].max
self.window_x = w_x
self.window_y = w_y
self.window_z = w_z
if cursor.x != -30000
cursor.x, cursor.y, cursor.z = x, y, z
end
end
# add an announcement
# color = integer, bright = bool
def add_announcement(str, color=nil, bright=nil)
cont = false
while str.length > 0
rep = Report.cpp_new
rep.color = color if color
rep.bright = ((bright && bright != 0) ? 1 : 0) if bright != nil
rep.year = cur_year
rep.time = cur_year_tick
rep.flags.continuation = cont
cont = true
rep.flags.announcement = true
rep.text = str[0, 73]
str = str[73..-1].to_s
rep.id = world.status.next_report_id
world.status.next_report_id += 1
world.status.reports << rep
world.status.announcements << rep
world.status.display_timer = 2000
yield rep if block_given?
end
end
# add an announcement to display in a game popup message
# (eg "the megabeast foobar arrived")
def popup_announcement(str, color=nil, bright=nil)
pop = PopupMessage.cpp_new(:text => str)
pop.color = color if color
pop.bright = bright if bright
world.status.popups << pop
end
end
class Viewscreen
def feed_keys(*keys)
keyset = StlSet.cpp_new(keys, InterfaceKey)
ret = feed(keyset)
keyset._cpp_delete
ret
end
end
end

@ -1,280 +0,0 @@
module DFHack
class << self
# return an Unit
# with no arg, return currently selected unit in df UI ('v' or 'k' menu)
# with numeric arg, search unit by unit.id
# with an argument that respond to x/y/z (eg cursor), find first unit at this position
def unit_find(what=:selected, y=nil, z=nil)
if what == :selected
return world.units.all.binsearch(df.get_selected_unit_id)
elsif what.kind_of?(Integer)
# search by id
return world.units.all.binsearch(what) if not z
# search by coords
x = what
world.units.all.find { |u| u.pos.x == x and u.pos.y == y and u.pos.z == z }
elsif what.respond_to?(:x) or what.respond_to?(:pos)
world.units.all.find { |u| same_pos?(what, u) }
else
raise "what what?"
end
end
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
def unit_citizens
world.units.active.find_all { |u|
unit_iscitizen(u)
}
end
def unit_testflagcurse(u, flag)
return false if u.curse.rem_tags1.send(flag)
return true if u.curse.add_tags1.send(flag)
return false if u.caste < 0
u.race_tg.caste[u.caste].flags[flag]
end
def unit_isfortmember(u)
# RE from viewscreen_unitlistst ctor
return false if df.gamemode != :DWARF or
u.mood == :Berserk or
unit_testflagcurse(u, :CRAZED) or
unit_testflagcurse(u, :OPPOSED_TO_LIFE) or
u.enemy.undead or
u.flags3.ghostly or
u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or
u.flags1.forest or
u.flags1.merchant or u.flags1.diplomat
return true if u.flags1.tame
return false if u.flags2.underworld or u.flags2.resident or
u.flags2.visitor_uninvited or u.flags2.visitor or
u.civ_id == -1 or
u.civ_id != df.ui.civ_id
true
end
# return the page in viewscreen_unitlist where the unit would appear
def unit_category(u)
return if u.flags1.left or u.flags1.incoming
# return if hostile & unit_invisible(u) (hidden_in_ambush or caged+mapblock.hidden or caged+holder.ambush
return :Dead if u.flags2.killed
return :Dead if u.flags3.ghostly # hostile ?
return if u.flags1.inactive
return :Others if !unit_isfortmember(u)
casteflags = u.race_tg.caste[u.caste].flags if u.caste >= 0
return :Livestock if casteflags and (casteflags[:PET] or casteflags[:PET_EXOTIC])
return :Citizens if unit_testflagcurse(u, :CAN_SPEAK)
:Livestock
# some other stuff with ui.race_id ? (jobs only?)
end
# merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id }
# diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id }
def unit_nemesis(u)
if ref = u.general_refs.find { |r| r.kind_of?(DFHack::GeneralRefIsNemesisst) }
ref.nemesis_tg
end
end
# return the subcategory for :Others (from vs_unitlist)
def unit_other_category(u)
# comment is actual code returned by the df function
return :Berserk if u.mood == :Berserk # 5
return :Berserk if unit_testflagcurse(u, :CRAZED) # 14
return :Undead if unit_testflagcurse(u, :OPPOSED_TO_LIFE) # 1
return :Undead if u.flags3.ghostly # 15
if df.gamemode == :ADVENTURE
return :Hostile if u.civ_id == -1 # 2
if u.animal.population.region_x == -1
return :Wild if u.flags2.roaming_wilderness_population_source_not_a_map_feature # 0
else
return :Hostile if u.flags2.important_historical_figure and n = unit_nemesis(u) and n.flags[:ACTIVE_ADVENTURER] # 2
end
return :Hostile if u.flags2.resident # 3
return :Hostile # 4
end
return :Invader if u.flags1.active_invader or u.flags1.invader_origin # 6
return :Friendly if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat # 8
return :Hostile if u.flags1.tame # 7
if u.civ_id != -1
return :Unsure if u.civ_id != df.ui.civ_id or u.flags1.resident or u.flags1.visitor or u.flags1.visitor_uninvited # 10
return :Hostile # 7
elsif u.animal.population.region_x == -1
return :Friendly if u.flags2.visitor # 8
return :Uninvited if u.flags2.visitor_uninvited # 12
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
return :Resident if u.flags2.resident # 13
return :Friendly # 8
else
return :Friendly if u.flags2.visitor # 8
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
return :Wild if u.animal.population.feature_idx == -1 and u.animal.population.cave_id == -1 # 0
return :Wild # 11
end
end
def unit_iscitizen(u)
unit_category(u) == :Citizens
end
def unit_hostiles
world.units.active.find_all { |u|
unit_ishostile(u)
}
end
# returns if an unit is openly hostile
# does not include ghosts / wildlife
def unit_ishostile(u)
# return true if u.flags3.ghostly and not u.flags1.inactive
return false unless unit_category(u) == :Others
case unit_other_category(u)
when :Berserk, :Undead, :Hostile, :Invader, :Underworld
# XXX :Resident, :Uninvited?
true
when :Unsure
# from df code, with removed duplicate checks already in other_category
return true if u.enemy.undead or u.flags3.ghostly or u.flags1.marauder
return false if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat or u.flags2.visitor
return true if u.flags1.tame or u.flags2.underworld
if histfig = u.hist_figure_tg
group = df.ui.group_tg
case unit_checkdiplomacy_hf_ent(histfig, group)
when 4, 5
true
else
false
end
elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id)
diplo.relation != 1 and diplo.relation != 5
else
u.animal.population.region_x != -1 or u.flags2.resident or u.flags2.visitor_uninvited
end
end
end
def unit_checkdiplomacy_hf_ent(histfig, group)
var_3d = var_3e = var_45 = var_46 = var_47 = var_48 = var_49 = nil
var_3d = 1 if group.type == :Outcast or group.type == :NomadicGroup or
(group.type == :Civilization and group.entity_raw.flags[:LOCAL_BANDITRY])
histfig.entity_links.each { |link|
if link.entity_id == group.id
case link.getType
when :MEMBER, :MERCENARY, :SLAVE, :PRISONER, :POSITION, :HERO
var_47 = 1
when :FORMER_MEMBER, :FORMER_MERCENARY, :FORMER_SLAVE, :FORMER_PRISONER
var_48 = 1
when :ENEMY
var_49 = 1
when :CRIMINAL
var_45 = 1
end
else
case link.getType
when :MEMBER, :MERCENARY, :SLAVE
if link_entity = link.entity_tg
diplo = group.unknown1b.diplomacy.binsearch(link.entity_id, :group_id)
case diplo.relation
when 0, 3, 4
var_48 = 1
when 1, 5
var_46 = 1
end
var_3e = 1 if link_entity.type == :Outcast or link_entity.type == :NomadicGroup or
(link_entity.type == :Civilization and link_entity.entity_raw.flags[:LOCAL_BANDITRY])
end
end
end
}
if var_49
4
elsif var_46
5
elsif !var_47 and group.resources.ethic[:KILL_NEUTRAL] == 16
4
elsif df.gamemode == :ADVENTURE and !var_47 and (var_3e or !var_3d)
4
elsif var_45
3
elsif var_47
2
elsif var_48
1
else
0
end
end
# list workers (citizen, not crazy / child / inmood / noble)
def unit_workers
world.units.active.find_all { |u|
unit_isworker(u)
}
end
def unit_isworker(u)
unit_iscitizen(u) and
u.race == df.ui.race_id and
u.mood == :None and
u.profession != :CHILD and
u.profession != :BABY and
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
end
# list currently idle workers
def unit_idlers
world.units.active.find_all { |u|
unit_isidler(u)
}
end
def unit_isidler(u)
unit_isworker(u) and
# current_job includes eat/drink/sleep/pickupequip
!u.job.current_job and
# filter 'attend meeting'
not u.specific_refs.find { |s| s.type == :ACTIVITY } and
# filter soldiers (TODO check schedule)
u.military.squad_id == -1 and
# filter incoming migrants
not u.status.misc_traits.find { |t| t.id == :Migrant }
end
def unit_entitypositions(unit)
list = []
return list if not histfig = unit.hist_figure_tg
histfig.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst
next if not ent = el.entity_tg
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
next if not pos = ent.positions.own.binsearch(pa.position_id)
list << pos
}
list
end
end
class LanguageName
def to_s(english=false)
df.translate_name(self, english)
end
end
end

@ -1 +0,0 @@
libruby*

@ -1 +0,0 @@
libruby*