diff --git a/docs/Core.rst b/docs/Core.rst index 4fb1fdb71..b82fad109 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -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 diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 072bd32ab..0fef8cbe9 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -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 diff --git a/docs/changelog.txt b/docs/changelog.txt index f94e7cc0c..b5e6d3f97 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -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 diff --git a/docs/dev/Dev-intro.rst b/docs/dev/Dev-intro.rst index 8ed7badc7..9253795eb 100644 --- a/docs/dev/Dev-intro.rst +++ b/docs/dev/Dev-intro.rst @@ -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 ` -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 `. +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`` diff --git a/docs/dev/Documentation.rst b/docs/dev/Documentation.rst index d649b8a15..07242d698 100644 --- a/docs/dev/Documentation.rst +++ b/docs/dev/Documentation.rst @@ -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. diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 2b360350f..90ccdb851 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -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]])`` diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst deleted file mode 100644 index f1fafbb36..000000000 --- a/docs/plugins/ruby.rst +++ /dev/null @@ -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. diff --git a/library/Core.cpp b/library/Core.cpp index abcba2e78..78257a262 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -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 &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 diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 223abfeac..3670755fd 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -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(); } diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 6bef8a74c..78c5e5dc8 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -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 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::iterator begin(); std::map::iterator end(); diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 5af41c929..711555a78 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -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 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e24e6129c..06d2b5c0b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -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") diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt deleted file mode 100644 index 704137621..000000000 --- a/plugins/ruby/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/plugins/ruby/README b/plugins/ruby/README deleted file mode 100644 index 767844414..000000000 --- a/plugins/ruby/README +++ /dev/null @@ -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 ; 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, - - - - - -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) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb deleted file mode 100644 index dae1d455b..000000000 --- a/plugins/ruby/building.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl deleted file mode 100755 index 95d91c3ab..000000000 --- a/plugins/ruby/codegen.pl +++ /dev/null @@ -1,1166 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use XML::LibXML; - -our @lines_rb; - -my $os = $ARGV[2] or die('os not provided (argv[2])'); -if ($os =~ /linux/i or $os =~ /darwin/i) { - $os = 'linux'; -} elsif ($os =~ /windows/i) { - $os = 'windows'; -} else { - die "Unknown OS: " . $ARGV[2] . "\n"; -} - -my $arch = $ARGV[3] or die('arch not provided (argv[3])'); -if ($arch =~ /64/i) { - $arch = 64; -} elsif ($arch =~ /32/i) { - $arch = 32; -} else { - die "Unknown architecture: " . $ARGV[3] . "\n"; -} - -# 32 bits on Windows and 32-bit *nix, 64 bits on 64-bit *nix -my $SIZEOF_LONG; -if ($os eq 'windows' || $arch == 32) { - $SIZEOF_LONG = 4; -} else { - $SIZEOF_LONG = 8; -} - -my $SIZEOF_PTR = ($arch == 64) ? 8 : 4; - -sub indent_rb(&) { - my ($sub) = @_; - my @lines; - { - local @lines_rb; - $sub->(); - @lines = map { " " . $_ } @lines_rb; - } - push @lines_rb, @lines -} - -sub rb_ucase { - my ($name) = @_; - return $name if ($name eq uc($name)); - return join("", map { ucfirst $_ } (split('_', $name))); -} - - -my %item_renderer = ( - 'global' => \&render_item_global, - 'number' => \&render_item_number, - 'container' => \&render_item_container, - 'compound' => \&render_item_compound, - 'pointer' => \&render_item_pointer, - 'static-array' => \&render_item_staticarray, - 'primitive' => \&render_item_primitive, - 'bytes' => \&render_item_bytes, -); - - -my %global_types; -our $current_typename; - -sub render_global_enum { - my ($name, $type) = @_; - - my $rbname = rb_ucase($name); - push @lines_rb, "class $rbname < MemHack::Enum"; - indent_rb { - render_enum_fields($type); - }; - push @lines_rb, "end\n"; -} - -sub render_enum_fields { - my ($type) = @_; - - push @lines_rb, "ENUM = Hash.new"; - push @lines_rb, "NUME = Hash.new"; - - my %attr_type; - my %attr_list; - render_enum_initattrs($type, \%attr_type, \%attr_list); - - my $value = -1; - for my $item ($type->findnodes('child::enum-item')) - { - $value = $item->getAttribute('value') || ($value+1); - my $elemname = $item->getAttribute('name'); # || "unk_$value"; - - if ($elemname) - { - my $rbelemname = rb_ucase($elemname); - push @lines_rb, "ENUM[$value] = :$rbelemname ; NUME[:$rbelemname] = $value"; - for my $iattr ($item->findnodes('child::item-attr')) - { - my $attr = render_enum_attr($rbelemname, $iattr, \%attr_type, \%attr_list); - $lines_rb[$#lines_rb] .= ' ; ' . $attr; - } - } - } -} - -sub render_enum_initattrs { - my ($type, $attr_type, $attr_list) = @_; - - for my $attr ($type->findnodes('child::enum-attr')) - { - my $rbattr = rb_ucase($attr->getAttribute('name')); - my $typeattr = $attr->getAttribute('type-name'); - # find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool) - if ($typeattr) { - if ($global_types{$typeattr}) { - $attr_type->{$rbattr} = 'symbol'; - } else { - $attr_type->{$rbattr} = 'naked'; - } - } else { - $attr_type->{$rbattr} = 'quote'; - } - - my $def = $attr->getAttribute('default-value'); - if ($attr->getAttribute('is-list')) - { - push @lines_rb, "$rbattr = Hash.new { |h, k| h[k] = [] }"; - $attr_list->{$rbattr} = 1; - } - elsif ($def) - { - $def = ":$def" if ($attr_type->{$rbattr} eq 'symbol'); - $def =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote'); - $def = "'$def'" if ($attr_type->{$rbattr} eq 'quote'); - push @lines_rb, "$rbattr = Hash.new($def)"; - } - else - { - push @lines_rb, "$rbattr = Hash.new"; - } - } -} - -sub render_enum_attr { - my ($rbelemname, $iattr, $attr_type, $attr_list) = @_; - - my $ian = $iattr->getAttribute('name'); - my $iav = $iattr->getAttribute('value'); - my $rbattr = rb_ucase($ian); - - my $op = ($attr_list->{$rbattr} ? '<<' : '='); - - $iav = ":$iav" if ($attr_type->{$rbattr} eq 'symbol'); - $iav =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote'); - $iav = "'$iav'" if ($attr_type->{$rbattr} eq 'quote'); - - return "${rbattr}[:$rbelemname] $op $iav"; -} - - -sub render_global_bitfield { - my ($name, $type) = @_; - - my $rbname = rb_ucase($name); - push @lines_rb, "class $rbname < MemHack::Compound"; - indent_rb { - render_bitfield_fields($type); - }; - push @lines_rb, "end\n"; -} - -sub render_bitfield_fields { - my ($type) = @_; - - push @lines_rb, "field(:_whole, 0) {"; - indent_rb { - render_item_number($type); - }; - push @lines_rb, "}"; - - my $shift = 0; - for my $field ($type->findnodes('child::ld:field')) - { - my $count = $field->getAttribute('count') || 1; - my $name = $field->getAttribute('name'); - my $type = $field->getAttribute('type-name'); - my $enum = rb_ucase($type) if ($type and $global_types{$type}); - $name = $field->getAttribute('ld:anon-name') if (!$name); - print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number')); - - if ($name) - { - if ($enum) { - push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }"; - } elsif ($count == 1) { - push @lines_rb, "field(:$name, 0) { bit $shift }"; - } else { - push @lines_rb, "field(:$name, 0) { bits $shift, $count }"; - } - } - - $shift += $count; - } -} - - -my %seen_class; -our $compound_off; -our $compound_pointer; - -sub render_global_class { - my ($name, $type) = @_; - - my $meta = $type->getAttribute('ld:meta'); - my $rbname = rb_ucase($name); - - # ensure pre-definition of ancestors - my $parent = $type->getAttribute('inherits-from'); - render_global_class($parent, $global_types{$parent}) if ($parent and !$seen_class{$parent}); - - return if $seen_class{$name}; - $seen_class{$name}++; - - local $compound_off = 0; - $compound_off = $SIZEOF_PTR if ($meta eq 'class-type'); - $compound_off = sizeof($global_types{$parent}) if $parent; - local $current_typename = $rbname; - - my $rtti_name; - if ($meta eq 'class-type') - { - $rtti_name = $type->getAttribute('original-name') || - $type->getAttribute('type-name') || - $name; - } - - my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound'); - my $ienum; - if (($type->getAttribute('ld:subtype') or '') eq 'df-other-vectors-type') - { - $rbparent = 'MemHack::OtherVectors'; - $ienum = rb_ucase($type->getAttribute('index-enum')); - } - push @lines_rb, "class $rbname < $rbparent"; - indent_rb { - my $sz = sizeof($type); - # see comment is sub sizeof ; but gcc has sizeof(cls) aligned - $sz = align_field($sz, $SIZEOF_PTR) if $os eq 'linux' and $meta eq 'class-type'; - push @lines_rb, "sizeof $sz\n"; - - push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name; - - push @lines_rb, "ienum $ienum\n" if $ienum; - - render_struct_fields($type); - - my $vms = $type->findnodes('child::virtual-methods')->[0]; - if ($vms) - { - my $voff = render_class_vmethods_voff($parent); - render_class_vmethods($vms, $voff); - } - }; - push @lines_rb, "end\n"; -} - -sub render_struct_fields { - my ($type) = @_; - - my $isunion = $type->getAttribute('is-union'); - - for my $field ($type->findnodes('child::ld:field')) - { - my $name = $field->getAttribute('name'); - $name = $field->getAttribute('ld:anon-name') if (!$name); - - if (!$name and $field->getAttribute('ld:anon-compound')) - { - render_struct_fields($field); - } - else - { - $compound_off = align_field($compound_off, get_field_align($field)); - if ($name) - { - push @lines_rb, "field(:$name, $compound_off) {"; - indent_rb { - render_item($field); - }; - push @lines_rb, "}"; - - render_struct_field_refs($type, $field, $name); - } - } - - $compound_off += sizeof($field) if (!$isunion); - } -} - -# handle generating accessor for xml attributes ref-target, refers-to etc -sub render_struct_field_refs { - my ($parent, $field, $name) = @_; - - my $reftg = $field->getAttribute('ref-target'); - - my $refto = $field->getAttribute('refers-to'); - render_field_refto($parent, $name, $refto) if ($refto); - - my $meta = $field->getAttribute('ld:meta'); - my $item = $field->findnodes('child::ld:item')->[0]; - if ($meta and $meta eq 'container' and $item) { - my $itemreftg = $item->getAttribute('ref-target'); - render_container_reftarget($parent, $item, $name, $itemreftg) if $itemreftg; - } elsif ($reftg) { - render_field_reftarget($parent, $field, $name, $reftg); - } -} - -sub render_field_reftarget { - my ($parent, $field, $name, $reftg) = @_; - - my $tg = $global_types{$reftg}; - return if (!$tg); - - my $tgname = "${name}_tg"; - $tgname =~ s/_id(.?.?)_tg/_tg$1/; - - my $aux = $field->getAttribute('aux-value'); - if ($aux) { - # minimal support (aims is unit.caste_tg) - return if $aux !~ /^\$\$\.[^_][\w\.]+$/; - $aux =~ s/\$\$\.//; - - for my $codehelper ($tg->findnodes('child::code-helper')) { - if ($codehelper->getAttribute('name') eq 'find-instance') { - my $helper = $codehelper->textContent; - $helper =~ s/\$global/df/; - $helper =~ s/\$\$/$aux/; - $helper =~ s/\$/$name/; - if ($helper =~ /^[\w\.\[\]]+$/) { - push @lines_rb, "def $tgname ; $helper ; end"; - } - } - } - return; - } - - my $tgvec = $tg->getAttribute('instance-vector'); - return if (!$tgvec); - my $idx = $tg->getAttribute('key-field'); - - $tgvec =~ s/^\$global/df/; - return if $tgvec !~ /^[\w\.]+$/; - - for my $othername (map { $_->getAttribute('name') } $parent->findnodes('child::ld:field')) { - $tgname .= '_' if ($othername and $tgname eq $othername); - } - - if ($idx) { - my $fidx = ''; - $fidx = ', :' . $idx if ($idx ne 'id'); - push @lines_rb, "def $tgname ; ${tgvec}.binsearch($name$fidx) ; end"; - } else { - push @lines_rb, "def $tgname ; ${tgvec}[$name] ; end"; - } - -} - -sub render_field_refto { - my ($parent, $name, $tgvec) = @_; - - $tgvec =~ s/^\$global/df/; - $tgvec =~ s/\[\$\]$//; - return if $tgvec !~ /^[\w\.]+$/; - - my $tgname = "${name}_tg"; - $tgname =~ s/_id(.?.?)_tg/_tg$1/; - - for my $othername (map { $_->getAttribute('name') } $parent->findnodes('child::ld:field')) { - $tgname .= '_' if ($othername and $tgname eq $othername); - } - - push @lines_rb, "def $tgname ; ${tgvec}[$name] ; end"; -} - -sub render_container_reftarget { - my ($parent, $item, $name, $reftg) = @_; - - my $aux = $item->getAttribute('aux-value'); - return if ($aux); # TODO - - my $tg = $global_types{$reftg}; - return if (!$tg); - my $tgvec = $tg->getAttribute('instance-vector'); - return if (!$tgvec); - my $idx = $tg->getAttribute('key-field'); - - $tgvec =~ s/^\$global/df/; - return if $tgvec !~ /^[\w\.]+$/; - - my $tgname = "${name}_tg"; - $tgname =~ s/_id(.?.?)_tg/_tg$1/; - - for my $othername (map { $_->getAttribute('name') } $parent->findnodes('child::ld:field')) { - $tgname .= '_' if ($othername and $tgname eq $othername); - } - - if ($idx) { - my $fidx = ''; - $fidx = ', :' . $idx if ($idx ne 'id'); - push @lines_rb, "def $tgname ; $name.map { |i| $tgvec.binsearch(i$fidx) } ; end"; - } else { - push @lines_rb, "def $tgname ; $name.map { |i| ${tgvec}[i] } ; end"; - } -} - -# return the size of the parent's vtables -sub render_class_vmethods_voff { - my ($name) = @_; - - return 0 if !$name; - - my $type = $global_types{$name}; - my $parent = $type->getAttribute('inherits-from'); - - my $voff = render_class_vmethods_voff($parent); - my $vms = $type->findnodes('child::virtual-methods')->[0]; - - for my $meth ($vms->findnodes('child::vmethod')) - { - $voff += $SIZEOF_PTR if $meth->getAttribute('is-destructor') and $os eq 'linux'; - $voff += $SIZEOF_PTR; - } - - return $voff; -} - -sub render_class_vmethods { - my ($vms, $voff) = @_; - - for my $meth ($vms->findnodes('child::vmethod')) - { - my $name = $meth->getAttribute('name'); - - if ($name) - { - my @argnames; - my @argargs; - - # check if arguments need special treatment (eg auto-convert from symbol to enum value) - for my $arg ($meth->findnodes('child::ld:field')) - { - my $nr = $#argnames + 1; - my $argname = lcfirst($arg->getAttribute('name') || "arg$nr"); - push @argnames, $argname; - - if ($arg->getAttribute('ld:meta') eq 'global' and $arg->getAttribute('ld:subtype') eq 'enum') { - push @argargs, rb_ucase($arg->getAttribute('type-name')) . ".int($argname)"; - } else { - push @argargs, $argname; - } - } - - # write vmethod ruby wrapper - push @lines_rb, "def $name(" . join(', ', @argnames) . ')'; - indent_rb { - my $args = join('', map { ", $_" } @argargs); - my $ret = $meth->findnodes('child::ret-type')->[0]; - my $call; - if (!$ret or $ret->getAttribute('ld:meta') ne 'primitive') { - $call = "DFHack.vmethod_call(self, $voff$args)"; - } else { - $call = "DFHack.vmethod_call_mem_return(self, $voff, rv$args)"; - } - render_class_vmethod_ret($call, $ret); - }; - push @lines_rb, 'end'; - } - - # on linux, the destructor uses 2 entries - $voff += $SIZEOF_PTR if $meth->getAttribute('is-destructor') and $os eq 'linux'; - $voff += $SIZEOF_PTR; - } -} - -sub render_class_vmethod_ret { - my ($call, $ret) = @_; - - if (!$ret) - { - # method returns void, hide return value - push @lines_rb, "$call ; nil"; - return; - } - - my $retmeta = $ret->getAttribute('ld:meta') || ''; - if ($retmeta eq 'global') - { - # method returns an enum value: auto-convert to symbol - my $retname = $ret->getAttribute('type-name'); - if ($retname and $global_types{$retname} and - $global_types{$retname}->getAttribute('ld:meta') eq 'enum-type') - { - push @lines_rb, rb_ucase($retname) . ".sym($call)"; - } - else - { - print "vmethod global nonenum $call\n"; - push @lines_rb, $call; - } - - } - elsif ($retmeta eq 'number') - { - # raw method call returns an int32, mask according to actual return type - my $retsubtype = $ret->getAttribute('ld:subtype'); - my $retbits = sizeof($ret) * 8; - push @lines_rb, "val = $call"; - if ($retsubtype eq 'bool') - { - push @lines_rb, "(val & 1) != 0"; - } - elsif ($ret->getAttribute('ld:unsigned')) - { - push @lines_rb, "val & ((1 << $retbits) - 1)"; - } - elsif ($retbits != 32) - { - # manual sign extension - push @lines_rb, "val &= ((1 << $retbits) - 1)"; - push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)"; - } - - } - elsif ($retmeta eq 'pointer') - { - # method returns a pointer to some struct, create the correct ruby wrapper - push @lines_rb, "ptr = $call"; - push @lines_rb, "class << self"; - indent_rb { - render_item($ret->findnodes('child::ld:item')->[0]); - }; - push @lines_rb, "end._at(ptr) if ptr != 0"; - } - elsif ($retmeta eq 'primitive') - { - my $subtype = $ret->getAttribute('ld:subtype'); - if ($subtype eq 'stl-string') { - push @lines_rb, "rv = DFHack::StlString.cpp_new"; - push @lines_rb, $call; - push @lines_rb, "rv"; - } else { - print "Unknown return subtype for $call\n"; - push @lines_rb, "nil"; - } - } - else - { - print "vmethod unkret $call\n"; - push @lines_rb, $call; - } -} - -sub render_global_objects { - my (@objects) = @_; - my @global_objects; - - local $compound_off = 0; - local $current_typename = 'Global'; - - # define all globals as 'fields' of a virtual globalobject wrapping the whole address space - push @lines_rb, 'class GlobalObjects < MemHack::Compound'; - indent_rb { - for my $obj (@objects) - { - my $oname = $obj->getAttribute('name'); - - # check if the symbol is defined in xml to avoid NULL deref - my $addr = "DFHack.get_global_address('$oname')"; - push @lines_rb, "addr = $addr"; - push @lines_rb, "if addr != 0"; - indent_rb { - push @lines_rb, "field(:$oname, addr) {"; - my $item = $obj->findnodes('child::ld:item')->[0]; - indent_rb { - render_item($item); - }; - push @lines_rb, "}"; - }; - push @lines_rb, "end"; - - push @global_objects, $oname; - } - }; - push @lines_rb, "end"; - - # define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world - indent_rb { - push @lines_rb, "Global = GlobalObjects.new._at(0)"; - for my $oname (@global_objects) - { - push @lines_rb, "if DFHack.get_global_address('$oname') != 0"; - indent_rb { - push @lines_rb, "def self.$oname ; Global.$oname ; end"; - push @lines_rb, "def self.$oname=(v) ; Global.$oname = v ; end"; - }; - push @lines_rb, "end"; - } - }; -} - - -my %align_cache; -my %sizeof_cache; - -sub align_field { - my ($off, $fldalign) = @_; - my $dt = $off % $fldalign; - $off += $fldalign - $dt if $dt > 0; - return $off; -} - -sub get_field_align { - my ($field) = @_; - my $al = $SIZEOF_PTR; - my $meta = $field->getAttribute('ld:meta'); - - if ($meta eq 'number') { - $al = sizeof($field); - # linux aligns int64_t to $SIZEOF_PTR, windows to 8 - # floats are 4 bytes so no pb - $al = 4 if ($al > 4 and (($os eq 'linux' and $arch == 32) or $al != 8)); - } elsif ($meta eq 'global') { - $al = get_global_align($field); - } elsif ($meta eq 'compound') { - $al = get_compound_align($field); - } elsif ($meta eq 'static-array') { - my $tg = $field->findnodes('child::ld:item')->[0]; - $al = get_field_align($tg); - } elsif ($meta eq 'bytes') { - $al = $field->getAttribute('alignment') || 1; - } elsif ($meta eq 'primitive') { - my $subtype = $field->getAttribute('ld:subtype'); - if ($subtype eq 'stl-fstream' and $os eq 'windows') { $al = 8; } - } - - return $al; -} - -sub get_global_align { - my ($field) = @_; - - my $typename = $field->getAttribute('type-name'); - return $align_cache{$typename} if $align_cache{$typename}; - - my $g = $global_types{$typename}; - - my $st = $field->getAttribute('ld:subtype') || ''; - if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type') - { - my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t'; - print "$st type $base\n" if $base !~ /int(\d+)_t/; - # dont cache, field->base-type may differ - return $1/8; - } - - my $al = 1; - for my $gf ($g->findnodes('child::ld:field')) { - my $fld_al = get_field_align($gf); - $al = $fld_al if $fld_al > $al; - } - $align_cache{$typename} = $al; - - return $al; -} - -sub get_compound_align { - my ($field) = @_; - - my $st = $field->getAttribute('ld:subtype') || ''; - if ($st eq 'bitfield' or $st eq 'enum') - { - my $base = $field->getAttribute('base-type') || 'uint32_t'; - if ($base eq 'long') { - return $SIZEOF_LONG; - } - print "$st type $base\n" if $base !~ /int(\d+)_t/; - return $1/8; - } - - my $al = 1; - for my $f ($field->findnodes('child::ld:field')) { - my $fal = get_field_align($f); - $al = $fal if $fal > $al; - } - - return $al; -} - -sub get_container_count { - my ($field) = @_; - my $count = $field->getAttribute('count'); - if ($count) { - return $count; - } - my $enum = $field->getAttribute('index-enum'); - if ($enum) { - my $tag = $global_types{$enum}; - return $tag->getAttribute('last-value') + 1; - } - - return 0; -} - -sub sizeof { - my ($field) = @_; - my $meta = $field->getAttribute('ld:meta'); - - if ($meta eq 'number') { - if ($field->getAttribute('ld:subtype') eq 'long') { - return $SIZEOF_LONG; - } - - return $field->getAttribute('ld:bits')/8; - - } elsif ($meta eq 'pointer') { - return $SIZEOF_PTR; - - } elsif ($meta eq 'static-array') { - my $count = get_container_count($field); - my $tg = $field->findnodes('child::ld:item')->[0]; - return $count * sizeof($tg); - - } elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') { - my $base = $field->getAttribute('base-type') || 'uint32_t'; - print "$meta type $base\n" if $base !~ /int(\d+)_t/; - return $1/8; - - } elsif ($meta eq 'global') { - my $typename = $field->getAttribute('type-name'); - return $sizeof_cache{$typename} if $sizeof_cache{$typename}; - - my $g = $global_types{$typename}; - my $st = $field->getAttribute('ld:subtype') || ''; - if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type') - { - my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t'; - print "$st type $base\n" if $base !~ /int(\d+)_t/; - return $1/8; - } - - return sizeof($g); - - } elsif ($meta eq 'class-type' or $meta eq 'struct-type' or $meta eq 'compound') { - return sizeof_compound($field); - - } elsif ($meta eq 'container') { - my $subtype = $field->getAttribute('ld:subtype'); - - if ($subtype eq 'stl-vector') { - if ($os eq 'linux' or $os eq 'windows') { - return ($arch == 64) ? 24 : 12; - } else { - print "sizeof stl-vector on $os\n"; - } - } elsif ($subtype eq 'stl-bit-vector') { - if ($os eq 'linux') { - return ($arch == 64) ? 40 : 20; - } elsif ($os eq 'windows') { - return ($arch == 64) ? 32 : 16; - } else { - print "sizeof stl-bit-vector on $os\n"; - } - } elsif ($subtype eq 'stl-deque') { - if ($os eq 'linux') { - return ($arch == 64) ? 80 : 40; - } elsif ($os eq 'windows') { - return ($arch == 64) ? 40 : 20; - } else { - print "sizeof stl-deque on $os\n"; - } - } elsif ($subtype eq 'df-linked-list') { - return 3 * $SIZEOF_PTR; - } elsif ($subtype eq 'df-flagarray') { - return 2 * $SIZEOF_PTR; # XXX length may be 4 on windows? - } elsif ($subtype eq 'df-static-flagarray') { - return $field->getAttribute('count'); - } elsif ($subtype eq 'df-array') { - return 4 + $SIZEOF_PTR; # XXX 4->2 ? - } else { - print "sizeof container $subtype\n"; - } - - } elsif ($meta eq 'primitive') { - my $subtype = $field->getAttribute('ld:subtype'); - - if ($subtype eq 'stl-string') { - if ($os eq 'linux') { - return ($arch == 64) ? 8 : 4; - } elsif ($os eq 'windows') { - return ($arch == 64) ? 32 : 24; - } else { - print "sizeof stl-string on $os\n"; - } - print "sizeof stl-string\n"; - } elsif ($subtype eq 'stl-fstream') { - if ($os eq 'linux') { - return 284; # TODO: fix on x64 - } elsif ($os eq 'windows') { - return ($arch == 64) ? 280 : 192; - } else { - print "sizeof stl-fstream on $os\n"; - } - print "sizeof stl-fstream\n"; - } else { - print "sizeof primitive $subtype\n"; - } - - } elsif ($meta eq 'bytes') { - return $field->getAttribute('size'); - } else { - print "sizeof $meta\n"; - } -} - -sub sizeof_compound { - my ($field) = @_; - - my $typename = $field->getAttribute('type-name'); - return $sizeof_cache{$typename} if $typename and $sizeof_cache{$typename}; - - my $meta = $field->getAttribute('ld:meta'); - - my $st = $field->getAttribute('ld:subtype') || ''; - if ($st eq 'bitfield' or $st eq 'enum') - { - my $base = $field->getAttribute('base-type') || 'uint32_t'; - if ($base eq 'long') { - $sizeof_cache{$typename} = $SIZEOF_LONG if $typename; - return $SIZEOF_LONG; - } - print "$st type $base\n" if $base !~ /int(\d+)_t/; - $sizeof_cache{$typename} = $1/8 if $typename; - return $1/8; - } - - if ($field->getAttribute('is-union')) - { - my $sz = 0; - for my $f ($field->findnodes('child::ld:field')) - { - my $fsz = sizeof($f); - $sz = $fsz if $fsz > $sz; - } - return $sz; - } - - my $parent = $field->getAttribute('inherits-from'); - my $off = 0; - $off = $SIZEOF_PTR if ($meta eq 'class-type'); - $off = sizeof($global_types{$parent}) if ($parent); - - my $al = 1; - $al = $SIZEOF_PTR if ($meta eq 'class-type'); - - for my $f ($field->findnodes('child::ld:field')) - { - my $fa = get_field_align($f); - $al = $fa if $fa > $al; - $off = align_field($off, $fa); - $off += sizeof($f); - } - - # GCC: class a { vtable; char; } ; class b:a { char c2; } -> c2 has offset 5 (Windows MSVC: offset 8) - $al = 1 if ($meta eq 'class-type' and $os eq 'linux'); - $off = align_field($off, $al); - $sizeof_cache{$typename} = $off if $typename; - - return $off; -} - - -sub render_item { - my ($item) = @_; - return if (!$item); - - my $meta = $item->getAttribute('ld:meta'); - - my $renderer = $item_renderer{$meta}; - if ($renderer) { - $renderer->($item); - } else { - print "no render item $meta\n"; - } -} - -sub render_item_global { - my ($item) = @_; - - my $typename = $item->getAttribute('type-name'); - my $subtype = $item->getAttribute('ld:subtype'); - - if ($subtype and $subtype eq 'enum') { - render_item_number($item); - } else { - my $rbname = rb_ucase($typename); - push @lines_rb, "global :$rbname"; - } -} - -sub render_item_number { - my ($item, $classname) = @_; - - my $subtype = $item->getAttribute('ld:subtype'); - my $meta = $item->getAttribute('ld:meta'); - my $initvalue = $item->getAttribute('init-value'); - $initvalue ||= -1 if $item->getAttribute('refers-to') or $item->getAttribute('ref-target'); - my $typename = $item->getAttribute('type-name'); - undef $typename if ($meta and $meta eq 'bitfield-type'); - my $g = $global_types{$typename} if ($typename); - $typename = rb_ucase($typename) if $typename; - $typename = $classname if (!$typename and $subtype and $subtype eq 'enum'); # compound enum - - $initvalue = 1 if ($initvalue and $initvalue eq 'true'); - $initvalue = ":$initvalue" if ($initvalue and $typename and $initvalue =~ /[a-zA-Z]/); - $initvalue ||= 'nil' if $typename; - - $subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'bitfield' or $subtype eq 'enum'); - $subtype ||= $g->getAttribute('base-type') if ($g); - $subtype = 'int32_t' if (!$subtype); - - if ($subtype eq 'uint64_t') { - push @lines_rb, 'number 64, false'; - } elsif ($subtype eq 'int64_t') { - push @lines_rb, 'number 64, true'; - } elsif ($subtype eq 'uint32_t') { - push @lines_rb, 'number 32, false'; - } elsif ($subtype eq 'int32_t') { - push @lines_rb, 'number 32, true'; - } elsif ($subtype eq 'uint16_t') { - push @lines_rb, 'number 16, false'; - } elsif ($subtype eq 'int16_t') { - push @lines_rb, 'number 16, true'; - } elsif ($subtype eq 'uint8_t') { - push @lines_rb, 'number 8, false'; - } elsif ($subtype eq 'int8_t') { - push @lines_rb, 'number 8, true'; - } elsif ($subtype eq 'bool') { - push @lines_rb, 'number 8, true'; - $initvalue ||= 'nil'; - $typename ||= 'BooleanEnum'; - } elsif ($subtype eq 'long') { - push @lines_rb, 'number ' . $SIZEOF_LONG . ', true'; - } elsif ($subtype eq 's-float') { - push @lines_rb, 'float'; - return; - } elsif ($subtype eq 'd-float') { - push @lines_rb, 'double'; - return; - } else { - print "no render number $subtype\n"; - return; - } - - $lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue); - $lines_rb[$#lines_rb] .= ", $typename" if ($typename); -} - -sub render_item_compound { - my ($item) = @_; - - my $subtype = $item->getAttribute('ld:subtype'); - - local $compound_off = 0; - my $classname = $current_typename . '_' . rb_ucase($item->getAttribute('ld:typedef-name')); - local $current_typename = $classname; - - if (!$subtype || $subtype eq 'bitfield') - { - push @lines_rb, "compound(:$classname) {"; - indent_rb { - # declare sizeof() only for useful compound, eg the one behind pointers - # that the user may want to allocate - my $sz = sizeof($item); - push @lines_rb, "sizeof $sz\n" if $compound_pointer; - - if (!$subtype) { - local $compound_pointer = 0; - render_struct_fields($item); - } else { - render_bitfield_fields($item); - } - }; - push @lines_rb, "}" - } - elsif ($subtype eq 'enum') - { - push @lines_rb, "class ::DFHack::$classname < MemHack::Enum"; - indent_rb { - # declare constants - render_enum_fields($item); - }; - push @lines_rb, "end\n"; - - # actual field - render_item_number($item, $classname); - } - else - { - print "no render compound $subtype\n"; - } -} - -sub render_item_container { - my ($item) = @_; - - my $subtype = $item->getAttribute('ld:subtype'); - my $rbmethod = join('_', split('-', $subtype)); - my $tg = $item->findnodes('child::ld:item')->[0]; - my $indexenum = $item->getAttribute('index-enum'); - my $count = $item->getAttribute('count'); - if ($tg) - { - if ($rbmethod eq 'df_linked_list') { - push @lines_rb, "$rbmethod {"; - } else { - my $tglen = sizeof($tg) if $tg; - push @lines_rb, "$rbmethod($tglen) {"; - } - indent_rb { - render_item($tg); - }; - push @lines_rb, "}"; - } - elsif ($indexenum) - { - $indexenum = rb_ucase($indexenum); - if ($count) { - push @lines_rb, "$rbmethod($count, $indexenum)"; - } else { - push @lines_rb, "$rbmethod($indexenum)"; - } - } - else - { - if ($count) { - push @lines_rb, "$rbmethod($count)"; - } else { - push @lines_rb, "$rbmethod"; - } - } -} - -sub render_item_pointer { - my ($item) = @_; - - my $tg = $item->findnodes('child::ld:item')->[0]; - my $ary = $item->getAttribute('is-array') || ''; - - if ($ary eq 'true') { - my $tglen = sizeof($tg) if $tg; - push @lines_rb, "pointer_ary($tglen) {"; - } else { - push @lines_rb, "pointer {"; - } - indent_rb { - local $compound_pointer = 1; - render_item($tg); - }; - push @lines_rb, "}"; -} - -sub render_item_staticarray { - my ($item) = @_; - - my $count = get_container_count($item); - my $tg = $item->findnodes('child::ld:item')->[0]; - my $tglen = sizeof($tg) if $tg; - my $indexenum = $item->getAttribute('index-enum'); - - if ($indexenum) { - $indexenum = rb_ucase($indexenum); - push @lines_rb, "static_array($count, $tglen, $indexenum) {"; - } else { - push @lines_rb, "static_array($count, $tglen) {"; - } - indent_rb { - render_item($tg); - }; - push @lines_rb, "}"; -} - -sub render_item_primitive { - my ($item) = @_; - - my $subtype = $item->getAttribute('ld:subtype'); - if ($subtype eq 'stl-string') { - push @lines_rb, "stl_string"; - } elsif ($subtype eq 'stl-fstream') { - } else { - print "no render primitive $subtype\n"; - } -} - -sub render_item_bytes { - my ($item) = @_; - - my $subtype = $item->getAttribute('ld:subtype'); - if ($subtype eq 'padding') { - } elsif ($subtype eq 'static-string') { - my $size = $item->getAttribute('size') || -1; - push @lines_rb, "static_string($size)"; - } else { - print "no render bytes $subtype\n"; - } -} - - - -my $input = $ARGV[0] or die "need input xml"; -my $output = $ARGV[1] or die "need output file"; - -my $doc = XML::LibXML->new()->parse_file($input); -$global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type'); - -# render enums first, this allows later code to refer to them directly -my @nonenums; -for my $name (sort { $a cmp $b } keys %global_types) -{ - my $type = $global_types{$name}; - my $meta = $type->getAttribute('ld:meta'); - if ($meta eq 'enum-type') { - render_global_enum($name, $type); - } else { - push @nonenums, $name; - } -} - -# render other structs/bitfields/classes -for my $name (@nonenums) -{ - my $type = $global_types{$name}; - my $meta = $type->getAttribute('ld:meta'); - if ($meta eq 'struct-type' or $meta eq 'class-type') { - render_global_class($name, $type); - } elsif ($meta eq 'bitfield-type') { - render_global_bitfield($name, $type); - } else { - print "no render global type $meta\n"; - } -} - -# render globals -render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object')); - - -open FH, ">$output"; -print FH "module DFHack\n"; -print FH "$_\n" for @lines_rb; -print FH "end\n"; -close FH; diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb deleted file mode 100644 index 59477c382..000000000 --- a/plugins/ruby/item.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/job.rb b/plugins/ruby/job.rb deleted file mode 100644 index bf033fbb3..000000000 --- a/plugins/ruby/job.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/linux32/.gitignore b/plugins/ruby/linux32/.gitignore deleted file mode 100644 index ef44e3942..000000000 --- a/plugins/ruby/linux32/.gitignore +++ /dev/null @@ -1 +0,0 @@ -libruby* diff --git a/plugins/ruby/linux64/.gitignore b/plugins/ruby/linux64/.gitignore deleted file mode 100644 index ef44e3942..000000000 --- a/plugins/ruby/linux64/.gitignore +++ /dev/null @@ -1 +0,0 @@ -libruby* diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb deleted file mode 100644 index 5c6295946..000000000 --- a/plugins/ruby/map.rb +++ /dev/null @@ -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 - "#" - 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 diff --git a/plugins/ruby/material.rb b/plugins/ruby/material.rb deleted file mode 100644 index ca0a64779..000000000 --- a/plugins/ruby/material.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/osx32/.gitignore b/plugins/ruby/osx32/.gitignore deleted file mode 100644 index ef44e3942..000000000 --- a/plugins/ruby/osx32/.gitignore +++ /dev/null @@ -1 +0,0 @@ -libruby* diff --git a/plugins/ruby/osx64/.gitignore b/plugins/ruby/osx64/.gitignore deleted file mode 100644 index ef44e3942..000000000 --- a/plugins/ruby/osx64/.gitignore +++ /dev/null @@ -1 +0,0 @@ -libruby* diff --git a/plugins/ruby/plant.rb b/plugins/ruby/plant.rb deleted file mode 100644 index 2f5a1c7c4..000000000 --- a/plugins/ruby/plant.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb deleted file mode 100644 index 162914e32..000000000 --- a/plugins/ruby/ruby-autogen-defs.rb +++ /dev/null @@ -1,1034 +0,0 @@ -# definition of classes used by ruby-autogen -$sizeof_ptr = case RUBY_PLATFORM - when /x86_64|x64/i; 64 - else 32 - end - -module DFHack - def self.memory_read_int64(addr) - (memory_read_int32(addr) & 0xffffffff) + (memory_read_int32(addr+4) << 32) - end - def self.memory_write_int64(addr, v) - memory_write_int32(addr, v & 0xffffffff) ; memory_write_int32(addr+4, v>>32) - end - if $sizeof_ptr == 64 - def self.memory_read_ptr(addr) - memory_read_int64(addr) & 0xffffffff_ffffffff - end - def self.memory_write_ptr(addr, v) - memory_write_int64(addr, v) - end - else - def self.memory_read_ptr(addr) - memory_read_int32(addr) & 0xffffffff - end - def self.memory_write_ptr(addr, v) - memory_write_int32(addr, v) - end - end - - module MemHack - INSPECT_SIZE_LIMIT=16384 - class MemStruct - attr_accessor :_memaddr - def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end - def _get ; self ; end - def _cpp_init ; end - def _cpp_delete ; end - end - - class Compound < MemStruct - class << self - attr_accessor :_fields, :_rtti_classname, :_sizeof - def field(name, offset) - struct = yield - return if not struct - @_fields ||= [] - @_fields << [name, offset, struct] - define_method(name) { struct._at(@_memaddr+offset)._get } - define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) } - end - def _fields_ancestors - if superclass.respond_to?(:_fields_ancestors) - superclass._fields_ancestors + _fields.to_a - else - _fields.to_a - end - end - - def number(bits, signed, initvalue=nil, enum=nil) - Number.new(bits, signed, initvalue, enum) - end - def float - Float.new - end - def double - Double.new - end - def bit(shift, enum=nil) - BitField.new(shift, 1, enum) - end - def bits(shift, len, enum=nil) - BitField.new(shift, len, enum) - end - def pointer - Pointer.new((yield if block_given?)) - end - def pointer_ary(tglen) - PointerAry.new(tglen, yield) - end - def static_array(len, tglen, indexenum=nil) - StaticArray.new(tglen, len, indexenum, yield) - end - def static_string(len) - StaticString.new(len) - end - - def stl_vector(tglen=nil) - tg = yield if tglen - case tglen - when 1; StlVector8.new(tg) - when 2; StlVector16.new(tg) - when 4; StlVector32.new(tg) - when 8; StlVector64.new(tg) - else StlVector32.new(tg) - end - end - def stl_string - StlString.new - end - def stl_bit_vector - StlBitVector.new - end - def stl_deque(tglen) - StlDeque.new(tglen, yield) - end - - def df_flagarray(indexenum=nil) - DfFlagarray.new(indexenum) - end - def df_static_flagarray(len, indexenum=nil) - DfStaticFlagarray.new(len, indexenum) - end - def df_array(tglen) - DfArray.new(tglen, yield) - end - def df_linked_list - DfLinkedList.new(yield) - end - - def global(glob) - Global.new(glob) - end - def compound(name=nil, &b) - m = Class.new(Compound) - DFHack.const_set(name, m) if name - m.class_eval(&b) - m.new - end - def rtti_classname(n) - DFHack.rtti_register(n, self) - @_rtti_classname = n - end - def sizeof(n) - @_sizeof = n - end - - # allocate a new c++ object, return its ruby wrapper - def cpp_new(init=nil) - ptr = DFHack.malloc(_sizeof) - if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname) - DFHack.memory_write_ptr(ptr, vt) - # TODO call constructor - end - o = new._at(ptr) - o._cpp_init - o._set(init) if init - o - end - end - def _cpp_init - _fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init } - end - def _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 - def _set(h) - case h - when Hash; h.each { |k, v| send("#{k}=", v) } - when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) } - else _field_names.each { |n| send("#{n}=", h.send(n)) } - end - end - def _fields ; self.class._fields.to_a ; end - def _fields_ancestors ; self.class._fields_ancestors.to_a ; end - def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end - def _rtti_classname ; self.class._rtti_classname ; end - def _raw_rtti_classname ; df.get_rtti_classname(df.get_vtable_ptr(@_memaddr)) if self.class._rtti_classname ; end - def _sizeof ; self.class._sizeof ; end - def ==(o) ; o.kind_of?(Compound) and o._memaddr == _memaddr ; end - - @@inspecting = {} # avoid infinite recursion on mutually-referenced objects - def inspect - cn = self.class.name.sub(/^DFHack::/, '') - out = "#<#{cn}" - return out << ' ...>' if @@inspecting[_memaddr] - @@inspecting[_memaddr] = true - _fields_ancestors.each { |n, o, s| - out << ' ' if out.length != 0 and out[-1, 1] != ' ' - if out.length > INSPECT_SIZE_LIMIT - out << '...' - break - end - out << inspect_field(n, o, s) - } - out.chomp!(' ') - @@inspecting.delete _memaddr - out << '>' - end - def inspect_field(n, o, s) - if s.kind_of?(BitField) and s._len == 1 and not s._enum - send(n) ? n.to_s : '' - elsif s.kind_of?(Pointer) - "#{n}=#{s._at(@_memaddr+o).inspect}" - elsif n == :_whole - "_whole=0x#{_whole.to_s(16)}" - else - v = send(n).inspect - "#{n}=#{v}" - end - rescue - "#{n}=ERR(#{$!})" - end - end - - class OtherVectors < Compound - class << self - attr_accessor :_enum - def ienum(enum) - @_enum = enum - end - end - - def _indexenum - self.class._enum - end - def [](i) - self.send(self.class._enum.sym(i)) - end - def []=(i, v) - self.send((self.class._enum.sym(i).to_s + "=").to_sym, v) - end - end - - class Enum - # number -> symbol - def self.enum - # ruby weirdness, needed to make the constants 'virtual' - @enum ||= const_get(:ENUM) - end - # symbol -> number - def self.nume - @nume ||= const_get(:NUME) - end - - def self.int(i, allow_bad_sym=false) - raise ArgumentError, "invalid enum member #{i} of #{self}" if i.kind_of?(::Symbol) and not allow_bad_sym and not nume.has_key?(i) - nume[i] || i - end - def self.sym(i) - enum[i] || i - end - end - - class Number < MemStruct - attr_accessor :_bits, :_signed, :_initvalue, :_enum - def initialize(bits, signed, initvalue, enum) - @_bits = bits - @_signed = signed - @_initvalue = initvalue - @_enum = enum - end - - def _get - v = case @_bits - when 64; DFHack.memory_read_int64(@_memaddr) - when 32; DFHack.memory_read_int32(@_memaddr) - when 16; DFHack.memory_read_int16(@_memaddr) - when 8; DFHack.memory_read_int8( @_memaddr) - end - v &= (1 << @_bits) - 1 if not @_signed - v = @_enum.sym(v) if @_enum - v - end - - def _set(v) - v = @_enum.int(v) if @_enum - case @_bits - when 64; DFHack.memory_write_int64(@_memaddr, v) - when 32; DFHack.memory_write_int32(@_memaddr, v) - when 16; DFHack.memory_write_int16(@_memaddr, v) - when 8; DFHack.memory_write_int8( @_memaddr, v) - end - end - - def _cpp_init - _set(@_initvalue) if @_initvalue - end - end - class Float < MemStruct - def _get - DFHack.memory_read_float(@_memaddr) - end - - def _set(v) - DFHack.memory_write_float(@_memaddr, v) - end - - def _cpp_init - _set(0.0) - end - end - class Double < MemStruct - def _get - DFHack.memory_read_double(@_memaddr) - end - - def _set(v) - DFHack.memory_write_double(@_memaddr, v) - end - - def _cpp_init - _set(0.0) - end - end - class BitField < MemStruct - attr_accessor :_shift, :_len, :_enum - def initialize(shift, len, enum=nil) - @_shift = shift - @_len = len - @_enum = enum - end - def _mask - (1 << @_len) - 1 - end - - def _get - v = DFHack.memory_read_int32(@_memaddr) >> @_shift - if @_len == 1 and not @_enum - ((v & 1) == 0) ? false : true - else - v &= _mask - v = @_enum.sym(v) if @_enum - v - end - end - - def _set(v) - if @_len == 1 and (not @_enum or v == false or v == true) - # allow 'bit = 0' - v = (v && v != 0 ? 1 : 0) - end - v = @_enum.int(v) if @_enum - v = (v & _mask) << @_shift - - ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff - DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v) - end - end - - class Pointer < MemStruct - attr_accessor :_tg - def initialize(tg) - @_tg = tg - end - - def _getp - DFHack.memory_read_ptr(@_memaddr) - end - - def _setp(v) - DFHack.memory_write_ptr(@_memaddr, v) - end - - def _get - addr = _getp - return if addr == 0 - return addr if not @_tg - @_tg._at(addr)._get - end - - # XXX shaky... - def _set(v) - case v - when Pointer; DFHack.memory_write_ptr(@_memaddr, v._getp) - when MemStruct; DFHack.memory_write_ptr(@_memaddr, v._memaddr) - when Integer - if @_tg and @_tg.kind_of?(MemHack::Number) - if _getp == 0 - _setp(DFHack.malloc(@_tg._bits/8)) - end - @_tg._at(_getp)._set(v) - else - DFHack.memory_write_ptr(@_memaddr, v) - end - when nil; DFHack.memory_write_ptr(@_memaddr, 0) - else @_tg._at(_getp)._set(v) - end - end - - def inspect - ptr = _getp - if ptr == 0 - 'NULL' - else - cn = '' - cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg - cn = @_tg._glob if cn == 'Global' - "#" - end - end - end - class PointerAry < MemStruct - attr_accessor :_tglen, :_tg - def initialize(tglen, tg) - @_tglen = tglen - @_tg = tg - end - - def _getp(i=0) - delta = (i != 0 ? i*@_tglen : 0) - DFHack.memory_read_ptr(@_memaddr) + delta - end - - def _get - addr = _getp - return if addr == 0 - self - end - - def _set(v) - case v - when Pointer; DFHack.memory_write_ptr(@_memaddr, v._getp) - when MemStruct; DFHack.memory_write_ptr(@_memaddr, v._memaddr) - when Integer; DFHack.memory_write_ptr(@_memaddr, v) - when nil; DFHack.memory_write_ptr(@_memaddr, 0) - else raise "cannot PointerAry._set(#{v.inspect})" - end - end - - def [](i) - addr = _getp(i) - return if addr == 0 - @_tg._at(addr)._get - end - def []=(i, v) - addr = _getp(i) - raise 'null pointer' if addr == 0 - @_tg._at(addr)._set(v) - end - - def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#" ; end - end - module Enumerable - include ::Enumerable - attr_accessor :_indexenum - def each ; (0...length).each { |i| yield self[i] } ; end - def inspect - out = '[' - each_with_index { |e, idx| - out << ', ' if out.length > 1 - if out.length > INSPECT_SIZE_LIMIT - out << '...' - break - end - out << "#{_indexenum.sym(idx)}=" if _indexenum - out << e.inspect - } - out << ']' - end - def empty? ; length == 0 ; end - def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end - def index(e=nil, &b) ; (0...length).find { |i| b ? b[self[i]] : self[i] == e } ; end - def map! ; (0...length).each { |i| self[i] = yield(self[i]) } ; end - def first ; self[0] ; end - def last ; self[length-1] ; end - end - class StaticArray < MemStruct - attr_accessor :_tglen, :_length, :_indexenum, :_tg - def initialize(tglen, length, indexenum, tg) - @_tglen = tglen - @_length = length - @_indexenum = indexenum - @_tg = tg - end - def _set(a) - a.each_with_index { |v, i| self[i] = v } - end - def _cpp_init - _length.times { |i| _tgat(i)._cpp_init } - end - def _cpp_delete - _length.times { |i| _tgat(i)._cpp_delete } - end - alias length _length - alias size _length - def _tgat(i) - @_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length - end - def [](i) - i = _indexenum.int(i) if _indexenum - i += @_length if i < 0 - if t = _tgat(i) - t._get - end - end - def []=(i, v) - i = _indexenum.int(i) if _indexenum - i += @_length if i < 0 - if t = _tgat(i) - t._set(v) - else - raise 'index out of bounds' - end - end - - include Enumerable - end - class StaticString < MemStruct - attr_accessor :_length - def initialize(length) - @_length = length - end - def length - if @_length == -1 - maxlen = 4096 - (@_memaddr & 0xfff) - maxlen += 4096 until len = DFHack.memory_read(@_memaddr, maxlen).index("\0") - len - else - @_length - end - end - def _get - DFHack.memory_read(@_memaddr, length) - end - def _set(v) - DFHack.memory_write(@_memaddr, v[0, length]) - end - end - - class StlVector32 < MemStruct - attr_accessor :_tg - def initialize(tg) - @_tg = tg || Number.new(32, false, 0, nil) - end - - def length - DFHack.memory_vector32_length(@_memaddr) - end - def size ; length ; end # alias wouldnt work for subclasses - def valueptr_at(idx) - DFHack.memory_vector32_ptrat(@_memaddr, idx) - end - def insert_at(idx, val) - DFHack.memory_vector32_insertat(@_memaddr, idx, val) - end - def delete_at(idx) - DFHack.memory_vector32_deleteat(@_memaddr, idx) - end - - def _set(v) - delete_at(length-1) while length > v.length # match lengthes - v.each_with_index { |e, i| self[i] = e } # patch entries - end - - def self._cpp_new - new._at DFHack.memory_vector_new - end - def _cpp_delete - DFHack.memory_vector_delete(@_memaddr) - end - def _cpp_init - DFHack.memory_vector_init(@_memaddr) - end - - def clear - delete_at(length-1) while length > 0 - end - def [](idx) - idx += length if idx < 0 - @_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length - end - def []=(idx, v) - idx += length if idx < 0 - if idx >= length - insert_at(length, 0) while idx >= length - elsif idx < 0 - raise 'index out of bounds' - end - @_tg._at(valueptr_at(idx))._set(v) - end - def push(v) - self[length] = v - self - end - def <<(v) ; push(v) ; end - def pop - l = length - if l > 0 - v = self[l-1] - delete_at(l-1) - end - v - end - - include Enumerable - # do a binary search in an ordered vector for a specific target attribute - # ex: world.history.figures.binsearch(unit.hist_figure_id) - def binsearch(target, field=:id) - o_start = 0 - o_end = length - 1 - while o_end >= o_start - o_half = o_start + (o_end-o_start)/2 - obj = self[o_half] - oval = obj.send(field) - if oval == target - return obj - elsif oval < target - o_start = o_half+1 - else - o_end = o_half-1 - end - end - end - end - class StlVector64 < StlVector32 - def length - DFHack.memory_vector64_length(@_memaddr) - end - def valueptr_at(idx) - DFHack.memory_vector64_ptrat(@_memaddr, idx) - end - def insert_at(idx, val) - DFHack.memory_vector64_insertat(@_memaddr, idx, val) - end - def delete_at(idx) - DFHack.memory_vector64_deleteat(@_memaddr, idx) - end - end - class StlVector16 < StlVector32 - def length - DFHack.memory_vector16_length(@_memaddr) - end - def valueptr_at(idx) - DFHack.memory_vector16_ptrat(@_memaddr, idx) - end - def insert_at(idx, val) - DFHack.memory_vector16_insertat(@_memaddr, idx, val) - end - def delete_at(idx) - DFHack.memory_vector16_deleteat(@_memaddr, idx) - end - end - class StlVector8 < StlVector32 - def length - DFHack.memory_vector8_length(@_memaddr) - end - def valueptr_at(idx) - DFHack.memory_vector8_ptrat(@_memaddr, idx) - end - def insert_at(idx, val) - DFHack.memory_vector8_insertat(@_memaddr, idx, val) - end - def delete_at(idx) - DFHack.memory_vector8_deleteat(@_memaddr, idx) - end - end - class StlBitVector < StlVector32 - def initialize ; end - def length - DFHack.memory_vectorbool_length(@_memaddr) - end - def insert_at(idx, val) - DFHack.memory_vectorbool_insertat(@_memaddr, idx, val) - end - def delete_at(idx) - DFHack.memory_vectorbool_deleteat(@_memaddr, idx) - end - def [](idx) - idx += length if idx < 0 - DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length - end - def []=(idx, v) - idx += length if idx < 0 - if idx >= length - insert_at(idx, v) - elsif idx < 0 - raise 'index out of bounds' - else - DFHack.memory_vectorbool_setat(@_memaddr, idx, v) - end - end - def self._cpp_new - new._at DFHack.memory_vectorbool_new - end - def _cpp_delete - DFHack.memory_vectorbool_delete(@_memaddr) - end - end - class StlString < MemStruct - def _get - DFHack.memory_read_stlstring(@_memaddr) - end - - def _set(v) - DFHack.memory_write_stlstring(@_memaddr, v) - end - - def self._cpp_new - new._at DFHack.memory_stlstring_new - end - def _cpp_delete - DFHack.memory_stlstring_delete(@_memaddr) - end - def _cpp_init - DFHack.memory_stlstring_init(@_memaddr) - end - end - class StlDeque < MemStruct - attr_accessor :_tglen, :_tg - def initialize(tglen, tg) - @_tglen = tglen - @_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, deques are rare enough. - def inspect ; "#" ; end - end - - class DfFlagarray < MemStruct - attr_accessor :_indexenum - def initialize(indexenum) - @_indexenum = indexenum - end - def length - DFHack.memory_bitarray_length(@_memaddr) - end - # TODO _cpp_init, _cpp_delete - def size ; length ; end - def resize(len) - DFHack.memory_bitarray_resize(@_memaddr, len) - end - def [](idx) - idx = _indexenum.int(idx) if _indexenum - idx += length if idx < 0 - DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length - end - def []=(idx, v) - idx = _indexenum.int(idx) if _indexenum - idx += length if idx < 0 - if idx >= length or idx < 0 - raise 'index out of bounds' - else - DFHack.memory_bitarray_set(@_memaddr, idx, v) - end - end - def inspect - out = "#' - end - - include Enumerable - end - class DfStaticFlagarray < MemStruct - attr_accessor :_indexenum - def initialize(len, indexenum) - @len = len*8 - @_indexenum = indexenum - end - def length - @len - end - def size ; length ; end - def [](idx) - idx = _indexenum.int(idx) if _indexenum - idx += length if idx < 0 - return if idx < 0 or idx >= length - byte = DFHack.memory_read_int8(@_memaddr + idx/8) - (byte & (1 << (idx%8))) > 0 - end - def []=(idx, v) - idx = _indexenum.int(idx) if _indexenum - idx += length if idx < 0 - if idx >= length or idx < 0 - raise 'index out of bounds' - else - byte = DFHack.memory_read_int8(@_memaddr + idx/8) - if (v == nil or v == false or v == 0) - byte &= 0xff ^ (1 << (idx%8)) - else - byte |= (1 << (idx%8)) - end - DFHack.memory_write_int8(@_memaddr + idx/8, byte) - end - end - def inspect - out = "#' - end - - include Enumerable - end - class DfArray < Compound - attr_accessor :_tglen, :_tg - def initialize(tglen, tg) - @_tglen = tglen - @_tg = tg - end - - field(:_ptr, 0) { number $sizeof_ptr, false } - field(:_length, $sizeof_ptr/8) { number 16, false } - - def length ; _length ; end - def size ; _length ; end - # TODO _cpp_init, _cpp_delete - def _tgat(i) - @_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length - end - def [](i) - i += _length if i < 0 - if t = _tgat(i) - t._get - end - end - def []=(i, v) - i += _length if i < 0 - if t = _tgat(i) - t._set(v) - else - raise 'index out of bounds' - end - end - def _set(a) - a.each_with_index { |v, i| self[i] = v } - end - - include Enumerable - end - class DfLinkedList < Compound - attr_accessor :_tg - def initialize(tg) - @_tg = tg - end - - field(:_ptr, 0) { pointer } - field(:_prev, $sizeof_ptr/8) { pointer } - field(:_next, 2*$sizeof_ptr/8) { pointer } - - def item - # With the current xml structure, currently _tg designate - # the type of the 'next' and 'prev' fields, not 'item'. - # List head has item == NULL, so we can safely return nil. - - #addr = _ptr - #return if addr == 0 - #@_tg._at(addr)._get - end - - def item=(v) - #addr = _ptr - #raise 'null pointer' if not addr - #@_tg.at(addr)._set(v) - raise 'null pointer' - end - - def prev - addr = _prev - return if not addr - @_tg._at(addr)._get - end - - def next - addr = _next - return if not addr - @_tg._at(addr)._get - end - alias next= _next= - alias prev= _prev= - - include Enumerable - def each - o = self - while o - yield o.item if o.item - o = o.next - end - end - def inspect ; "#" ; end - end - - class Global < MemStruct - attr_accessor :_glob - def initialize(glob) - @_glob = glob - end - def _at(addr) - g = DFHack.const_get(@_glob) - g = DFHack.rtti_getclassat(g, addr) - g.new._at(addr) - end - def inspect ; "#<#{@_glob}>" ; end - end - - end - - class BooleanEnum - def self.int(v) ; ((v == true) || (v == 1)) ? 1 : 0 ; end - def self.sym(v) ; (!v || (v == 0)) ? false : true ; end - end - - class StlString < MemHack::Compound - field(:str, 0) { stl_string } - - def self.cpp_new(init=nil) - s = MemHack::StlString._cpp_new - s._set(init) if init - new._at(s._memaddr) - end - - def _cpp_delete - MemHack::StlString.new._at(@_memaddr+0)._cpp_delete - @_memaddr = nil - end - end - - class StlSet - attr_accessor :_memaddr, :_enum - def self.cpp_new(init=nil, enum=nil) - ret = new DFHack.memory_stlset_new, enum - init.each { |k| ret.set(k) } if init - ret - end - - def initialize(addr, enum=nil) - addr = nil if addr == 0 - @_memaddr = addr - @_enum = enum - end - - def isset(key) - raise unless @_memaddr - key = @_enum.int(key) if _enum - DFHack.memory_stlset_isset(@_memaddr, key) - end - alias is_set? isset - - def set(key) - raise unless @_memaddr - key = @_enum.int(key) if _enum - DFHack.memory_stlset_set(@_memaddr, key) - end - - def delete(key) - raise unless @_memaddr - key = @_enum.int(key) if _enum - DFHack.memory_stlset_deletekey(@_memaddr, key) - end - - def clear - raise unless @_memaddr - DFHack.memory_stlset_clear(@_memaddr) - end - - def _cpp_delete - raise unless @_memaddr - DFHack.memory_stlset_delete(@_memaddr) - @_memaddr = nil - end - end - - - # cpp rtti name -> rb class - @rtti_n2c = {} - @rtti_c2n = {} - - # cpp rtti name -> vtable ptr - @rtti_n2v = {} - @rtti_v2n = {} - - def self.rtti_n2c ; @rtti_n2c ; end - def self.rtti_c2n ; @rtti_c2n ; end - def self.rtti_n2v ; @rtti_n2v ; end - def self.rtti_v2n ; @rtti_v2n ; end - - # register a ruby class with a cpp rtti class name - def self.rtti_register(cppname, cls) - @rtti_n2c[cppname] = cls - @rtti_c2n[cls] = cppname - end - - # return the ruby class to use for the cpp object at address if rtti info is available - def self.rtti_getclassat(cls, addr) - if addr != 0 and @rtti_c2n[cls] - # rtti info exist for class => cpp object has a vtable - @rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls - else - cls - end - end - - # try to read the rtti classname from an object vtable pointer - def self.rtti_readclassname(vptr) - unless n = @rtti_v2n[vptr] - n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym - @rtti_n2v[n] = vptr - end - n - end - - # return the vtable pointer from the cpp rtti name - def self.rtti_getvtable(cppname) - unless v = @rtti_n2v[cppname] - v = get_vtable(cppname.to_s) - @rtti_n2v[cppname] = v - @rtti_v2n[v] = cppname if v != 0 - end - v if v != 0 - end - - def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0, a5=0) - this = obj._memaddr - vt = df.get_vtable_ptr(this) - fptr = df.memory_read_ptr(vt + voff) - vmethod_do_call(this, fptr, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), - vmethod_arg(a3), vmethod_arg(a4), vmethod_arg(a5)) - end - - def self.vmethod_call_mem_return(obj, voff, r0=0, a0=0, a1=0, a2=0, a3=0, a4=0) - this = obj._memaddr - vt = df.get_vtable_ptr(this) - fptr = df.memory_read_ptr(vt + voff) - vmethod_do_call(vmethod_arg(r0), fptr, this, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), - vmethod_arg(a3), vmethod_arg(a4)) - end - - def self.vmethod_arg(arg) - case arg - when nil, false; 0 - when true; 1 - when Integer; arg - #when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer - when MemHack::Compound, StlSet; arg._memaddr - else raise "bad vmethod arg #{arg.class}" - end - end -end diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp deleted file mode 100644 index 0209806d1..000000000 --- a/plugins/ruby/ruby.cpp +++ /dev/null @@ -1,1237 +0,0 @@ -// blindly copied imports from fastdwarf -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "VersionInfo.h" -#include "MemAccess.h" -#include "DataDefs.h" - -#include "modules/Gui.h" -#include "df/global_objects.h" -#include "df/building.h" -#include "df/item.h" -#include "df/unit.h" - -#include "tinythread.h" - -using namespace DFHack; - - - -// DFHack stuff - - -static int df_loadruby(void); -static void df_unloadruby(void); -static void df_rubythread(void*); -static command_result df_rubyeval(color_ostream &out, std::vector & parameters); -static void ruby_bind_dfhack(void); - -// inter-thread communication stuff -enum RB_command { - RB_IDLE, - RB_INIT, - RB_DIE, - RB_EVAL, -}; -tthread::mutex *m_irun; -tthread::mutex *m_mutex; -static volatile RB_command r_type; -static volatile command_result r_result; -static color_ostream *r_console; // color_ostream given as argument, if NULL resort to console_proxy -static const char *r_command; -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") - - -DFhackDataExport bool plugin_is_enabled = true; - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - plugin_is_enabled = enable; - return CR_OK; -} - - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - onupdate_active = 0; - - // fail silently instead of spamming the console with 'failed to initialize' - // if libruby is not present, the error is still logged in stderr.log - if (!df_loadruby()) - return CR_OK; - - // the ruby thread sleeps trying to lock this - // when it gets it, it runs according to r_type - // when finished, it sets r_type to IDLE and unlocks - m_irun = new tthread::mutex(); - - // when any thread is going to request something to the ruby thread, - // lock this before anything, and release when everything is done - m_mutex = new tthread::mutex(); - - // list of dfhack commands to run when the current ruby run is done (once locks are released) - dfhack_run_queue = new std::vector; - - r_type = RB_INIT; - - // create the dedicated ruby thread - // df_rubythread starts the ruby interpreter and goes to type=IDLE when done - r_thread = new tthread::thread(df_rubythread, 0); - - // wait until init phase 1 is done - while (r_type != RB_IDLE) - tthread::this_thread::yield(); - - // ensure the ruby thread sleeps until we have a command to handle - m_irun->lock(); - - // check return value from rbinit - if (r_result == CR_FAILURE) - return CR_FAILURE; - - commands.push_back(PluginCommand("rb_eval", - "Ruby interpreter. Eval() a ruby string.", - df_rubyeval)); - - commands.push_back(PluginCommand("rb", - "Ruby interpreter. Eval() a ruby string.", - df_rubyeval)); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - // if dlopen failed - if (!r_thread) - return CR_OK; - - // ensure ruby thread is idle - m_mutex->lock(); - - r_type = RB_DIE; - r_command = NULL; - // start ruby thread - m_irun->unlock(); - - // wait until ruby thread ends after RB_DIE - r_thread->join(); - - // cleanup everything - delete r_thread; - r_thread = 0; - delete m_irun; - // we can release m_mutex, other users will check r_thread - m_mutex->unlock(); - delete m_mutex; - delete dfhack_run_queue; - - // dlclose libruby - df_unloadruby(); - - return CR_OK; -} - -static command_result do_plugin_eval_ruby(color_ostream &out, const char *command) -{ - command_result ret; - - // ensure ruby thread is idle - m_mutex->lock(); - if (!r_thread) - // raced with plugin_shutdown - return CR_OK; - - r_type = RB_EVAL; - r_command = command; - r_console = &out; - // wake ruby thread up - m_irun->unlock(); - - // semi-active loop until ruby thread is done - while (r_type != RB_IDLE) - tthread::this_thread::yield(); - - ret = r_result; - r_console = NULL; - - // block ruby thread - m_irun->lock(); - // let other plugin_eval_ruby run - m_mutex->unlock(); - - return ret; -} - -// 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) - { - out.printerr("Failed to load ruby library.\n"); - return CR_FAILURE; - } - - if (!strncmp(command, "nolock ", 7)) { - // debug only! - // run ruby commands without locking the main thread - // useful when the game is frozen after a segfault - 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; - 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 ) -{ - if (!r_thread) - return CR_OK; - - // ruby sets this flag when needed, to avoid lag running ruby code every - // frame if not necessary - if (!onupdate_active) - return CR_OK; - - if (df::global::cur_year && *df::global::cur_year < onupdate_minyear) - return CR_OK; - if (df::global::cur_year_tick && onupdate_minyeartick >= 0 && - *df::global::cur_year_tick < onupdate_minyeartick) - return CR_OK; - if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && - *df::global::cur_year_tick_advmode < onupdate_minyeartickadv) - return CR_OK; - - return plugin_eval_ruby(out, "DFHack.onupdate"); -} - -DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e) -{ - if (!r_thread) - return CR_OK; - - std::string cmd = "DFHack.onstatechange "; - switch (e) { -#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break - case SC_UNKNOWN : return CR_OK; - SCASE(WORLD_LOADED); - SCASE(WORLD_UNLOADED); - SCASE(MAP_LOADED); - SCASE(MAP_UNLOADED); - SCASE(VIEWSCREEN_CHANGED); - SCASE(CORE_INITIALIZED); - // if we go through plugin_eval at BEGIN_UNLOAD, it'll - // try to get the suspend lock and deadlock at df exit - case SC_BEGIN_UNLOAD : return CR_OK; - SCASE(PAUSED); - SCASE(UNPAUSED); -#undef SCASE - } - - return plugin_eval_ruby(out, cmd.c_str()); -} - -static command_result df_rubyeval(color_ostream &out, std::vector & parameters) -{ - if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?")) - { - out.print("This command executes an arbitrary ruby statement.\n"); - return CR_OK; - } - - // reconstruct the text from dfhack console line - std::string full = ""; - - for (unsigned i=0 ; i> 1) -#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func) - -void (*ruby_init_stack)(VALUE*); -void (*ruby_sysinit)(int *, const char ***); -void (*ruby_init)(void); -void (*ruby_init_loadpath)(void); -void (*ruby_script)(const char*); -int (*ruby_cleanup)(int); -ID (*rb_intern)(const char*); -VALUE (*rb_funcall)(VALUE, ID, int, ...); -VALUE (*rb_define_module)(const char*); -void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int); -VALUE (*rb_gv_get)(const char*); -VALUE (*rb_str_new)(const char*, long); -char* (*rb_string_value_ptr)(VALUE*); -VALUE (*rb_eval_string_protect)(const char*, int*); -VALUE (*rb_ary_shift)(VALUE); -VALUE (*rb_float_new)(double); -double (*rb_num2dbl)(VALUE); -VALUE (*rb_int2inum)(intptr_t); // XXX check on win64 long vs intptr_t -VALUE (*rb_uint2inum)(uintptr_t); -uintptr_t (*rb_num2ulong)(VALUE); -// end of rip(ruby.h) - -DFHack::DFLibrary *libruby_handle; - -// load the ruby library, initialize function pointers -static int df_loadruby(void) -{ - const char *libpaths[] = { -#if defined(WIN32) - "./libruby.dll", -#elif defined(__APPLE__) - "hack/libruby.dylib", - "/System/Library/Frameworks/Ruby.framework/Ruby", -#else - "hack/libruby.so", - "libruby.so", -#endif - NULL - }; - - for (const char **path = libpaths; *path; path++) { - if ((libruby_handle = OpenPlugin(*path))) - break; - else - fprintf(stderr, "ruby: warning: Failed to load %s\n", *path); - } - - if (!libruby_handle) { - Core::printerr("Cannot initialize ruby plugin: failed to load ruby library\n"); - return 0; - } - - // ruby_sysinit is optional (ruby1.9 only) - ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit"); -#define rbloadsyma(s,a) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #a))) return 0 -#define rbloadsym(s) rbloadsyma(s,s) - rbloadsym(ruby_init_stack); - rbloadsym(ruby_init); - rbloadsym(ruby_init_loadpath); - rbloadsym(ruby_script); - rbloadsym(ruby_cleanup); - rbloadsym(rb_intern); - rbloadsym(rb_funcall); - rbloadsym(rb_define_module); - rbloadsym(rb_define_singleton_method); - rbloadsym(rb_gv_get); - rbloadsym(rb_str_new); - rbloadsym(rb_string_value_ptr); - rbloadsym(rb_eval_string_protect); - rbloadsym(rb_ary_shift); - rbloadsym(rb_num2dbl); - rbloadsym(rb_int2inum); -#if defined(_WIN64) - rbloadsyma(rb_uint2inum, rb_ull2inum); - rbloadsyma(rb_num2ulong, rb_num2ull); -#else - rbloadsym(rb_uint2inum); - rbloadsym(rb_num2ulong); -#endif - -#undef rbloadsym - // rb_float_new_in_heap in ruby 2 - if (!((rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new"))) || - (rb_float_new = (decltype(rb_float_new))(LookupPlugin(libruby_handle, "rb_float_new_in_heap"))))) - return 0; - - return 1; -} - -static void df_unloadruby(void) -{ - if (libruby_handle) { - ClosePlugin(libruby_handle); - libruby_handle = 0; - } -} - -static void printerr(const char* fmt, const char *arg) -{ - if (r_console) - r_console->printerr(fmt, arg); - else - Core::printerr(fmt, arg); -} - -// ruby thread code -static void dump_rb_error(void) -{ - VALUE s, err; - - err = rb_gv_get("$!"); - - s = rb_funcall(err, rb_intern("class"), 0); - s = rb_funcall(s, rb_intern("name"), 0); - printerr("E: %s: ", rb_string_value_ptr(&s)); - - s = rb_funcall(err, rb_intern("message"), 0); - printerr("%s\n", rb_string_value_ptr(&s)); - - err = rb_funcall(err, rb_intern("backtrace"), 0); - for (int i=0 ; i<8 ; ++i) - if ((s = rb_ary_shift(err)) != Qnil) - printerr(" %s\n", rb_string_value_ptr(&s)); -} - -// ruby thread main loop -static void df_rubythread(void *p) -{ - int state, running; - - // may need to be run from df main thread? - VALUE foo; - ruby_init_stack(&foo); - - if (ruby_sysinit) { - // ruby1.9 specific API - static int argc; - static const char *argv[] = { "dfhack", 0 }; - ruby_sysinit(&argc, (const char ***)&argv); - } - - // initialize the ruby interpreter - ruby_init(); - ruby_init_loadpath(); - // default value for the $0 "current script name" - ruby_script("dfhack"); - - // create the ruby objects to map DFHack to ruby methods - ruby_bind_dfhack(); - - console_proxy = new color_ostream_proxy(Core::getInstance().getConsole()); - - // ensure noone bothers us while we load data defs in the background - m_mutex->lock(); - - // tell the main thread our initialization is finished - r_result = CR_OK; - r_type = RB_IDLE; - - // initialize ruby constants (may depend on libruby compilation flags/version) - Qnil = rb_eval_string_protect("nil", &state); - Qtrue = rb_eval_string_protect("true", &state); - Qfalse = rb_eval_string_protect("false", &state); - - // load the default ruby-level definitions in the background - state=0; - rb_eval_string_protect("require './hack/ruby/ruby'", &state); - if (state) - dump_rb_error(); - - // ready to go - m_mutex->unlock(); - - running = 1; - while (running) { - // sleep waiting for new command - m_irun->lock(); - - switch (r_type) { - case RB_IDLE: - case RB_INIT: - break; - - case RB_DIE: - running = 0; - ruby_cleanup(0); - break; - - case RB_EVAL: - state = 0; - rb_eval_string_protect(r_command, &state); - if (state) - dump_rb_error(); - break; - } - - r_result = CR_OK; - r_type = RB_IDLE; - m_irun->unlock(); - tthread::this_thread::yield(); - } -} - - -#define BOOL_ISFALSE(v) ((v) == Qfalse || (v) == Qnil || (v) == INT2FIX(0)) - -// main DFHack ruby module -static VALUE rb_cDFHack; - - -// DFHack module ruby methods, binds specific dfhack methods - -// df-dfhack version (eg "0.34.11-r2") -static VALUE rb_dfversion(VALUE self) -{ - const char *dfhack_version = Version::dfhack_version(); - return rb_str_new(dfhack_version, strlen(dfhack_version)); -} - -// enable/disable calls to DFHack.onupdate() -static VALUE rb_dfonupdate_active(VALUE self) -{ - if (onupdate_active) - return Qtrue; - else - return Qfalse; -} - -static VALUE rb_dfonupdate_active_set(VALUE self, VALUE val) -{ - onupdate_active = (BOOL_ISFALSE(val) ? 0 : 1); - return Qtrue; -} - -static VALUE rb_dfonupdate_minyear(VALUE self) -{ - return rb_uint2inum(onupdate_minyear); -} - -static VALUE rb_dfonupdate_minyear_set(VALUE self, VALUE val) -{ - onupdate_minyear = rb_num2ulong(val); - return Qtrue; -} - -static VALUE rb_dfonupdate_minyeartick(VALUE self) -{ - return rb_uint2inum(onupdate_minyeartick); -} - -static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val) -{ - onupdate_minyeartick = rb_num2ulong(val); - return Qtrue; -} - -static VALUE rb_dfonupdate_minyeartickadv(VALUE self) -{ - return rb_uint2inum(onupdate_minyeartickadv); -} - -static VALUE rb_dfonupdate_minyeartickadv_set(VALUE self, VALUE val) -{ - onupdate_minyeartickadv = rb_num2ulong(val); - return Qtrue; -} - -static VALUE rb_dfprint_str(VALUE self, VALUE s) -{ - if (r_console) - r_console->print("%s", rb_string_value_ptr(&s)); - else - console_proxy->print("%s", rb_string_value_ptr(&s)); - return Qnil; -} - -static VALUE rb_dfprint_color(VALUE self, VALUE c, VALUE s) -{ - if (r_console) { - color_value old_col = r_console->color(); - r_console->color(color_value(rb_num2ulong(c))); - r_console->print("%s", rb_string_value_ptr(&s)); - r_console->color(old_col); - } else - console_proxy->print("%s", rb_string_value_ptr(&s)); - return Qnil; -} - -static VALUE rb_dfprint_err(VALUE self, VALUE s) -{ - printerr("%s", rb_string_value_ptr(&s)); - return Qnil; -} - -static VALUE rb_dfget_global_address(VALUE self, VALUE name) -{ - return rb_uint2inum(Core::getInstance().vinfo->getAddress(rb_string_value_ptr(&name))); -} - -static VALUE rb_dfget_vtable(VALUE self, VALUE name) -{ - return rb_uint2inum((uintptr_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name))); -} - -// read the c++ class name from a vtable pointer, inspired from doReadClassName -// XXX virtual classes only! dark pointer arithmetic, use with caution ! -static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr) -{ - char *ptr = (char*)rb_num2ulong(vptr); -#if defined(_WIN64) - // win64 - char *rtti = *(char**)(ptr - 0x8); - char *typeinfo = (char*)Core::getInstance().p->getBase() + *(uint32_t*)(rtti + 0xC); - // skip the .?AV, trim @@ from end - return rb_str_new(typeinfo+0x14, strlen(typeinfo+0x14)-2); -#elif defined(WIN32) - // win32 - char *rtti = *(char**)(ptr - 0x4); - char *typeinfo = *(char**)(rtti + 0xC); - // skip the .?AV, trim @@ from end - return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2); -#else - // linux/osx 32/64 - char *typeinfo = *(char**)(ptr - sizeof(void*)); - char *typestring = *(char**)(typeinfo + sizeof(void*)); - while (*typestring >= '0' && *typestring <= '9') - typestring++; - return rb_str_new(typestring, strlen(typestring)); -#endif -} - -static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) -{ - return rb_uint2inum(*(uintptr_t*)rb_num2ulong(objptr)); -} - -static VALUE rb_dfget_selected_building_id(VALUE self) -{ - df::building *b = Gui::getAnyBuilding(Core::getTopViewscreen()); - return rb_int2inum(b ? b->id : -1); -} - -static VALUE rb_dfget_selected_item_id(VALUE self) -{ - df::item *i = Gui::getAnyItem(Core::getTopViewscreen()); - return rb_int2inum(i ? i->id : -1); -} - -static VALUE rb_dfget_selected_unit_id(VALUE self) -{ - df::unit *u = Gui::getAnyUnit(Core::getTopViewscreen()); - return rb_int2inum(u ? u->id : -1); -} - -// run a dfhack command, as if typed from the dfhack console -static VALUE rb_dfhack_run(VALUE self, VALUE cmd) -{ - std::string s; - int strlen = FIX2INT(rb_funcall(cmd, rb_intern("bytesize"), 0)); - s.assign(rb_string_value_ptr(&cmd), strlen); - dfhack_run_queue->push_back(s); - return Qtrue; -} - - - -// raw memory access -// used by the ruby class definitions -// XXX may cause game crash ! double-check your addresses ! - -static VALUE rb_dfmalloc(VALUE self, VALUE len) -{ - char *ptr = (char*)malloc(FIX2INT(len)); - if (!ptr) - return Qnil; - memset(ptr, 0, FIX2INT(len)); - return rb_uint2inum((uintptr_t)ptr); -} - -static VALUE rb_dffree(VALUE self, VALUE ptr) -{ - free((void*)rb_num2ulong(ptr)); - return Qtrue; -} - -// memory reading (buffer) -static VALUE rb_dfmemory_read(VALUE self, VALUE addr, VALUE len) -{ - return rb_str_new((char*)rb_num2ulong(addr), rb_num2ulong(len)); -} - -// memory reading (integers/floats) -static VALUE rb_dfmemory_read_int8(VALUE self, VALUE addr) -{ - return rb_int2inum(*(char*)rb_num2ulong(addr)); -} -static VALUE rb_dfmemory_read_int16(VALUE self, VALUE addr) -{ - return rb_int2inum(*(short*)rb_num2ulong(addr)); -} -static VALUE rb_dfmemory_read_int32(VALUE self, VALUE addr) -{ - return rb_int2inum(*(int*)rb_num2ulong(addr)); -} - -static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr) -{ - return rb_float_new(*(float*)rb_num2ulong(addr)); -} - -static VALUE rb_dfmemory_read_double(VALUE self, VALUE addr) -{ - return rb_float_new(*(double*)rb_num2ulong(addr)); -} - - -// memory writing (buffer) -static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw) -{ - // no stable api for raw.length between rb1.8/rb1.9 ... - int strlen = FIX2INT(rb_funcall(raw, rb_intern("bytesize"), 0)); - - memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen); - - return Qtrue; -} - -// memory writing (integers/floats) -static VALUE rb_dfmemory_write_int8(VALUE self, VALUE addr, VALUE val) -{ - *(char*)rb_num2ulong(addr) = rb_num2ulong(val); - return Qtrue; -} -static VALUE rb_dfmemory_write_int16(VALUE self, VALUE addr, VALUE val) -{ - *(short*)rb_num2ulong(addr) = rb_num2ulong(val); - return Qtrue; -} -static VALUE rb_dfmemory_write_int32(VALUE self, VALUE addr, VALUE val) -{ - *(int*)rb_num2ulong(addr) = rb_num2ulong(val); - return Qtrue; -} - -static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val) -{ - *(float*)rb_num2ulong(addr) = rb_num2dbl(val); - return Qtrue; -} - -static VALUE rb_dfmemory_write_double(VALUE self, VALUE addr, VALUE val) -{ - *(double*)rb_num2ulong(addr) = rb_num2dbl(val); - return Qtrue; -} - -// return memory permissions at address (eg "rx", nil if unmapped) -static VALUE rb_dfmemory_check(VALUE self, VALUE addr) -{ - void *ptr = (void*)rb_num2ulong(addr); - std::vector ranges; - Core::getInstance().p->getMemRanges(ranges); - - unsigned i = 0; - while (i < ranges.size() && ranges[i].end <= ptr) - i++; - - if (i >= ranges.size() || ranges[i].start > ptr || !ranges[i].valid) - return Qnil; - - std::string perm = ""; - if (ranges[i].read) - perm += "r"; - if (ranges[i].write) - perm += "w"; - if (ranges[i].execute) - perm += "x"; - if (ranges[i].shared) - perm += "s"; - - return rb_str_new(perm.c_str(), perm.length()); -} - -// memory write (tmp override page permissions, eg patch code) -static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) -{ - int strlen = FIX2INT(rb_funcall(raw, rb_intern("bytesize"), 0)); - bool ret; - - ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr), - rb_string_value_ptr(&raw), strlen); - - return ret ? Qtrue : Qfalse; -} - -// allocate memory pages -static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len) -{ - void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len)); - - return (ret == (void*)-1) ? Qnil : rb_uint2inum((uintptr_t)ret); -} - -// free memory from pagealloc -static VALUE rb_dfmemory_pagedealloc(VALUE self, VALUE ptr, VALUE len) -{ - int ret = Core::getInstance().p->memDealloc((void*)rb_num2ulong(ptr), rb_num2ulong(len)); - - return ret ? Qfalse : Qtrue; -} - -// change memory page permissions -// ptr must be page-aligned -// prot is a String, eg 'rwx', 'r', 'x' -static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE prot_str) -{ - int ret, prot=0; - char *prot_p = rb_string_value_ptr(&prot_str); - - if (*prot_p == 'r') { - prot |= Process::MemProt::READ; - ++prot_p; - } - if (*prot_p == 'w') { - prot |= Process::MemProt::WRITE; - ++prot_p; - } - if (*prot_p == 'x') { - prot |= Process::MemProt::EXEC; - ++prot_p; - } - - Core::printerr("pageprot %zx %zx %x\n", rb_num2ulong(ptr), rb_num2ulong(len), prot); - ret = Core::getInstance().p->memProtect((void*)rb_num2ulong(ptr), rb_num2ulong(len), prot); - - return ret ? Qfalse : Qtrue; -} - - -// stl::string -static VALUE rb_dfmemory_stlstring_new(VALUE self) -{ - std::string *ptr = new std::string; - return rb_uint2inum((uintptr_t)ptr); -} -static VALUE rb_dfmemory_stlstring_delete(VALUE self, VALUE addr) -{ - std::string *ptr = (std::string*)rb_num2ulong(addr); - if (ptr) - delete ptr; - return Qtrue; -} -static VALUE rb_dfmemory_stlstring_init(VALUE self, VALUE addr) -{ - new((void*)rb_num2ulong(addr)) std::string(); - return Qtrue; -} -static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr) -{ - std::string *s = (std::string*)rb_num2ulong(addr); - return rb_str_new(s->c_str(), s->length()); -} -static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val) -{ - std::string *s = (std::string*)rb_num2ulong(addr); - int strlen = FIX2INT(rb_funcall(val, rb_intern("bytesize"), 0)); - s->assign(rb_string_value_ptr(&val), strlen); - return Qtrue; -} - - -// vector access -static VALUE rb_dfmemory_vec_new(VALUE self) -{ - std::vector *ptr = new std::vector; - return rb_uint2inum((uintptr_t)ptr); -} -static VALUE rb_dfmemory_vec_delete(VALUE self, VALUE addr) -{ - std::vector *ptr = (std::vector*)rb_num2ulong(addr); - if (ptr) - delete ptr; - return Qtrue; -} -static VALUE rb_dfmemory_vec_init(VALUE self, VALUE addr) -{ - new((void*)rb_num2ulong(addr)) std::vector(); - return Qtrue; -} -// vector -static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum(v->size()); -} -static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); -} -static VALUE rb_dfmemory_vec8_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val)); - return Qtrue; -} -static VALUE rb_dfmemory_vec8_deleteat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->erase(v->begin()+FIX2INT(idx)); - return Qtrue; -} - -// vector -static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum(v->size()); -} -static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); -} -static VALUE rb_dfmemory_vec16_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val)); - return Qtrue; -} -static VALUE rb_dfmemory_vec16_deleteat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->erase(v->begin()+FIX2INT(idx)); - return Qtrue; -} - -// vector -static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum(v->size()); -} -static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); -} -static VALUE rb_dfmemory_vec32_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val)); - return Qtrue; -} -static VALUE rb_dfmemory_vec32_deleteat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->erase(v->begin()+FIX2INT(idx)); - return Qtrue; -} - -// vector -static VALUE rb_dfmemory_vec64_length(VALUE self, VALUE addr) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum(v->size()); -} -static VALUE rb_dfmemory_vec64_ptrat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum((uintptr_t)&v->at(FIX2INT(idx))); -} -static VALUE rb_dfmemory_vec64_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val)); - return Qtrue; -} -static VALUE rb_dfmemory_vec64_deleteat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->erase(v->begin()+FIX2INT(idx)); - return Qtrue; -} - -// vector -static VALUE rb_dfmemory_vecbool_new(VALUE self) -{ - std::vector *ptr = new std::vector; - return rb_uint2inum((uintptr_t)ptr); -} -static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr) -{ - std::vector *ptr = (std::vector*)rb_num2ulong(addr); - if (ptr) - delete ptr; - return Qtrue; -} -static VALUE rb_dfmemory_vecbool_init(VALUE self, VALUE addr) -{ - new((void*)rb_num2ulong(addr)) std::vector(); - return Qtrue; -} -static VALUE rb_dfmemory_vecbool_length(VALUE self, VALUE addr) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return rb_uint2inum(v->size()); -} -static VALUE rb_dfmemory_vecbool_at(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - return v->at(FIX2INT(idx)) ? Qtrue : Qfalse; -} -static VALUE rb_dfmemory_vecbool_setat(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->at(FIX2INT(idx)) = (BOOL_ISFALSE(val) ? 0 : 1); - return Qtrue; -} -static VALUE rb_dfmemory_vecbool_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->insert(v->begin()+FIX2INT(idx), (BOOL_ISFALSE(val) ? 0 : 1)); - return Qtrue; -} -static VALUE rb_dfmemory_vecbool_deleteat(VALUE self, VALUE addr, VALUE idx) -{ - std::vector *v = (std::vector*)rb_num2ulong(addr); - v->erase(v->begin()+FIX2INT(idx)); - return Qtrue; -} - -// BitArray -static VALUE rb_dfmemory_bitarray_length(VALUE self, VALUE addr) -{ - DFHack::BitArray *b = (DFHack::BitArray*)rb_num2ulong(addr); - return rb_uint2inum(b->size*8); // b->size is in bytes -} -static VALUE rb_dfmemory_bitarray_resize(VALUE self, VALUE addr, VALUE sz) -{ - DFHack::BitArray *b = (DFHack::BitArray*)rb_num2ulong(addr); - b->resize(rb_num2ulong(sz)); - return Qtrue; -} -static VALUE rb_dfmemory_bitarray_isset(VALUE self, VALUE addr, VALUE idx) -{ - DFHack::BitArray *b = (DFHack::BitArray*)rb_num2ulong(addr); - return b->is_set(rb_num2ulong(idx)) ? Qtrue : Qfalse; -} -static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE val) -{ - DFHack::BitArray *b = (DFHack::BitArray*)rb_num2ulong(addr); - b->set(rb_num2ulong(idx), (BOOL_ISFALSE(val) ? 0 : 1)); - return Qtrue; -} - -// add basic support for std::set used for passing keyboard keys to viewscreens -#include -static VALUE rb_dfmemory_set_new(VALUE self) -{ - std::set *ptr = new std::set; - return rb_uint2inum((uintptr_t)ptr); -} - -static VALUE rb_dfmemory_set_delete(VALUE self, VALUE set) -{ - std::set *ptr = (std::set*)rb_num2ulong(set); - if (ptr) - delete ptr; - return Qtrue; -} - -static VALUE rb_dfmemory_set_set(VALUE self, VALUE set, VALUE key) -{ - std::set *ptr = (std::set*)rb_num2ulong(set); - ptr->insert(rb_num2ulong(key)); - return Qtrue; -} - -static VALUE rb_dfmemory_set_isset(VALUE self, VALUE set, VALUE key) -{ - std::set *ptr = (std::set*)rb_num2ulong(set); - return ptr->count(rb_num2ulong(key)) ? Qtrue : Qfalse; -} - -static VALUE rb_dfmemory_set_deletekey(VALUE self, VALUE set, VALUE key) -{ - std::set *ptr = (std::set*)rb_num2ulong(set); - ptr->erase(rb_num2ulong(key)); - return Qtrue; -} - -static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set) -{ - std::set *ptr = (std::set*)rb_num2ulong(set); - ptr->clear(); - return Qtrue; -} - - -/* call an arbitrary object virtual method */ -#if defined(_WIN32) && !defined(_WIN64) -__declspec(naked) static intptr_t raw_vcall(void *that, void *fptr, uintptr_t a0, - uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) -{ - // __thiscall requires that the callee cleans up the stack - // here we dont know how many arguments it will take, so - // we simply fix esp across the funcall - __asm { - push ebp - mov ebp, esp - - push a5 - push a4 - push a3 - push a2 - push a1 - push a0 - - mov ecx, that - - call fptr - - mov esp, ebp - pop ebp - ret - } -} -#else -static intptr_t raw_vcall(void *that, void *fptr, uintptr_t a0, - uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) -{ - intptr_t (*t_fptr)(void *me, uintptr_t, uintptr_t, uintptr_t, - uintptr_t, uintptr_t, uintptr_t); - t_fptr = (decltype(t_fptr))fptr; - return t_fptr(that, a0, a1, a2, a3, a4, a5); -} -#endif - -// call an arbitrary vmethod, convert args/ret to native values for raw_vcall -static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE fptr, VALUE a0, VALUE a1, VALUE a2, VALUE a3, VALUE a4, VALUE a5) -{ - return rb_int2inum(raw_vcall((void*)rb_num2ulong(cppobj), (void*)rb_num2ulong(fptr), - rb_num2ulong(a0), rb_num2ulong(a1), - rb_num2ulong(a2), rb_num2ulong(a3), - rb_num2ulong(a4), rb_num2ulong(a5))); -} - - -// define module DFHack and its methods -static void ruby_bind_dfhack(void) { - rb_cDFHack = rb_define_module("DFHack"); - - rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdate_active), 0); - rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdate_active_set), 1); - rb_define_singleton_method(rb_cDFHack, "onupdate_minyear", RUBY_METHOD_FUNC(rb_dfonupdate_minyear), 0); - rb_define_singleton_method(rb_cDFHack, "onupdate_minyear=", RUBY_METHOD_FUNC(rb_dfonupdate_minyear_set), 1); - rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick), 0); - rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick_set), 1); - rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv), 0); - rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv_set), 1); - rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1); - rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1); - rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1); - rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1); - rb_define_singleton_method(rb_cDFHack, "get_selected_building_id", RUBY_METHOD_FUNC(rb_dfget_selected_building_id), 0); - rb_define_singleton_method(rb_cDFHack, "get_selected_item_id", RUBY_METHOD_FUNC(rb_dfget_selected_item_id), 0); - rb_define_singleton_method(rb_cDFHack, "get_selected_unit_id", RUBY_METHOD_FUNC(rb_dfget_selected_unit_id), 0); - 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_color", RUBY_METHOD_FUNC(rb_dfprint_color), 2); - rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); - rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); - rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); - rb_define_singleton_method(rb_cDFHack, "pagealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagealloc), 1); - rb_define_singleton_method(rb_cDFHack, "pagedealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagedealloc), 2); - rb_define_singleton_method(rb_cDFHack, "pageprotect", RUBY_METHOD_FUNC(rb_dfmemory_pageprotect), 3); - rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); - rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0); - - rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2); - rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1); - rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1); - rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1); - rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1); - rb_define_singleton_method(rb_cDFHack, "memory_read_double", RUBY_METHOD_FUNC(rb_dfmemory_read_double), 1); - - rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2); - rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2); - rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2); - rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2); - rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2); - rb_define_singleton_method(rb_cDFHack, "memory_write_double", RUBY_METHOD_FUNC(rb_dfmemory_write_double), 2); - rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1); - rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2); - - rb_define_singleton_method(rb_cDFHack, "memory_stlstring_new", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_new), 0); - rb_define_singleton_method(rb_cDFHack, "memory_stlstring_delete", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_delete), 1); - rb_define_singleton_method(rb_cDFHack, "memory_stlstring_init", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_init), 1); - rb_define_singleton_method(rb_cDFHack, "memory_read_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_read_stlstring), 1); - rb_define_singleton_method(rb_cDFHack, "memory_write_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_write_stlstring), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector_new", RUBY_METHOD_FUNC(rb_dfmemory_vec_new), 0); - rb_define_singleton_method(rb_cDFHack, "memory_vector_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec_delete), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vector_init", RUBY_METHOD_FUNC(rb_dfmemory_vec_init), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vector8_length", RUBY_METHOD_FUNC(rb_dfmemory_vec8_length), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vector8_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_ptrat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector8_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_insertat), 3); - rb_define_singleton_method(rb_cDFHack, "memory_vector8_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_deleteat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector16_length", RUBY_METHOD_FUNC(rb_dfmemory_vec16_length), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vector16_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_ptrat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector16_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_insertat), 3); - rb_define_singleton_method(rb_cDFHack, "memory_vector16_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_deleteat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector32_length", RUBY_METHOD_FUNC(rb_dfmemory_vec32_length), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector32_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insertat), 3); - rb_define_singleton_method(rb_cDFHack, "memory_vector32_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_deleteat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector64_length", RUBY_METHOD_FUNC(rb_dfmemory_vec64_length), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vector64_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_ptrat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vector64_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_insertat), 3); - rb_define_singleton_method(rb_cDFHack, "memory_vector64_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec64_deleteat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_new", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_new), 0); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_init", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_init), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_length", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_length), 1); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_at", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_at), 2); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_setat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_setat), 3); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_insertat), 3); - rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_deleteat), 2); - rb_define_singleton_method(rb_cDFHack, "memory_bitarray_length", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_length), 1); - rb_define_singleton_method(rb_cDFHack, "memory_bitarray_resize", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_resize), 2); - rb_define_singleton_method(rb_cDFHack, "memory_bitarray_isset", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_isset), 2); - rb_define_singleton_method(rb_cDFHack, "memory_bitarray_set", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_set), 3); - rb_define_singleton_method(rb_cDFHack, "memory_stlset_new", RUBY_METHOD_FUNC(rb_dfmemory_set_new), 0); - rb_define_singleton_method(rb_cDFHack, "memory_stlset_delete", RUBY_METHOD_FUNC(rb_dfmemory_set_delete), 1); - rb_define_singleton_method(rb_cDFHack, "memory_stlset_set", RUBY_METHOD_FUNC(rb_dfmemory_set_set), 2); - rb_define_singleton_method(rb_cDFHack, "memory_stlset_isset", RUBY_METHOD_FUNC(rb_dfmemory_set_isset), 2); - rb_define_singleton_method(rb_cDFHack, "memory_stlset_deletekey", RUBY_METHOD_FUNC(rb_dfmemory_set_deletekey), 2); - rb_define_singleton_method(rb_cDFHack, "memory_stlset_clear", RUBY_METHOD_FUNC(rb_dfmemory_set_clear), 1); -} diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb deleted file mode 100644 index 663f85111..000000000 --- a/plugins/ruby/ruby.rb +++ /dev/null @@ -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/ } diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb deleted file mode 100644 index 9268dcb9e..000000000 --- a/plugins/ruby/ui.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb deleted file mode 100644 index b27f2f0c3..000000000 --- a/plugins/ruby/unit.rb +++ /dev/null @@ -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 diff --git a/plugins/ruby/win32/.gitignore b/plugins/ruby/win32/.gitignore deleted file mode 100644 index ef44e3942..000000000 --- a/plugins/ruby/win32/.gitignore +++ /dev/null @@ -1 +0,0 @@ -libruby* diff --git a/plugins/ruby/win64/.gitignore b/plugins/ruby/win64/.gitignore deleted file mode 100644 index ef44e3942..000000000 --- a/plugins/ruby/win64/.gitignore +++ /dev/null @@ -1 +0,0 @@ -libruby*