diff --git a/.gitignore b/.gitignore index 81b55ffdb..ceb0aa27a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ build/library build/tools build/plugins build/depends +build/install_manifest.txt #ignore Kdevelop stuff .kdev4 @@ -49,6 +50,10 @@ dfhack/python/PyDFHack.egg-info dfhack/python/build dfhack/python/dist +# Ruby binding binaries +plugins/ruby/libruby* +plugins/ruby/ruby-autogen.rb + # CPack stuff build/CPack*Config.cmake diff --git a/LUA_API.rst b/LUA_API.rst index 5fc653bb3..2bb2c949e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -17,7 +17,7 @@ are treated by DFHack command line prompt almost as native C++ commands, and invoked by plugins written in c++. This document describes native API available to Lua in detail. -For the most part it does not describe utility functions +It does not describe all of the utility functions implemented by Lua files located in hack/lua/... @@ -1323,9 +1323,9 @@ Features: order using ``dfhack.safecall``. -======= -Modules -======= +=========== +Lua Modules +=========== DFHack sets up the lua interpreter so that the built-in ``require`` function can be used to load shared lua code from hack/lua/. @@ -1333,7 +1333,7 @@ The ``dfhack`` namespace reference itself may be obtained via ``require('dfhack')``, although it is initially created as a global by C++ bootstrap code. -The following functions are provided: +The following module management functions are provided: * ``mkmodule(name)`` @@ -1357,6 +1357,194 @@ The following functions are provided: should be kept limited to the standard Lua library and API described in this document. +Global environment +================== + +A number of variables and functions are provided in the base global +environment by the mandatory init file dfhack.lua: + +* Color constants + + These are applicable both for ``dfhack.color()`` and color fields + in DF functions or structures: + + COLOR_RESET, COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, + COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_GREY, COLOR_DARKGREY, + COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED, + COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE + +* ``dfhack.onStateChange`` event codes + + Available only in the core context, as is the event itself: + + SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED, + SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED + +* Functions already described above + + safecall, qerror, mkmodule, reload + +* ``printall(obj)`` + + If the argument is a lua table or DF object reference, prints all fields. + +* ``copyall(obj)`` + + Returns a shallow copy of the table or reference as a lua table. + +* ``pos2xyz(obj)`` + + The object must have fields x, y and z. Returns them as 3 values. + If obj is *nil*, or x is -30000 (the usual marker for undefined + coordinates), returns *nil*. + +* ``xyz2pos(x,y,z)`` + + Returns a table with x, y and z as fields. + +* ``safe_index(obj,index...)`` + + Walks a sequence of dereferences, which may be represented by numbers or strings. + Returns *nil* if any of obj or indices is *nil*, or a numeric index is out of array bounds. + +utils +===== + +* ``utils.compare(a,b)`` + + Comparator function; returns *-1* if ab, *0* otherwise. + +* ``utils.compare_name(a,b)`` + + Comparator for names; compares empty string last. + +* ``utils.is_container(obj)`` + + Checks if obj is a container ref. + +* ``utils.make_index_sequence(start,end)`` + + Returns a lua sequence of numbers in start..end. + +* ``utils.make_sort_order(data, ordering)`` + + Computes a sorted permutation of objects in data, as a table of integer + indices into the data sequence. Uses ``data.n`` as input length + if present. + + The ordering argument is a sequence of ordering specs, represented + as lua tables with following possible fields: + + ord.key = *function(value)* + Computes comparison key from input data value. Not called on nil. + If omitted, the comparison key is the value itself. + ord.key_table = *function(data)* + Computes a key table from the data table in one go. + ord.compare = *function(a,b)* + Comparison function. Defaults to ``utils.compare`` above. + Called on non-nil keys; nil sorts last. + ord.nil_first = *true/false* + If true, nil keys are sorted first instead of last. + ord.reverse = *true/false* + If true, sort non-nil keys in descending order. + + For every comparison during sorting the specs are applied in + order until an unambiguous decision is reached. Sorting is stable. + + Example of sorting a sequence by field foo:: + + local spec = { key = function(v) return v.foo end } + local order = utils.make_sort_order(data, { spec }) + local output = {} + for i = 1,#order do output[i] = data[order[i]] end + + Separating the actual reordering of the sequence in this + way enables applying the same permutation to multiple arrays. + This function is used by the sort plugin. + +* ``utils.assign(tgt, src)`` + + Does a recursive assignment of src into tgt. + Uses ``df.assign`` if tgt is a native object ref; otherwise + recurses into lua tables. + +* ``utils.clone(obj, deep)`` + + Performs a shallow, or semi-deep copy of the object as a lua table tree. + The deep mode recurses into lua tables and subobjects, except pointers + to other heap objects. + Null pointers are represented as df.NULL. Zero-based native containers + are converted to 1-based lua sequences. + +* ``utils.clone_with_default(obj, default, force)`` + + Copies the object, using the ``default`` lua table tree + as a guide to which values should be skipped as uninteresting. + The ``force`` argument makes it always return a non-*nil* value. + +* ``utils.sort_vector(vector,field,cmpfun)`` + + Sorts a native vector or lua sequence using the comparator function. + If ``field`` is not *nil*, applies the comparator to the field instead + of the whole object. + +* ``utils.binsearch(vector,key,field,cmpfun,min,max)`` + + Does a binary search in a native vector or lua sequence for + ``key``, using ``cmpfun`` and ``field`` like sort_vector. + If ``min`` and ``max`` are specified, they are used as the + search subrange bounds. + + If found, returns *item, true, idx*. Otherwise returns + *nil, false, insert_idx*, where *insert_idx* is the correct + insertion point. + +* ``utils.insert_sorted(vector,item,field,cmpfun)`` + + Does a binary search, and inserts item if not found. + Returns *did_insert, vector[idx], idx*. + +* ``utils.insert_or_update(vector,item,field,cmpfun)`` + + Like ``insert_sorted``, but also assigns the item into + the vector cell if insertion didn't happen. + + As an example, you can use this to set skill values:: + + utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id') + + (For an explanation of ``new=true``, see table assignment in the wrapper section) + +* ``utils.prompt_yes_no(prompt, default)`` + + Presents a yes/no prompt to the user. If ``default`` is not *nil*, + allows just pressing Enter to submit the default choice. + If the user enters ``'abort'``, throws an error. + +* ``utils.prompt_input(prompt, checkfun, quit_str)`` + + Presents a prompt to input data, until a valid string is entered. + Once ``checkfun(input)`` returns *true, ...*, passes the values + through. If the user enters the quit_str (defaults to ``'~~~'``), + throws an error. + +* ``utils.check_number(text)`` + + A ``prompt_input`` ``checkfun`` that verifies a number input. + +dumper +====== + +A third-party lua table dumper module from +http://lua-users.org/wiki/DataDumper. Defines one +function: + +* ``dumper.DataDumper(value, varname, fastmode, ident, indent_step)`` + + Returns ``value`` converted to a string. The ``indent_step`` + argument specifies the indentation step size in spaces. For + the other arguments see the original documentation link above. + ======= Plugins @@ -1430,6 +1618,9 @@ are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans the extension. +If the first line of the script is a one-line comment, it is +used by the built-in ``ls`` and ``help`` commands. + **NOTE:** Scripts placed in subdirectories still can be accessed, but do not clutter the ``ls`` command list; thus it is preferred for obscure developer-oriented scripts and scripts used by tools. diff --git a/Lua API.html b/Lua API.html index 04e899366..2c9a6a8df 100644 --- a/Lua API.html +++ b/Lua API.html @@ -360,13 +360,18 @@ ul.auto-toc { -
  • Modules
  • -
  • Plugins

    The current version of DFHack has extensive support for @@ -381,7 +386,7 @@ structures, and interaction with dfhack itself.

  • are treated by DFHack command line prompt almost as native C++ commands, and invoked by plugins written in c++.

    This document describes native API available to Lua in detail. -For the most part it does not describe utility functions +It does not describe all of the utility functions implemented by Lua files located in hack/lua/...

    DF data structure wrapper

    @@ -1480,14 +1485,14 @@ order using dfhack.safecall.

    -
    -

    Modules

    +
    +

    Lua Modules

    DFHack sets up the lua interpreter so that the built-in require function can be used to load shared lua code from hack/lua/. The dfhack namespace reference itself may be obtained via require('dfhack'), although it is initially created as a global by C++ bootstrap code.

    -

    The following functions are provided:

    +

    The following module management functions are provided:

    • mkmodule(name)

      Creates an environment table for the module. Intended to be used as:

      @@ -1509,16 +1514,183 @@ should be kept limited to the standard Lua library and API described in this document.

    +
    +

    Global environment

    +

    A number of variables and functions are provided in the base global +environment by the mandatory init file dfhack.lua:

    +
      +
    • Color constants

      +

      These are applicable both for dfhack.color() and color fields +in DF functions or structures:

      +

      COLOR_RESET, COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, +COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_GREY, COLOR_DARKGREY, +COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED, +COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE

      +
    • +
    • dfhack.onStateChange event codes

      +

      Available only in the core context, as is the event itself:

      +

      SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED, +SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED

      +
    • +
    • Functions already described above

      +

      safecall, qerror, mkmodule, reload

      +
    • +
    • printall(obj)

      +

      If the argument is a lua table or DF object reference, prints all fields.

      +
    • +
    • copyall(obj)

      +

      Returns a shallow copy of the table or reference as a lua table.

      +
    • +
    • pos2xyz(obj)

      +

      The object must have fields x, y and z. Returns them as 3 values. +If obj is nil, or x is -30000 (the usual marker for undefined +coordinates), returns nil.

      +
    • +
    • xyz2pos(x,y,z)

      +

      Returns a table with x, y and z as fields.

      +
    • +
    • safe_index(obj,index...)

      +

      Walks a sequence of dereferences, which may be represented by numbers or strings. +Returns nil if any of obj or indices is nil, or a numeric index is out of array bounds.

      +
    • +
    +
    +
    +

    utils

    +
      +
    • utils.compare(a,b)

      +

      Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

      +
    • +
    • utils.compare_name(a,b)

      +

      Comparator for names; compares empty string last.

      +
    • +
    • utils.is_container(obj)

      +

      Checks if obj is a container ref.

      +
    • +
    • utils.make_index_sequence(start,end)

      +

      Returns a lua sequence of numbers in start..end.

      +
    • +
    • utils.make_sort_order(data, ordering)

      +

      Computes a sorted permutation of objects in data, as a table of integer +indices into the data sequence. Uses data.n as input length +if present.

      +

      The ordering argument is a sequence of ordering specs, represented +as lua tables with following possible fields:

      +
      +
      ord.key = function(value)
      +

      Computes comparison key from input data value. Not called on nil. +If omitted, the comparison key is the value itself.

      +
      +
      ord.key_table = function(data)
      +

      Computes a key table from the data table in one go.

      +
      +
      ord.compare = function(a,b)
      +

      Comparison function. Defaults to utils.compare above. +Called on non-nil keys; nil sorts last.

      +
      +
      ord.nil_first = true/false
      +

      If true, nil keys are sorted first instead of last.

      +
      +
      ord.reverse = true/false
      +

      If true, sort non-nil keys in descending order.

      +
      +
      +

      For every comparison during sorting the specs are applied in +order until an unambiguous decision is reached. Sorting is stable.

      +

      Example of sorting a sequence by field foo:

      +
      +local spec = { key = function(v) return v.foo end }
      +local order = utils.make_sort_order(data, { spec })
      +local output = {}
      +for i = 1,#order do output[i] = data[order[i]] end
      +
      +

      Separating the actual reordering of the sequence in this +way enables applying the same permutation to multiple arrays. +This function is used by the sort plugin.

      +
    • +
    • utils.assign(tgt, src)

      +

      Does a recursive assignment of src into tgt. +Uses df.assign if tgt is a native object ref; otherwise +recurses into lua tables.

      +
    • +
    • utils.clone(obj, deep)

      +

      Performs a shallow, or semi-deep copy of the object as a lua table tree. +The deep mode recurses into lua tables and subobjects, except pointers +to other heap objects. +Null pointers are represented as df.NULL. Zero-based native containers +are converted to 1-based lua sequences.

      +
    • +
    • utils.clone_with_default(obj, default, force)

      +

      Copies the object, using the default lua table tree +as a guide to which values should be skipped as uninteresting. +The force argument makes it always return a non-nil value.

      +
    • +
    • utils.sort_vector(vector,field,cmpfun)

      +

      Sorts a native vector or lua sequence using the comparator function. +If field is not nil, applies the comparator to the field instead +of the whole object.

      +
    • +
    • utils.binsearch(vector,key,field,cmpfun,min,max)

      +

      Does a binary search in a native vector or lua sequence for +key, using cmpfun and field like sort_vector. +If min and max are specified, they are used as the +search subrange bounds.

      +

      If found, returns item, true, idx. Otherwise returns +nil, false, insert_idx, where insert_idx is the correct +insertion point.

      +
    • +
    • utils.insert_sorted(vector,item,field,cmpfun)

      +

      Does a binary search, and inserts item if not found. +Returns did_insert, vector[idx], idx.

      +
    • +
    • utils.insert_or_update(vector,item,field,cmpfun)

      +

      Like insert_sorted, but also assigns the item into +the vector cell if insertion didn't happen.

      +

      As an example, you can use this to set skill values:

      +
      +utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')
      +
      +

      (For an explanation of new=true, see table assignment in the wrapper section)

      +
    • +
    • utils.prompt_yes_no(prompt, default)

      +

      Presents a yes/no prompt to the user. If default is not nil, +allows just pressing Enter to submit the default choice. +If the user enters 'abort', throws an error.

      +
    • +
    • utils.prompt_input(prompt, checkfun, quit_str)

      +

      Presents a prompt to input data, until a valid string is entered. +Once checkfun(input) returns true, ..., passes the values +through. If the user enters the quit_str (defaults to '~~~'), +throws an error.

      +
    • +
    • utils.check_number(text)

      +

      A prompt_input checkfun that verifies a number input.

      +
    • +
    +
    +
    +

    dumper

    +

    A third-party lua table dumper module from +http://lua-users.org/wiki/DataDumper. Defines one +function:

    +
      +
    • dumper.DataDumper(value, varname, fastmode, ident, indent_step)

      +

      Returns value converted to a string. The indent_step +argument specifies the indentation step size in spaces. For +the other arguments see the original documentation link above.

      +
    • +
    +
    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -1556,17 +1728,19 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    -

    Scripts

    +

    Scripts

    Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans the extension.

    +

    If the first line of the script is a one-line comment, it is +used by the built-in ls and help commands.

    NOTE: Scripts placed in subdirectories still can be accessed, but do not clutter the ls command list; thus it is preferred for obscure developer-oriented scripts and scripts used by tools. diff --git a/library/Core.cpp b/library/Core.cpp index 09344135c..826576b77 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -281,7 +281,7 @@ static command_result runLuaScript(color_ostream &out, std::string name, vector< return ok ? CR_OK : CR_FAILURE; } -static command_result runRubyScript(PluginManager *plug_mgr, std::string name, vector &args) +static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string name, vector &args) { std::string rbcmd = "$script_args = ["; for (size_t i = 0; i < args.size(); i++) @@ -290,7 +290,7 @@ static command_result runRubyScript(PluginManager *plug_mgr, std::string name, v rbcmd += "load './hack/scripts/" + name + ".rb'"; - return plug_mgr->eval_ruby(rbcmd.c_str()); + return plug_mgr->eval_ruby(out, rbcmd.c_str()); } command_result Core::runCommand(color_ostream &out, const std::string &command) @@ -632,7 +632,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve if (fileExists(filename + ".lua")) res = runLuaScript(con, first, parts); else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) - res = runRubyScript(plug_mgr, first, parts); + res = runRubyScript(con, plug_mgr, first, parts); else con.printerr("%s is not a recognized command.\n", first.c_str()); } @@ -752,6 +752,7 @@ Core::Core() misc_data_mutex=0; last_world_data_ptr = NULL; last_local_map_ptr = NULL; + last_pause_state = false; top_viewscreen = NULL; screen_window = NULL; server = NULL; @@ -1116,6 +1117,15 @@ int Core::Update() } } + if (df::global::pause_state) + { + if (*df::global::pause_state != last_pause_state) + { + onStateChange(out, last_pause_state ? SC_UNPAUSED : SC_PAUSED); + last_pause_state = *df::global::pause_state; + } + } + // Execute per-frame handlers onUpdate(out); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a314883e1..ff7524318 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -188,7 +188,7 @@ bool Plugin::load(color_ostream &con) plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown"); plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange"); plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); - plugin_eval_ruby = (command_result (*)(const char*)) LookupPlugin(plug, "plugin_eval_ruby"); + plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); index_lua(plug); this->name = *plug_name; plugin_lib = plug; diff --git a/library/include/Core.h b/library/include/Core.h index 653298d8f..d25beef5f 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -75,7 +75,9 @@ namespace DFHack SC_MAP_UNLOADED = 3, SC_VIEWSCREEN_CHANGED = 4, SC_CORE_INITIALIZED = 5, - SC_BEGIN_UNLOAD = 6 + SC_BEGIN_UNLOAD = 6, + SC_PAUSED = 7, + SC_UNPAUSED = 8 }; // Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF. @@ -228,6 +230,7 @@ namespace DFHack // for state change tracking void *last_local_map_ptr; df::viewscreen *top_viewscreen; + bool last_pause_state; // Very important! bool started; diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 5da9fc92f..22171a15c 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -209,7 +209,7 @@ namespace DFHack command_result (*plugin_onupdate)(color_ostream &); command_result (*plugin_onstatechange)(color_ostream &, state_change_event); RPCService* (*plugin_rpcconnect)(color_ostream &); - command_result (*plugin_eval_ruby)(const char*); + command_result (*plugin_eval_ruby)(color_ostream &, const char*); }; class DFHACK_EXPORT PluginManager { @@ -238,7 +238,7 @@ namespace DFHack { return all_plugins.size(); } - command_result (*eval_ruby)(const char*); + command_result (*eval_ruby)(color_ostream &, const char*); // DATA private: tthread::mutex * cmdlist_mutex; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index d56d4df60..86ea1459f 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -39,6 +39,8 @@ if dfhack.is_core_context then SC_MAP_UNLOADED = 3 SC_VIEWSCREEN_CHANGED = 4 SC_CORE_INITIALIZED = 5 + SC_PAUSED = 7 + SC_UNPAUSED = 8 end -- Error handling diff --git a/library/lua/utils.lua b/library/lua/utils.lua index f303091d6..38a1e6c42 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -57,10 +57,10 @@ function is_container(obj) end -- Make a sequence of numbers in 1..size -function make_index_sequence(size) +function make_index_sequence(istart,iend) local index = {} - for i=1,size do - index[i] = i + for i=istart,iend do + index[i-istart+1] = i end return index end @@ -114,7 +114,7 @@ function make_sort_order(data,ordering) end -- Make an order table - local index = make_index_sequence(size) + local index = make_index_sequence(1,size) -- Sort the ordering table table.sort(index, function(ia,ib) @@ -379,7 +379,7 @@ function prompt_yes_no(msg,default) elseif string.match(rv,'^[Nn]') then return false elseif rv == 'abort' then - qerror('User abort in utils.prompt_yes_no()') + qerror('User abort') elseif rv == '' and default ~= nil then return default end @@ -393,7 +393,7 @@ function prompt_input(prompt,check,quit_str) while true do local rv = dfhack.lineedit(prompt) if rv == quit_str then - return nil + qerror('User abort') end local rtbl = table.pack(check(rv)) if rtbl[1] then diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index f8f99f81e..50cf21a9c 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -293,6 +293,12 @@ std::string MaterialInfo::getToken() switch (mode) { case Builtin: + if (material->id == "COAL") { + if (index == 0) + return "COAL:COKE"; + else if (index == 1) + return "COAL:CHARCOAL"; + } return material->id; case Inorganic: return "INORGANIC:" + inorganic->id; diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index 951468bc7..752d07cf6 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -36,7 +36,7 @@ function GetTextRegion() --if num>=100 then --print(string.format("%d %x->%x %s %s",k,v["start"],v["end"],v.name or "",flgs)) --end - local pos=string.find(v.name,".text") or string.find(v.name,"libs/Dwarf_Fortress") + local pos=string.find(v.name,"Dwarf Fortress.exe") or string.find(v.name,"libs/Dwarf_Fortress") if(pos~=nil) and v["execute"] then __TEXT=v; return v; @@ -99,6 +99,7 @@ function SetExecute(pos) UpdateRanges() local reg=GetRegionIn(pos) reg.execute=true + reg["write"]=true Process.setPermisions(reg,reg) -- TODO maybe make a page with only execute permisions or sth end -- engine bindings @@ -224,6 +225,11 @@ function engine.LoadModData(file) end return T2 end +function engine.FindMarkerCall(moddata,name) + if moddata.symbols[name] ~=nil then + return moddata.symbols[name]+1 + end +end function engine.FindMarker(moddata,name) if moddata.symbols[name] ~=nil then return engine.findmarker(0xDEADBEEF,moddata.data,moddata.size,moddata.symbols[name]) diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index e68684bf7..6fa86d7ac 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -81,7 +81,7 @@ table.insert(plugins,{"migrants","multi race imigrations"}) --table.insert(plugins,{"onfunction","run lua on some df function"}) --table.insert(plugins,{"editor","edit internals of df",EditDF}) table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) -table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"}) +table.insert(plugins,{"adv_tools","some tools for (mainly) adventurer hacking"}) loadall(plugins) dofile_silent("dfusion/initcustom.lua") diff --git a/plugins/Dfusion/luafiles/onfunction/compile.bat b/plugins/Dfusion/luafiles/onfunction/compile.bat deleted file mode 100644 index f06fb8c4c..000000000 --- a/plugins/Dfusion/luafiles/onfunction/compile.bat +++ /dev/null @@ -1 +0,0 @@ -as -anl --32 -o functions.o functions.asm \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/onfunction/functions.asm b/plugins/Dfusion/luafiles/onfunction/functions.asm deleted file mode 100644 index 13ef23191..000000000 --- a/plugins/Dfusion/luafiles/onfunction/functions.asm +++ /dev/null @@ -1,23 +0,0 @@ -.intel_syntax -push eax -push ebp -push esp -push esi -push edi -push edx -push ecx -push ebx -push eax -mov eax,[esp+36] -push eax -function: -call 0xdeadbee0 -function2: -mov [0xdeadbeef],eax -pop eax -function3: -jmp [0xdeadbeef] - - - - diff --git a/plugins/Dfusion/luafiles/onfunction/functions.o b/plugins/Dfusion/luafiles/onfunction/functions.o deleted file mode 100644 index 7b7d4a33f..000000000 Binary files a/plugins/Dfusion/luafiles/onfunction/functions.o and /dev/null differ diff --git a/plugins/Dfusion/luafiles/onfunction/init.lua b/plugins/Dfusion/luafiles/onfunction/init.lua deleted file mode 100644 index a32f6efc7..000000000 --- a/plugins/Dfusion/luafiles/onfunction/init.lua +++ /dev/null @@ -1,68 +0,0 @@ -onfunction=onfunction or {} -function onfunction.install() - ModData=engine.installMod("dfusion/onfunction/functions.o","functions",4) - modpos=ModData.pos - modsize=ModData.size - onfunction.pos=modpos - trgpos=engine.getpushvalue() - print(string.format("Function installed in:%x function to call is: %x",modpos,trgpos)) - local firstpos=modpos+engine.FindMarker(ModData,"function") - engine.poked(firstpos,trgpos-firstpos-4) --call Lua-Onfunction - onfunction.fpos=modpos+engine.FindMarker(ModData,"function3") - engine.poked(modpos+engine.FindMarker(ModData,"function2"),modpos+modsize) - engine.poked(onfunction.fpos,modpos+modsize) - SetExecute(modpos) - onfunction.calls={} - onfunction.functions={} - onfunction.names={} - onfunction.hints={} -end -function OnFunction(values) - --[=[print("Onfunction called!") - print("Data:") - for k,v in pairs(values) do - print(string.format("%s=%x",k,v)) - end - print("stack:") - for i=0,3 do - print(string.format("%d %x",i,engine.peekd(values.esp+i*4))) - end - --]=] - if onfunction.functions[values.ret] ~=nil then - onfunction.functions[values.ret](values) - end - - return onfunction.calls[values.ret] --returns real function to call -end -function onfunction.patch(addr) - - if(engine.peekb(addr)~=0xe8) then - error("Incorrect address, not a function call") - else - onfunction.calls[addr+5]=addr+engine.peekd(addr+1)+5 --adds real function to call - engine.poked(addr+1,engine.getmod("functions")-addr-5) - end -end -function onfunction.AddFunction(addr,name,hints) - onfunction.patch(addr) - onfunction.names[name]=addr+5 - if hints~=nil then - onfunction.hints[name]=hints - end -end -function onfunction.ReadHint(values,name,hintname) - local hints=onfunction.hints[name] - if hints ==nil then return nil end - local hint=hints[hintname] - if type(hint)=="string" then return values[hint] end - local off=hint.off or 0 - return engine.peek(off+values[hint.reg],hints[hintname].rtype) -end -function onfunction.SetCallback(name,func) - if onfunction.names[name]==nil then - error("No such function:"..name) - else - onfunction.functions[onfunction.names[name]]=func - end -end - diff --git a/plugins/Dfusion/luafiles/onfunction/locations.lua b/plugins/Dfusion/luafiles/onfunction/locations.lua deleted file mode 100644 index 362bfd7ab..000000000 --- a/plugins/Dfusion/luafiles/onfunction/locations.lua +++ /dev/null @@ -1,16 +0,0 @@ -if WINDOWS then --windows function defintions - --[=[onfunction.AddFunction(0x55499D+offsets.base(),"Move") --on creature move found with "watch mem=xcoord" - onfunction.AddFunction(0x275933+offsets.base(),"Die",{creature="edi"}) --on creature death? found by watching dead flag then stepping until new function - onfunction.AddFunction(0x2c1834+offsets.base(),"CreateCreature",{protocreature="eax"}) --arena - onfunction.AddFunction(0x349640+offsets.base(),"AddItem",{item="esp"}) --or esp - onfunction.AddFunction(0x26e840+offsets.base(),"Dig_Create",{item_type="esp"}) --esp+8 -> material esp->block type - onfunction.AddFunction(0x3d4301+offsets.base(),"Make_Item",{item_type="esp"}) - onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}}) - onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"}) - onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=] - --onfunction.AddFunction(0x4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? 0.34.07 - onfunction.AddFunction(0x72aB6+offsets.base(),"Die",{creature="edi"}) --0.34.07 -else --linux - --[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... - onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=] -end diff --git a/plugins/Dfusion/luafiles/onfunction/plugin.lua b/plugins/Dfusion/luafiles/onfunction/plugin.lua deleted file mode 100644 index 60360817c..000000000 --- a/plugins/Dfusion/luafiles/onfunction/plugin.lua +++ /dev/null @@ -1,15 +0,0 @@ -mypos=engine.getmod("functions") -function DeathMsg(values) - local name - local u=engine.cast(df.unit,values[onfunction.hints["Die"].creature]) - - print(u.name.first_name.." died") -end -if mypos then - print("Onfunction already installed") - --onfunction.patch(0x189dd6+offsets.base()) -else - onfunction.install() - dofile("dfusion/onfunction/locations.lua") - onfunction.SetCallback("Die",DeathMsg) -end diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 536b2501b..cfb73fa8b 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -140,7 +140,7 @@ static command_result autodump_main(color_ostream &out, vector & parame return CR_FAILURE; } df::tiletype ttype = MC.tiletypeAt(pos_cursor); - if(!DFHack::isFloorTerrain(ttype)) + if(!DFHack::isWalkable(ttype) || DFHack::isOpenTerrain(ttype)) { out.printerr("Cursor should be placed over a floor.\n"); return CR_FAILURE; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index de1a1aef6..3bae1e1b3 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -740,7 +740,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else if (df::enums::building_type::TradeDepot == type) { df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->flags.bits.trader_requested; + trader_requested = depot->trade_flags.bits.trader_requested; if (print_debug) out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); } diff --git a/plugins/probe.cpp b/plugins/probe.cpp index b7b7d298f..2ae6846d5 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -221,10 +221,11 @@ command_result df_probe (color_ostream &out, vector & parameters) out.print("temperature2: %d U\n",mc.temperature2At(cursor)); int offset = block.region_offset[des.bits.biome]; - df::coord2d region_pos = block.region_pos + df::coord2d ((offset % 3) - 1, (offset / 3) -1); + int bx = clip_range(block.region_pos.x + (offset % 3) - 1, 0, world->world_data->world_width-1); + int by = clip_range(block.region_pos.y + (offset / 3) - 1, 0, world->world_data->world_height-1); df::world_data::T_region_map* biome = - &world->world_data->region_map[region_pos.x][region_pos.y]; + &world->world_data->region_map[bx][by]; int sav = biome->savagery; int evi = biome->evilness; diff --git a/plugins/ruby/README b/plugins/ruby/README index 690e83ca0..97a4cbd30 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -66,6 +66,9 @@ 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, holds all information relative to the map tile. + df.each_map_block { |b| } df.each_map_block_z(zlevel) { |b| } Iterates over every map block (opt. on a single z-level). @@ -106,6 +109,14 @@ See buildings.rb/buildbed for an exemple. 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 ---------------- @@ -113,6 +124,10 @@ DFHack callbacks The plugin interfaces with dfhack 'onupdate' hook. To register ruby code to be run every graphic frame, use: handle = df.onupdate_register { 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(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 diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 826cd26b9..9a59411f9 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -1,5 +1,45 @@ module DFHack class << self + def building_find(what=:selected, y=nil, z=nil) + if what == :selected + case ui.main.mode + when :LookAround + k = ui_look_list.items[ui_look_cursor] + k.building if k.type == :Building + when :BuildingItems, :QueryBuilding + world.selected_building + end + + 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] diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index c7fb210c9..cbe7c932a 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -8,7 +8,7 @@ use XML::LibXML; our @lines_rb; my $os; -if ($^O =~ /linux/i) { +if ($^O =~ /linux/i or $^O =~ /darwin/i) { $os = 'linux'; } else { $os = 'windows'; @@ -298,8 +298,26 @@ sub render_field_reftarget { 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 ; ${tgvec}.binsearch($name$fidx) ; end"; + } else { + push @lines_rb, "def $tgname ; ${tgvec}[$name] ; end"; + } - render_field_refto($parent, $name, $tgvec); } sub render_field_refto { @@ -329,9 +347,9 @@ sub render_container_reftarget { return if (!$tg); my $tgvec = $tg->getAttribute('instance-vector'); return if (!$tgvec); + my $idx = $tg->getAttribute('key-field'); $tgvec =~ s/^\$global/df/; - $tgvec =~ s/\[\$\]$//; return if $tgvec !~ /^[\w\.]+$/; my $tgname = "${name}_tg"; @@ -341,7 +359,13 @@ sub render_container_reftarget { $tgname .= '_' if ($othername and $tgname eq $othername); } - push @lines_rb, "def $tgname ; $name.map { |i| ${tgvec}[i] } ; end"; + 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"; + } } sub render_class_vmethods { @@ -516,7 +540,9 @@ sub get_field_align { if ($meta eq 'number') { $al = $field->getAttribute('ld:bits')/8; - $al = 4 if $al > 4; + # linux aligns int64_t to 4, windows to 8 + # floats are 4 bytes so no pb + $al = 4 if ($al > 4 and ($os eq 'linux' or $al != 8)); } elsif ($meta eq 'global') { $al = get_global_align($field); } elsif ($meta eq 'compound') { diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index cd95e82a9..34b404505 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -2,15 +2,38 @@ module DFHack class << self # return an Item # arg similar to unit.rb/unit_find; no arg = 'k' menu - def item_find(what=:selected) + def item_find(what=:selected, y=nil, z=nil) if what == :selected - case ui.main.mode - when :LookAround - k = ui_look_list.items[ui_look_cursor] - k.item if k.type == :Item + if curview._rtti_classname == :viewscreen_itemst + ref = curview.entry_ref[curview.cursor_pos] + ref.item_tg if ref.kind_of?(GeneralRefItem) + else + case ui.main.mode + when :LookAround + k = ui_look_list.items[ui_look_cursor] + case k.type + when :Item + k.item + when :Building + # hilight a constructed bed/coffer + mats = k.building.contained_items.find_all { |i| i.use_mode == 2 } + mats[0].item if mats.length == 1 + end + when :BuildingItems + bld = world.selected_building + bld.contained_items[ui_building_item_cursor].item if bld + when :ViewUnits + u = world.units.active[ui_selected_unit] + u.inventory[ui_look_cursor].item if u and u.pos.z == cursor.z and + ui_unit_view_mode.value == :Inventory and u.inventory[ui_look_cursor] + end end elsif what.kind_of?(Integer) - world.items.all.binsearch(what) + # 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 diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index af9e8b804..a0438670d 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -26,6 +26,13 @@ module DFHack end end + def map_tile_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) + 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| @@ -51,4 +58,109 @@ module DFHack } 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 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 + + # 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] & (1 << @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 world_data.geo_biome for current tile + def geo_biome + b = 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.geo_biomes[ wd.region_map[rx][ry].geo_index ] + end + + # return the world_data.geo_biome.layer for current tile + def stone_layer + geo_biome.layers[designation.geolayer_index] + end + + # current tile mat_index (vein if applicable, or base material) + def mat_index + mat_index_vein or stone_layer.mat_index + end + + # MaterialInfo: inorganic token for current tile + def mat_info + MaterialInfo.new(0, mat_index) + end + + def inspect + "#" + end + end end diff --git a/plugins/ruby/material.rb b/plugins/ruby/material.rb new file mode 100644 index 000000000..4a92118d6 --- /dev/null +++ b/plugins/ruby/material.rb @@ -0,0 +1,199 @@ +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 + end + + class << self + def decode_mat(what, idx=nil) + MaterialInfo.new(what, idx) + end + end +end diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 64da12ff9..a1cba4168 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -87,7 +87,7 @@ module DFHack def compound(name=nil, &b) m = Class.new(Compound) DFHack.const_set(name, m) if name - m.instance_eval(&b) + m.class_eval(&b) m.new end def rtti_classname(n) @@ -277,6 +277,7 @@ module DFHack def _get addr = _getp return if addr == 0 + return addr if not @_tg @_tg._at(addr)._get end @@ -353,7 +354,9 @@ module DFHack end def empty? ; length == 0 ; end def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end - def index(elem=nil, &b) ; (0...length).find { |i| b ? b[self[i]] : self[i] == elem } ; end + def index(e=nil, &b) ; (0...length).find { |i| b ? b[self[i]] : self[i] == e } ; end + def first ; self[0] ; end + def last ; self[length-1] ; end end class StaticArray < MemStruct attr_accessor :_tglen, :_length, :_indexenum, :_tg @@ -377,12 +380,18 @@ module DFHack def [](i) i = _indexenum.int(i) if _indexenum i += @_length if i < 0 - _tgat(i)._get + if t = _tgat(i) + t._get + end end def []=(i, v) i = _indexenum.int(i) if _indexenum i += @_length if i < 0 - _tgat(i)._set(v) + if t = _tgat(i) + t._set(v) + else + raise 'index out of bounds' + end end include Enumerable @@ -441,7 +450,7 @@ module DFHack if idx >= length insert_at(idx, 0) elsif idx < 0 - raise 'invalid idx' + raise 'index out of bounds' end @_tg._at(valueptr_at(idx))._set(v) end @@ -527,7 +536,7 @@ module DFHack if idx >= length insert_at(idx, v) elsif idx < 0 - raise 'invalid idx' + raise 'index out of bounds' else DFHack.memory_vectorbool_setat(@_memaddr, idx, v) end @@ -579,7 +588,7 @@ module DFHack idx = _indexenum.int(idx) if _indexenum idx += length if idx < 0 if idx >= length or idx < 0 - raise 'invalid idx' + raise 'index out of bounds' else DFHack.memory_bitarray_set(@_memaddr, idx, v) end @@ -605,11 +614,17 @@ module DFHack end def [](i) i += _length if i < 0 - _tgat(i)._get + if t = _tgat(i) + t._get + end end def []=(i, v) i += _length if i < 0 - _tgat(i)._set(v) + 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 } diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 0f5264515..08ea13b9f 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -6,8 +6,7 @@ #include "VersionInfo.h" #include "DataDefs.h" -#include "df/world.h" -#include "df/unit.h" +#include "df/global_objects.h" #include "tinythread.h" @@ -35,9 +34,13 @@ 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; +static color_ostream_proxy *console_proxy; + DFHACK_PLUGIN("ruby") @@ -115,7 +118,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) } // send a single ruby line to be evaluated by the ruby thread -DFhackCExport command_result plugin_eval_ruby(const char *command) +DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) { // if dlopen failed if (!r_thread) @@ -136,6 +139,7 @@ DFhackCExport command_result plugin_eval_ruby(const char *command) r_type = RB_EVAL; r_command = command; + r_console = &out; // wake ruby thread up m_irun->unlock(); @@ -144,6 +148,7 @@ DFhackCExport command_result plugin_eval_ruby(const char *command) tthread::this_thread::yield(); ret = r_result; + r_console = NULL; // block ruby thread m_irun->lock(); @@ -160,11 +165,16 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // ruby sets this flag when needed, to avoid lag running ruby code every // frame if not necessary - // TODO dynamic check on df::cur_year{_tick} if (!onupdate_active) return CR_OK; - return plugin_eval_ruby("DFHack.onupdate"); + if (*df::global::cur_year < onupdate_minyear) + return CR_OK; + if (*df::global::cur_year == onupdate_minyear && + *df::global::cur_year_tick < onupdate_minyeartick) + return CR_OK; + + return plugin_eval_ruby(out, "DFHack.onupdate"); } DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e) @@ -184,10 +194,12 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch // 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(cmd.c_str()); + return plugin_eval_ruby(out, cmd.c_str()); } static command_result df_rubyeval(color_ostream &out, std::vector & parameters) @@ -209,7 +221,7 @@ static command_result df_rubyeval(color_ostream &out, std::vector full += " "; } - return plugin_eval_ruby(full.c_str()); + return plugin_eval_ruby(out, full.c_str()); } @@ -265,7 +277,7 @@ static int df_loadruby(void) #if defined(WIN32) "./libruby.dll"; #elif defined(__APPLE__) - "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/libruby.1.dylib"; + "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/libruby.1.dylib"; #else "hack/libruby.so"; #endif @@ -310,6 +322,13 @@ static void df_unloadruby(void) } } +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) @@ -320,19 +339,17 @@ static void dump_rb_error(void) s = rb_funcall(err, rb_intern("class"), 0); s = rb_funcall(s, rb_intern("name"), 0); - Core::printerr("E: %s: ", rb_string_value_ptr(&s)); + printerr("E: %s: ", rb_string_value_ptr(&s)); s = rb_funcall(err, rb_intern("message"), 0); - Core::printerr("%s\n", rb_string_value_ptr(&s)); + 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) - Core::printerr(" %s\n", rb_string_value_ptr(&s)); + printerr(" %s\n", rb_string_value_ptr(&s)); } -static color_ostream_proxy *console_proxy; - // ruby thread main loop static void df_rubythread(void *p) { @@ -412,7 +429,7 @@ static VALUE rb_cDFHack; // DFHack module ruby methods, binds specific dfhack methods // enable/disable calls to DFHack.onupdate() -static VALUE rb_dfonupdateactive(VALUE self) +static VALUE rb_dfonupdate_active(VALUE self) { if (onupdate_active) return Qtrue; @@ -420,21 +437,46 @@ static VALUE rb_dfonupdateactive(VALUE self) return Qfalse; } -static VALUE rb_dfonupdateactiveset(VALUE self, VALUE val) +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_dfprint_str(VALUE self, VALUE s) { - console_proxy->print("%s", rb_string_value_ptr(&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_err(VALUE self, VALUE s) { - Core::printerr("%s", rb_string_value_ptr(&s)); + printerr("%s", rb_string_value_ptr(&s)); return Qnil; } @@ -764,8 +806,12 @@ static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE static void ruby_bind_dfhack(void) { rb_cDFHack = rb_define_module("DFHack"); - rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdateactive), 0); - rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdateactiveset), 1); + 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, "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); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 64592e3eb..5ae63ebfe 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -23,26 +23,78 @@ module Kernel end module DFHack + class OnupdateCallback + attr_accessor :callback, :timelimit, :minyear, :minyeartick + def initialize(cb, tl) + @callback = cb + @ticklimit = tl + @minyear = (tl ? df.cur_year : 0) + @minyeartick = (tl ? df.cur_year_tick : 0) + end + + # run callback if timedout + def check_run(year, yeartick, yearlen) + if !@ticklimit + @callback.call + else + if year > @minyear or (year == @minyear and yeartick >= @minyeartick) + @callback.call + @minyear = year + @minyeartick = yeartick + @ticklimit + if @minyeartick > yearlen + @minyear += 1 + @minyeartick -= yearlen + end + end + end + 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 { DFHack.world.units[0].counters.job_counter = 0 } - def onupdate_register(&b) + def onupdate_register(ticklimit=nil, &b) @onupdate_list ||= [] - @onupdate_list << b + @onupdate_list << OnupdateCallback.new(b, ticklimit) DFHack.onupdate_active = true + if onext = @onupdate_list.sort.first + 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 def onupdate_unregister(b) @onupdate_list.delete b - DFHack.onupdate_active = false if @onupdate_list.empty? + if @onupdate_list.empty? + DFHack.onupdate_active = false + DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = 0 + end end + TICKS_PER_YEAR = 1200*28*12 # this method is called by dfhack every 'onupdate' if onupdate_active is true def onupdate @onupdate_list ||= [] - @onupdate_list.each { |cb| cb.call } + + ticks_per_year = TICKS_PER_YEAR + ticks_per_year *= 72 if gametype == :ADVENTURE_MAIN or gametype == :ADVENTURE_ARENA + + @onupdate_list.each { |o| + o.check_run(cur_year, cur_year_tick, ticks_per_year) + } + + if onext = @onupdate_list.sort.first + DFHack.onupdate_minyear = onext.minyear + DFHack.onupdate_minyeartick = onext.minyeartick + end end # register a callback to be called every gframe or more @@ -85,6 +137,57 @@ module DFHack 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_dwarf + 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 + (2..5).each { |i| out.last << wl.words[name.words[i]].forms[name.parts_of_speech[i]] if name.words[i] >= 0 } + 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 diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index fbe7ced77..6d2b5c2cd 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -1,6 +1,13 @@ # 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) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index e7d4335f0..04deee0e3 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -4,19 +4,28 @@ module DFHack # 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) + def unit_find(what=:selected, y=nil, z=nil) if what == :selected - case ui.main.mode - when :ViewUnits - # nobody selected => idx == 0 - v = world.units.active[ui_selected_unit] - v if v and v.pos.z == cursor.z - when :LookAround - k = ui_look_list.items[ui_look_cursor] - k.unit if k.type == :Unit + if curview._rtti_classname == :viewscreen_itemst + ref = curview.entry_ref[curview.cursor_pos] + ref.unit_tg if ref.kind_of?(GeneralRefUnit) + else + case ui.main.mode + when :ViewUnits + # nobody selected => idx == 0 + v = world.units.active[ui_selected_unit] + v if v and v.pos.z == cursor.z + when :LookAround + k = ui_look_list.items[ui_look_cursor] + k.unit if k.type == :Unit + end end elsif what.kind_of?(Integer) - world.units.all.binsearch(what) + # 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 @@ -75,4 +84,10 @@ module DFHack list end end + + class LanguageName + def to_s(english=true) + df.translate_name(self, english) + end + end end diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 7926e2ac5..10d7b52c2 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -165,6 +165,8 @@ command_result df_showmood (color_ostream &out, vector & parameters) out.print("not yet claimed a workshop but will want"); out.print(" the following items:\n"); + int count_got = job->items.size(), got; + for (size_t i = 0; i < job->job_items.size(); i++) { df::job_item *item = job->job_items[i]; @@ -267,7 +269,11 @@ command_result df_showmood (color_ostream &out, vector & parameters) } } - out.print(", quantity %i\n", item->quantity); + got = count_got; + if (got > item->quantity) + got = item->quantity; + out.print(", quantity %i (got %i)\n", item->quantity, got); + count_got -= got; } } if (!found) diff --git a/scripts/devel/prepare-save.lua b/scripts/devel/prepare-save.lua index 781e3b892..c282c8a43 100644 --- a/scripts/devel/prepare-save.lua +++ b/scripts/devel/prepare-save.lua @@ -1,7 +1,22 @@ -- Prepare the current save for use with devel/find-offsets. +local utils = require 'utils' + df.global.pause_state = true +print[[ +WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS. + +This script prepares the current savegame to be used +with devel/find-offsets. It CHANGES THE GAME STATE +to predefined values, and initiates an immediate +quicksave, thus PERMANENTLY MODIFYING the save. +]] + +if not utils.prompt_yes_no('Proceed?') then + return +end + --[[print('Placing anchor...') do diff --git a/scripts/fixstuckdoors.rb b/scripts/fixstuckdoors.rb new file mode 100644 index 000000000..619ceeeb6 --- /dev/null +++ b/scripts/fixstuckdoors.rb @@ -0,0 +1,20 @@ +# fix doors that are frozen in 'open' state + +# door is stuck in open state if the map occupancy flag incorrectly indicates +# that an unit is present (and creatures will prone to pass through) + +count = 0 +df.world.buildings.all.each { |bld| + # for all doors + next if bld._rtti_classname != :building_doorst + # check if it is open + next if bld.close_timer == 0 + # check if occupancy is set + occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) + next if not occ.unit + # check if an unit is present + next if df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z } + count += 1 + occ.unit = false +} +puts "unstuck #{count} doors"