-
+
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"