From 6bd67cc055a3e2ec9a7562536d8c51c11a2789d2 Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 14 Jun 2012 14:25:16 +0200
Subject: [PATCH 01/29] ruby: try to fix msvc build (use fastcall for thiscall
fptr)
---
plugins/ruby/ruby.cpp | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp
index 49119c9aa..0176e1eef 100644
--- a/plugins/ruby/ruby.cpp
+++ b/plugins/ruby/ruby.cpp
@@ -763,13 +763,18 @@ static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE v
static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE a1, VALUE a2, VALUE a3)
{
#ifdef WIN32
- __thiscall
-#endif
+ int (__fastcall *fptr)(char **me, int dummy_edx, int, int, int, int);
+#else
int (*fptr)(char **me, int, int, int, int);
+#endif
char **that = (char**)rb_num2ulong(cppobj);
int ret;
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
- ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
+ ret = fptr(that,
+#ifdef WIN32
+ 0,
+#endif
+ rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
return rb_int2inum(ret);
}
From cfdf941c52beeca5ba06e6b626b21959781e2a21 Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 14 Jun 2012 22:35:59 +0200
Subject: [PATCH 02/29] ruby: fix windows build warnings/load ruby.rb
---
plugins/ruby/ruby.cpp | 4 +++-
plugins/ruby/ruby.rb | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp
index 0176e1eef..06f033d2f 100644
--- a/plugins/ruby/ruby.cpp
+++ b/plugins/ruby/ruby.cpp
@@ -483,6 +483,7 @@ static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
{
rb_raise(*rb_eRuntimeError, "not implemented");
+ return Qnil;
}
static VALUE rb_dfget_global_address(VALUE self, VALUE name)
@@ -841,7 +842,8 @@ static void ruby_bind_dfhack(void) {
// load the default ruby-level definitions
int state=0;
- rb_load_protect(rb_str_new2("./hack/ruby.rb"), Qfalse, &state);
+ // windows cmake installs files in df/, linux installs files in df/hack/
+ rb_eval_string_protect("File.exist?('hack/ruby.rb') ? load('hack/ruby.rb') : load('ruby.rb')", &state);
if (state)
dump_rb_error();
}
diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb
index fd597e43f..9c11144c0 100644
--- a/plugins/ruby/ruby.rb
+++ b/plugins/ruby/ruby.rb
@@ -1048,7 +1048,7 @@ end
end
# load autogen'd file
-require 'hack/ruby-autogen'
+File.exist?('hack/ruby-autogen.rb') ? require('hack/ruby-autogen') : require('ruby-autogen')
# load optional user-specified startup file
load 'ruby_custom.rb' if File.exist?('ruby_custom.rb')
From 0bdae68294857c3ba745f91b559d9485b7366774 Mon Sep 17 00:00:00 2001
From: jj
Date: Fri, 15 Jun 2012 21:02:04 +0200
Subject: [PATCH 03/29] ruby: fix codegen enum base-types
---
plugins/ruby/codegen.pl | 2 ++
1 file changed, 2 insertions(+)
diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl
index 5cdeeedd9..45fcd8fa4 100755
--- a/plugins/ruby/codegen.pl
+++ b/plugins/ruby/codegen.pl
@@ -687,6 +687,7 @@ sub render_item_number {
my $initvalue = $item->getAttribute('init-value');
my $typename = $item->getAttribute('type-name');
undef $typename if ($meta and $meta eq 'bitfield-type');
+ my $g = $global_types{$typename} if ($typename);
$typename = rb_ucase($typename) if $typename;
$typename = $classname if (!$typename and $subtype and $subtype eq 'enum'); # compound enum
@@ -695,6 +696,7 @@ sub render_item_number {
$initvalue ||= 'nil' if $typename;
$subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'bitfield' or $subtype eq 'enum');
+ $subtype = $g->getAttribute('base-type') if ($g);
$subtype = 'int32_t' if (!$subtype);
if ($subtype eq 'int64_t') {
From ed4acbdedbb0ddecac5b8b666ef159289697ec93 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Wed, 20 Jun 2012 10:12:26 +0400
Subject: [PATCH 04/29] Add a searcher for current_weather, using a prepared
save.
---
library/lua/memscan.lua | 2 +-
scripts/devel/find-offsets.lua | 49 +++++++++++++++++++++--
scripts/devel/prepare-save.lua | 71 ++++++++++++++++++++++++++++++++++
3 files changed, 117 insertions(+), 5 deletions(-)
create mode 100644 scripts/devel/prepare-save.lua
diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua
index 4bd01c8f7..65b02194c 100644
--- a/library/lua/memscan.lua
+++ b/library/lua/memscan.lua
@@ -168,7 +168,7 @@ function MemoryArea:__tostring()
return string.format('', self.start_addr, self.end_addr)
end
function MemoryArea:contains_range(start,size)
- return start >= self.start_addr and (start+size) <= self.end_addr
+ return size >= 0 and start >= self.start_addr and (start+size) <= self.end_addr
end
function MemoryArea:contains_obj(obj,count)
local size, base = df.sizeof(obj)
diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua
index ef9c98299..391390f14 100644
--- a/scripts/devel/find-offsets.lua
+++ b/scripts/devel/find-offsets.lua
@@ -24,7 +24,10 @@ PERMANENT SAVE CORRUPTION.
Finding the first few globals requires this script to be
started immediately after loading the game, WITHOUT
-first loading a world.
+first loading a world. The rest expect a loaded save,
+not a fresh embark. Finding current_weather requires
+a special save previously processed with devel/prepare-save
+on a DF version with working dfhack.
The script expects vanilla game configuration, without
any custom tilesets or init file changes. Never unpause
@@ -387,6 +390,43 @@ number, so when it shows "Min (5000df", it means 50000:]],
addr, df.ui_build_selector, 'plate_info', 'unit_min')
end
+--
+-- current_weather
+--
+
+local function find_current_weather()
+ print('\nPlease load the save previously processed with prepare-save.')
+ if not utils.prompt_yes_no('Proceed?', true) then
+ return
+ end
+
+ local zone
+ if os_type == 'windows' then
+ zone = zoomed_searcher('crime_next_id', 512)
+ elseif os_type == 'darwin' then
+ zone = zoomed_searcher('cursor', -64)
+ elseif os_type == 'linux' then
+ zone = zoomed_searcher('ui_building_assign_type', -512)
+ end
+ zone = zone or searcher
+
+ local wbytes = {
+ 2, 1, 0, 2, 0,
+ 1, 2, 1, 0, 0,
+ 2, 0, 2, 1, 2,
+ 1, 2, 0, 1, 1,
+ 2, 0, 1, 0, 2
+ }
+
+ local idx, addr = zone.area.int8_t:find_one(wbytes)
+ if idx then
+ ms.found_offset('current_weather', addr)
+ return
+ end
+
+ dfhack.printerr('Could not find current_weather - must be a wrong save.')
+end
+
--
-- ui_menu_width
--
@@ -669,7 +709,7 @@ end
local function get_process_zone()
if os_type == 'windows' then
return zoomed_searcher('ui_workshop_job_cursor', 'ui_building_in_resize')
- else
+ elseif os_type == 'linux' or os_type == 'darwin' then
return zoomed_searcher('cur_year', 'cur_year_tick')
end
end
@@ -710,10 +750,10 @@ end
local function find_pause_state()
local zone
- if os_type == 'linux' then
+ if os_type == 'linux' or os_type == 'darwin' then
zone = zoomed_searcher('ui_look_cursor', 32)
elseif os_type == 'windows' then
- zone = zoomed_searcher('ui_workshop_job_cursor', 64)
+ zone = zoomed_searcher('ui_workshop_job_cursor', 80)
end
zone = zone or searcher
@@ -747,6 +787,7 @@ exec_finder(find_ui_build_selector, 'ui_build_selector')
print('\nPrimitive globals:\n')
+exec_finder(find_current_weather, 'current_weather')
exec_finder(find_ui_menu_width, { 'ui_menu_width', 'ui_area_map_width' })
exec_finder(find_ui_selected_unit, 'ui_selected_unit')
exec_finder(find_ui_unit_view_mode, 'ui_unit_view_mode')
diff --git a/scripts/devel/prepare-save.lua b/scripts/devel/prepare-save.lua
new file mode 100644
index 000000000..781e3b892
--- /dev/null
+++ b/scripts/devel/prepare-save.lua
@@ -0,0 +1,71 @@
+-- Prepare the current save for use with devel/find-offsets.
+
+df.global.pause_state = true
+
+--[[print('Placing anchor...')
+
+do
+ local wp = df.global.ui.waypoints
+
+ for _,pt in ipairs(wp.points) do
+ if pt.name == 'dfhack_anchor' then
+ print('Already placed.')
+ goto found
+ end
+ end
+
+ local x,y,z = pos2xyz(df.global.cursor)
+
+ if not x then
+ error("Place cursor at your preferred anchor point.")
+ end
+
+ local id = wp.next_point_id
+ wp.next_point_id = id + 1
+
+ wp.points:insert('#',{
+ new = true, id = id, name = 'dfhack_anchor',
+ comment=(x..','..y..','..z),
+ tile = string.byte('!'), fg_color = COLOR_LIGHTRED, bg_color = COLOR_BLUE,
+ pos = xyz2pos(x,y,z)
+ })
+
+::found::
+end]]
+
+print('Nicknaming units...')
+
+for i,unit in ipairs(df.global.world.units.active) do
+ dfhack.units.setNickname(unit, i..':'..unit.id)
+end
+
+print('Setting weather...')
+
+local wbytes = {
+ 2, 1, 0, 2, 0,
+ 1, 2, 1, 0, 0,
+ 2, 0, 2, 1, 2,
+ 1, 2, 0, 1, 1,
+ 2, 0, 1, 0, 2
+}
+
+for i=0,4 do
+ for j = 0,4 do
+ df.global.current_weather[i][j] = (wbytes[i*5+j+1] or 2)
+ end
+end
+
+local yearstr = df.global.cur_year..','..df.global.cur_year_tick
+
+print('Cur year and tick: '..yearstr)
+
+dfhack.persistent.save{
+ key='prepare-save/cur_year',
+ value=yearstr,
+ ints={df.global.cur_year, df.global.cur_year_tick}
+}
+
+-- Save
+
+dfhack.run_script('quicksave')
+
From f207714d4225e729fa43d04048d595ad6954521d Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 21 Jun 2012 21:08:36 +0400
Subject: [PATCH 05/29] Add finders for enabler, gps and init.
---
library/lua/memscan.lua | 29 +++++++-
scripts/devel/find-offsets.lua | 125 +++++++++++++++++++++++++++++++++
2 files changed, 153 insertions(+), 1 deletion(-)
diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua
index 65b02194c..4cf8d41c8 100644
--- a/library/lua/memscan.lua
+++ b/library/lua/memscan.lua
@@ -154,7 +154,8 @@ function MemoryArea.new(astart, aend)
int16_t = CheckedArray.new('int16_t',astart,aend),
uint16_t = CheckedArray.new('uint16_t',astart,aend),
int32_t = CheckedArray.new('int32_t',astart,aend),
- uint32_t = CheckedArray.new('uint32_t',astart,aend)
+ uint32_t = CheckedArray.new('uint32_t',astart,aend),
+ float = CheckedArray.new('float',astart,aend)
}
setmetatable(obj, MemoryArea)
return obj
@@ -453,4 +454,30 @@ function DiffSearcher:find_counter(prompt,data_type,delta,action_prompt)
)
end
+-- Screen size
+
+function get_screen_size()
+ -- Use already known globals
+ if dfhack.internal.getAddress('init') then
+ local d = df.global.init.display
+ return d.grid_x, d.grid_y
+ end
+ if dfhack.internal.getAddress('gps') then
+ local g = df.global.gps
+ return g.dimx, g.dimy
+ end
+
+ -- Parse stdout.log for resize notifications
+ io.stdout:flush()
+
+ local w,h = 80,25
+ for line in io.lines('stdout.log') do
+ local cw, ch = string.match(line, '^Resizing grid to (%d+)x(%d+)$')
+ if cw and ch then
+ w, h = tonumber(cw), tonumber(ch)
+ end
+ end
+ return w,h
+end
+
return _ENV
diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua
index 391390f14..bddd29dfe 100644
--- a/scripts/devel/find-offsets.lua
+++ b/scripts/devel/find-offsets.lua
@@ -252,6 +252,83 @@ local function find_gview()
dfhack.printerr('Could not find gview')
end
+--
+-- enabler
+--
+
+local function is_valid_enabler(e)
+ if not ms.is_valid_vector(e.textures.raws, 4)
+ or not ms.is_valid_vector(e.text_system, 4)
+ then
+ dfhack.printerr('Vector layout check failed.')
+ return false
+ end
+
+ return true
+end
+
+local function find_enabler()
+ -- Data from data/init/colors.txt
+ local colors = {
+ 0, 0, 0, 0, 0, 128, 0, 128, 0,
+ 0, 128, 128, 128, 0, 0, 128, 0, 128,
+ 128, 128, 0, 192, 192, 192, 128, 128, 128,
+ 0, 0, 255, 0, 255, 0, 0, 255, 255,
+ 255, 0, 0, 255, 0, 255, 255, 255, 0,
+ 255, 255, 255
+ }
+
+ for i = 1,#colors do colors[i] = colors[i]/255 end
+
+ local idx, addr = data.float:find_one(colors)
+ if idx then
+ validate_offset('enabler', is_valid_enabler, addr, df.enabler, 'ccolor')
+ return
+ end
+
+ dfhack.printerr('Could not find enabler')
+end
+
+--
+-- gps
+--
+
+local function is_valid_gps(g)
+ if g.clipx[0] < 0 or g.clipx[0] > g.clipx[1] or g.clipx[1] >= g.dimx then
+ dfhack.printerr('Invalid clipx: ', g.clipx[0], g.clipx[1], g.dimx)
+ end
+ if g.clipy[0] < 0 or g.clipy[0] > g.clipy[1] or g.clipy[1] >= g.dimy then
+ dfhack.printerr('Invalid clipy: ', g.clipy[0], g.clipy[1], g.dimy)
+ end
+
+ return true
+end
+
+local function find_gps()
+ print('\nPlease ensure the mouse cursor is not over the game window.')
+ if not utils.prompt_yes_no('Proceed?', true) then
+ return
+ end
+
+ local zone
+ if os_type == 'windows' or os_type == 'linux' then
+ zone = zoomed_searcher('cursor', 0x1000)
+ elseif os_type == 'darwin' then
+ zone = zoomed_searcher('enabler', 0x1000)
+ end
+ zone = zone or searcher
+
+ local w,h = ms.get_screen_size()
+
+ local idx, addr = zone.area.int32_t:find_one{w, h, -1, -1}
+ if idx then
+ validate_offset('gps', is_valid_gps, addr, df.graphic, 'dimx')
+ return
+ end
+
+ dfhack.printerr('Could not find gps')
+end
+
--
-- World
--
@@ -390,6 +467,51 @@ number, so when it shows "Min (5000df", it means 50000:]],
addr, df.ui_build_selector, 'plate_info', 'unit_min')
end
+--
+-- init
+--
+
+local function is_valid_init(i)
+ -- derived from curses_*.png image sizes presumably
+ if i.font.small_font_dispx ~= 8 or i.font.small_font_dispy ~= 12 or
+ i.font.large_font_dispx ~= 10 or i.font.large_font_dispy ~= 12 then
+ print('Unexpected font sizes: ',
+ i.font.small_font_dispx, i.font.small_font_dispy,
+ i.font.large_font_dispx, i.font.large_font_dispy)
+ if not utils.prompt_yes_no('Ignore?') then
+ return false
+ end
+ end
+
+ return true
+end
+
+local function find_init()
+ local zone
+ if os_type == 'windows' then
+ zone = zoomed_searcher('ui_build_selector', 0x3000)
+ elseif os_type == 'linux' or os_type == 'darwin' then
+ zone = zoomed_searcher('d_init', -0x2000)
+ end
+ zone = zone or searcher
+
+ local idx, addr = zone.area.int32_t:find_one{250, 150, 15, 0}
+ if idx then
+ validate_offset('init', is_valid_init, addr, df.init, 'input', 'hold_time')
+ return
+ end
+
+ local w,h = ms.get_screen_size()
+
+ local idx, addr = zone.area.int32_t:find_one{w, h}
+ if idx then
+ validate_offset('init', is_valid_init, addr, df.init, 'display', 'grid_x')
+ return
+ end
+
+ dfhack.printerr('Could not find init')
+end
+
--
-- current_weather
--
@@ -777,6 +899,8 @@ exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
exec_finder(find_announcements, 'announcements')
exec_finder(find_d_init, 'd_init')
exec_finder(find_gview, 'gview')
+exec_finder(find_enabler, 'enabler')
+exec_finder(find_gps, 'gps')
print('\nCompound globals (need loaded world):\n')
@@ -784,6 +908,7 @@ exec_finder(find_world, 'world')
exec_finder(find_ui, 'ui')
exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus')
exec_finder(find_ui_build_selector, 'ui_build_selector')
+exec_finder(find_init, 'init')
print('\nPrimitive globals:\n')
From 752da9ced5ce2df8cc9638cbf75a769325540e31 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 21 Jun 2012 21:26:25 +0400
Subject: [PATCH 06/29] Move formatting newly-found globals for symbols.xml to
lua code.
---
LUA_API.rst | 1 +
Lua API.html | 3 ++-
library/LuaApi.cpp | 6 +++---
library/LuaTools.cpp | 5 ++++-
library/include/ColorText.h | 2 ++
library/lua/memscan.lua | 10 ++++++++++
6 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/LUA_API.rst b/LUA_API.rst
index 9515690eb..5136bba57 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -451,6 +451,7 @@ Currently it defines the following features:
* ``dfhack.color([color])``
Sets the current output color. If color is *nil* or *-1*, resets to default.
+ Returns the previous color value.
* ``dfhack.is_interactive()``
diff --git a/Lua API.html b/Lua API.html
index 84d13e2f0..1c4dc4059 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -734,7 +734,8 @@ works with DFHack output infrastructure.
Same as println; intended for errors. Uses red color and logs to stderr.log.
dfhack.color([color])
-Sets the current output color. If color is nil or -1, resets to default.
+Sets the current output color. If color is nil or -1, resets to default.
+Returns the previous color value.
dfhack.is_interactive()
Checks if the thread can access the interactive console and returns true or false.
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 092404e33..b0a085eca 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1074,9 +1074,9 @@ static int internal_setAddress(lua_State *L)
}
// Print via printerr, so that it is definitely logged to stderr.log.
- addr -= Core::getInstance().vinfo->getRebaseDelta();
- std::string msg = stl_sprintf("", name.c_str(), addr);
- dfhack_printerr(L, msg);
+ uint32_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta();
+ fprintf(stderr, "Setting global '%s' to %x (%x)\n", name.c_str(), addr, iaddr);
+ fflush(stderr);
return 1;
}
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp
index 752c341b2..48244dedf 100644
--- a/library/LuaTools.cpp
+++ b/library/LuaTools.cpp
@@ -256,8 +256,11 @@ static int lua_dfhack_color(lua_State *S)
luaL_argerror(S, 1, "invalid color value");
color_ostream *out = Lua::GetOutput(S);
- if (out)
+ if (out) {
+ lua_pushinteger(S, (int)out->color());
out->color(color_ostream::color_value(cv));
+ return 1;
+ }
return 0;
}
diff --git a/library/include/ColorText.h b/library/include/ColorText.h
index 105832efd..0cc286dcf 100644
--- a/library/include/ColorText.h
+++ b/library/include/ColorText.h
@@ -111,6 +111,8 @@ namespace DFHack
void printerr(const char *format, ...);
void vprinterr(const char *format, va_list args);
+ /// Get color
+ color_value color() { return cur_color; }
/// Set color (ANSI color number)
void color(color_value c);
/// Reset color to default
diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua
index 4cf8d41c8..92a3e3e80 100644
--- a/library/lua/memscan.lua
+++ b/library/lua/memscan.lua
@@ -252,6 +252,16 @@ function found_offset(name,val)
end
else
dfhack.internal.setAddress(name, val)
+
+ local ival = val - dfhack.internal.getRebaseDelta()
+ local entry = string.format("\n", name, ival)
+
+ local ccolor = dfhack.color(COLOR_LIGHTGREEN)
+ dfhack.print(entry)
+ dfhack.color(ccolor)
+
+ io.stdout:write(entry)
+ io.stdout:flush()
end
end
From 65e82f7c12f95e461363e15c781c3fd4c5d241d3 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 22 Jun 2012 16:36:50 +0400
Subject: [PATCH 07/29] Support controllable error presentation verbosity in
lua code.
Use qerror to squash stack traces and location prefix.
---
LUA_API.rst | 88 +++++++++++++-----
Lua API.html | 162 ++++++++++++++++++++++-----------
library/LuaTools.cpp | 78 ++++++++++++++--
library/lua/dfhack.lua | 4 +
library/lua/memscan.lua | 2 +-
library/lua/utils.lua | 2 +-
scripts/devel/find-offsets.lua | 6 +-
scripts/fix/item-occupancy.lua | 3 +-
scripts/quicksave.lua | 3 +-
9 files changed, 259 insertions(+), 89 deletions(-)
diff --git a/LUA_API.rst b/LUA_API.rst
index 5136bba57..e8c413fe7 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -426,13 +426,17 @@ not destroy any objects allocated in this way, so the user
should be prepared to catch the error and do the necessary
cleanup.
-================
-DFHack utilities
-================
+==========
+DFHack API
+==========
DFHack utility functions are placed in the ``dfhack`` global tree.
-Currently it defines the following features:
+Native utilities
+================
+
+Input & Output
+--------------
* ``dfhack.print(args...)``
@@ -474,23 +478,9 @@ Currently it defines the following features:
If the interactive console is not accessible, returns *nil, error*.
-* ``dfhack.pcall(f[,args...])``
-
- Invokes f via xpcall, using an error function that attaches
- a stack trace to the error. The same function is used by SafeCall
- in C++, and dfhack.safecall.
-
- The returned error is a table with separate ``message`` and
- ``stacktrace`` string fields; it implements ``__tostring``.
-
-* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])``
-
- Just like pcall, but also prints the error using printerr before
- returning. Intended as a convenience function.
-
-* ``dfhack.saferesume(coroutine[,args...])``
- Compares to coroutine.resume like dfhack.safecall vs pcall.
+Miscellaneous
+-------------
* ``dfhack.run_script(name[,args...])``
@@ -511,6 +501,36 @@ Currently it defines the following features:
to group operations together in one big critical section. A plugin
can choose to run all lua code inside a C++-side suspend lock.
+
+Exception handling
+------------------
+
+* ``dfhack.error(msg[,level[,verbose]])``
+
+ Throws a dfhack exception object with location and stack trace.
+ The verbose parameter controls whether the trace is printed by default.
+
+* ``qerror(msg[,level])``
+
+ Calls ``dfhack.error()`` with ``verbose`` being *false*. Intended to
+ be used for user-caused errors in scripts, where stack traces are not
+ desirable.
+
+* ``dfhack.pcall(f[,args...])``
+
+ Invokes f via xpcall, using an error function that attaches
+ a stack trace to the error. The same function is used by SafeCall
+ in C++, and dfhack.safecall.
+
+* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])``
+
+ Just like pcall, but also prints the error using printerr before
+ returning. Intended as a convenience function.
+
+* ``dfhack.saferesume(coroutine[,args...])``
+
+ Compares to coroutine.resume like dfhack.safecall vs pcall.
+
* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])``
Invokes ``fn`` with ``args``, and after it returns or throws an
@@ -535,9 +555,33 @@ Currently it defines the following features:
Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``.
+* ``dfhack.exception``
+
+ Metatable of error objects used by dfhack. The objects have the
+ following properties:
+
+ ``err.where``
+ The location prefix string, or *nil*.
+ ``err.message``
+ The base message string.
+ ``err.stacktrace``
+ The stack trace string, or *nil*.
+ ``err.cause``
+ A different exception object, or *nil*.
+ ``err.thread``
+ The coroutine that has thrown the exception.
+ ``err.verbose``
+ Boolean, or *nil*; specifies if where and stacktrace should be printed.
+ ``tostring(err)``, or ``err:tostring([verbose])``
+ Converts the exception to string.
+
+* ``dfhack.exception.verbose``
+
+ The default value of the ``verbose`` argument of ``err:tostring()``.
+
Persistent configuration storage
-================================
+--------------------------------
This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent.
@@ -579,7 +623,7 @@ functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead.
Material info lookup
-====================
+--------------------
A material info record has fields:
diff --git a/Lua API.html b/Lua API.html
index 1c4dc4059..47cf08ab6 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -333,30 +333,36 @@ ul.auto-toc {
Recursive table assignment
-DFHack utilities
-- Persistent configuration storage
-- Material info lookup
-- C++ function wrappers
-- Gui module
-- Job module
-- Units module
-- Items module
-- Maps module
-- Burrows module
-- Buildings module
-- Constructions module
-- Internal API
+- DFHack API
+- Native utilities
-- Core interpreter context
@@ -717,10 +723,13 @@ should be prepared to catch the error and do the necessary
cleanup.
-
-
+
+
DFHack utility functions are placed in the dfhack global tree.
-
Currently it defines the following features:
+
+
+
+
+
+
dfhack.run_script(name[,args...])
Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
The name argument should be the name stem, as would be used on the command line.
@@ -782,6 +782,32 @@ the lock. It is safe to nest suspends.
to group operations together in one big critical section. A plugin
can choose to run all lua code inside a C++-side suspend lock.
+
+
+
+
+
+dfhack.error(msg[,level[,verbose]])
+Throws a dfhack exception object with location and stack trace.
+The verbose parameter controls whether the trace is printed by default.
+
+qerror(msg[,level])
+Calls dfhack.error() with verbose being false. Intended to
+be used for user-caused errors in scripts, where stack traces are not
+desirable.
+
+dfhack.pcall(f[,args...])
+Invokes f via xpcall, using an error function that attaches
+a stack trace to the error. The same function is used by SafeCall
+in C++, and dfhack.safecall.
+
+safecall(f[,args...]), dfhack.safecall(f[,args...])
+Just like pcall, but also prints the error using printerr before
+returning. Intended as a convenience function.
+
+dfhack.saferesume(coroutine[,args...])
+Compares to coroutine.resume like dfhack.safecall vs pcall.
+
dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])
Invokes fn with args, and after it returns or throws an
error calls cleanup_fn with cleanup_args. Any return values from
@@ -801,9 +827,40 @@ Implemented using call_with_final
dfhack.with_temp_object(obj,fn[,args...])
Calls fn(obj,args...), then finalizes with obj:delete().
+dfhack.exception
+Metatable of error objects used by dfhack. The objects have the
+following properties:
+
+- err.where
+The location prefix string, or nil.
+
+- err.message
+The base message string.
+
+- err.stacktrace
+The stack trace string, or nil.
+
+- err.cause
+A different exception object, or nil.
+
+- err.thread
+The coroutine that has thrown the exception.
+
+- err.verbose
+Boolean, or nil; specifies if where and stacktrace should be printed.
+
+- tostring(err), or err:tostring([verbose])
+Converts the exception to string.
+
+
+
+dfhack.exception.verbose
+The default value of the verbose argument of err:tostring().
+
+
-
+
This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent.
Entries are identified by a string key, but it is also possible to manage
@@ -838,7 +895,7 @@ functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead.
-
+
A material info record has fields:
type, index, material
@@ -881,8 +938,9 @@ Accept dfhack_material_category auto-assign table.
+
-
+
Thin wrappers around C++ functions, similar to the ones for virtual methods.
One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
@@ -911,7 +969,7 @@ can be omitted.
-
+
dfhack.units.getPosition(unit)
Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.
@@ -1038,7 +1096,7 @@ or raws. The ignore_noble boolean disables the
-
+
dfhack.items.getPosition(item)
Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.
@@ -1081,7 +1139,7 @@ Returns false in case of error.
-
+
dfhack.buildings.getSize(building)
Returns width, height, centerx, centery.
@@ -1297,7 +1355,7 @@ can be determined this way, constructBuilding
-
+
dfhack.constructions.designateNew(pos,type,item_type,mat_index)
Designates a new construction at given position. If there already is
@@ -1313,7 +1371,7 @@ Returns true, was_only_planned if removed; or false if none fo
-
+
These functions are intended for the use by dfhack developers,
and are only documented here for completeness:
@@ -1356,7 +1414,7 @@ Returns: found_index, or nil if end reached.
-
+
While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.
@@ -1387,7 +1445,7 @@ Using
timeout_active(id,nil) cancels the timer
-
+
An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
@@ -1413,14 +1471,14 @@ order using dfhack.safecall.
-
+
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.
-
+
Implements extended burrow manipulations.
Events:
-
+
Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp
index 48244dedf..28571a0f7 100644
--- a/library/LuaTools.cpp
+++ b/library/LuaTools.cpp
@@ -426,10 +426,12 @@ static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = N
// Create a new exception for this thread
lua_newtable(L);
- luaL_where(L, 1);
+ luaL_where(L, slevel);
+ lua_setfield(L, -2, "where");
lua_pushstring(L, "coroutine resume failed");
- lua_concat(L, 2);
lua_setfield(L, -2, "message");
+ lua_getfield(L, -2, "verbose");
+ lua_setfield(L, -2, "verbose");
lua_swap(L);
lua_setfield(L, -2, "cause");
}
@@ -483,12 +485,57 @@ static int dfhack_onerror(lua_State *L)
return 1;
}
+static int dfhack_error(lua_State *L)
+{
+ luaL_checkany(L, 1);
+ lua_settop(L, 3);
+ int level = std::max(1, luaL_optint(L, 2, 1));
+
+ lua_pushvalue(L, 1);
+
+ if (convert_to_exception(L, level))
+ {
+ luaL_where(L, level);
+ lua_setfield(L, -2, "where");
+
+ if (!lua_isnil(L, 3))
+ {
+ lua_pushvalue(L, 3);
+ lua_setfield(L, -2, "verbose");
+ }
+ }
+
+ return lua_error(L);
+}
+
static int dfhack_exception_tostring(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
+ lua_settop(L, 2);
+
+ if (lua_isnil(L, 2))
+ {
+ lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
+ lua_getfield(L, -1, "verbose");
+ lua_insert(L, 2);
+ lua_settop(L, 2);
+ }
+
+ lua_getfield(L, 1, "verbose");
+
+ bool verbose =
+ lua_toboolean(L, 2) || lua_toboolean(L, 3) ||
+ (lua_isnil(L, 2) && lua_isnil(L, 3));
int base = lua_gettop(L);
+ if (verbose || lua_isnil(L, 3))
+ {
+ lua_getfield(L, 1, "where");
+ if (!lua_isstring(L, -1))
+ lua_pop(L, 1);
+ }
+
lua_getfield(L, 1, "message");
if (!lua_isstring(L, -1))
{
@@ -496,15 +543,26 @@ static int dfhack_exception_tostring(lua_State *L)
lua_pushstring(L, "(error message is not a string)");
}
- lua_pushstring(L, "\n");
- lua_getfield(L, 1, "stacktrace");
- if (!lua_isstring(L, -1))
- lua_pop(L, 2);
+ if (verbose)
+ {
+ lua_pushstring(L, "\n");
+ lua_getfield(L, 1, "stacktrace");
+ if (!lua_isstring(L, -1))
+ lua_pop(L, 2);
+ }
lua_pushstring(L, "\ncaused by:\n");
lua_getfield(L, 1, "cause");
if (lua_isnil(L, -1))
lua_pop(L, 2);
+ else if (lua_istable(L, -1))
+ {
+ lua_pushcfunction(L, dfhack_exception_tostring);
+ lua_swap(L);
+ lua_pushvalue(L, 2);
+ if (lua_pcall(L, 2, 1, 0) != LUA_OK)
+ error_tostring(L);
+ }
else
error_tostring(L);
@@ -655,7 +713,12 @@ static int dfhack_coauxwrap (lua_State *L) {
if (Lua::IsSuccess(r))
return lua_gettop(L);
else
+ {
+ if (lua_checkstack(L, LUA_MINSTACK))
+ convert_to_exception(L, 1);
+
return lua_error(L);
+ }
}
static int dfhack_cowrap (lua_State *L) {
@@ -1162,6 +1225,7 @@ static const luaL_Reg dfhack_funcs[] = {
{ "safecall", dfhack_safecall },
{ "saferesume", dfhack_saferesume },
{ "onerror", dfhack_onerror },
+ { "error", dfhack_error },
{ "call_with_finalizer", dfhack_call_with_finalizer },
{ "with_suspend", lua_dfhack_with_suspend },
{ "open_plugin", dfhack_open_plugin },
@@ -1362,6 +1426,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_newtable(state);
lua_pushcfunction(state, dfhack_exception_tostring);
lua_setfield(state, -2, "__tostring");
+ lua_pushcfunction(state, dfhack_exception_tostring);
+ lua_setfield(state, -2, "tostring");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
lua_setfield(state, -2, "exception");
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua
index 4cdb4c950..d200a6c5c 100644
--- a/library/lua/dfhack.lua
+++ b/library/lua/dfhack.lua
@@ -49,6 +49,10 @@ function dfhack.pcall(f, ...)
return xpcall(f, dfhack.onerror, ...)
end
+function qerror(msg, level)
+ dfhack.error(msg, (level or 1) + 1, false)
+end
+
function dfhack.with_finalize(...)
return dfhack.call_with_finalizer(0,true,...)
end
diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua
index 92a3e3e80..970f821c2 100644
--- a/library/lua/memscan.lua
+++ b/library/lua/memscan.lua
@@ -235,7 +235,7 @@ function found_offset(name,val)
if not val then
print('Could not find offset '..name)
if not cval and not utils.prompt_yes_no('Continue with the script?') then
- error('User quit')
+ qerror('User quit')
end
return
end
diff --git a/library/lua/utils.lua b/library/lua/utils.lua
index 93ee840c4..f303091d6 100644
--- a/library/lua/utils.lua
+++ b/library/lua/utils.lua
@@ -379,7 +379,7 @@ function prompt_yes_no(msg,default)
elseif string.match(rv,'^[Nn]') then
return false
elseif rv == 'abort' then
- error('User abort in utils.prompt_yes_no()')
+ qerror('User abort in utils.prompt_yes_no()')
elseif rv == '' and default ~= nil then
return default
end
diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua
index bddd29dfe..6fc127351 100644
--- a/scripts/devel/find-offsets.lua
+++ b/scripts/devel/find-offsets.lua
@@ -43,12 +43,12 @@ end
local data = ms.get_data_segment()
if not data then
- error('Could not find data segment')
+ qerror('Could not find data segment')
end
print('\nData section: '..tostring(data))
if data.size < 5000000 then
- error('Data segment too short.')
+ qerror('Data segment too short.')
end
local searcher = ms.DiffSearcher.new(data)
@@ -103,7 +103,7 @@ local function exec_finder(finder, names)
if not dfhack.safecall(finder) then
if not utils.prompt_yes_no('Proceed with the rest of the script?') then
searcher:reset()
- error('Quit')
+ qerror('Quit')
end
end
else
diff --git a/scripts/fix/item-occupancy.lua b/scripts/fix/item-occupancy.lua
index b5466b7a8..09c6b3030 100644
--- a/scripts/fix/item-occupancy.lua
+++ b/scripts/fix/item-occupancy.lua
@@ -116,8 +116,7 @@ if opt then
if opt == '--fix' then
fix = true
else
- dfhack.printerr('Invalid option: '..opt)
- return
+ qerror('Invalid option: '..opt)
end
end
diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua
index c54cc730b..f4886b35b 100644
--- a/scripts/quicksave.lua
+++ b/scripts/quicksave.lua
@@ -1,8 +1,7 @@
-- Makes the game immediately save the state.
if not dfhack.isMapLoaded() then
- dfhack.printerr("World and map aren't loaded.")
- return
+ qerror("World and map aren't loaded.")
end
local ui_main = df.global.ui.main
From bd37cc09c525d4db9400e224e4ca3cadcf0eed4c Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 22 Jun 2012 20:17:55 +0400
Subject: [PATCH 08/29] Update the Lua API document with info about scripts.
---
LUA_API.rst | 183 ++++++++++++++++++++++++++++++-----------
Lua API.html | 172 ++++++++++++++++++++++++++------------
library/lua/dfhack.lua | 2 +
3 files changed, 256 insertions(+), 101 deletions(-)
diff --git a/LUA_API.rst b/LUA_API.rst
index e8c413fe7..5fc653bb3 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -4,9 +4,26 @@ DFHack Lua API
.. contents::
-====================
-DF structure wrapper
-====================
+The current version of DFHack has extensive support for
+the Lua scripting language, providing access to:
+
+1. Raw data structures used by the game.
+2. Many C++ functions for high-level access to these
+ structures, and interaction with dfhack itself.
+3. Some functions exported by C++ plugins.
+
+Lua code can be used both for writing scripts, which
+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
+implemented by Lua files located in hack/lua/...
+
+
+=========================
+DF data structure wrapper
+=========================
DF structures described by the xml files in library/xml are exported
to lua code as a tree of objects and functions under the ``df`` global,
@@ -479,29 +496,6 @@ Input & Output
If the interactive console is not accessible, returns *nil, error*.
-Miscellaneous
--------------
-
-* ``dfhack.run_script(name[,args...])``
-
- Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
- The ``name`` argument should be the name stem, as would be used on the command line.
- Note that the script is re-read from the file every time it is called, and errors
- are propagated to the caller.
-
-* ``dfhack.with_suspend(f[,args...])``
-
- Calls ``f`` with arguments after grabbing the DF core suspend lock.
- Suspending is necessary for accessing a consistent state of DF memory.
-
- Returned values and errors are propagated through after releasing
- the lock. It is safe to nest suspends.
-
- Every thread is allowed only one suspend per DF frame, so it is best
- to group operations together in one big critical section. A plugin
- can choose to run all lua code inside a C++-side suspend lock.
-
-
Exception handling
------------------
@@ -531,30 +525,6 @@ Exception handling
Compares to coroutine.resume like dfhack.safecall vs pcall.
-* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])``
-
- Invokes ``fn`` with ``args``, and after it returns or throws an
- error calls ``cleanup_fn`` with ``cleanup_args``. Any return values from
- ``fn`` are propagated, and errors are re-thrown.
-
- The ``num_cleanup_args`` integer specifies the number of ``cleanup_args``,
- and the ``always`` boolean specifies if cleanup should be called in any case,
- or only in case of an error.
-
-* ``dfhack.with_finalize(cleanup_fn,fn[,args...])``
-
- Calls ``fn`` with arguments, then finalizes with ``cleanup_fn``.
- Implemented using ``call_with_finalizer(0,true,...)``.
-
-* ``dfhack.with_onerror(cleanup_fn,fn[,args...])``
-
- Calls ``fn`` with arguments, then finalizes with ``cleanup_fn`` on any thrown error.
- Implemented using ``call_with_finalizer(0,false,...)``.
-
-* ``dfhack.with_temp_object(obj,fn[,args...])``
-
- Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``.
-
* ``dfhack.exception``
Metatable of error objects used by dfhack. The objects have the
@@ -580,6 +550,46 @@ Exception handling
The default value of the ``verbose`` argument of ``err:tostring()``.
+Locking and finalization
+------------------------
+
+* ``dfhack.with_suspend(f[,args...])``
+
+ Calls ``f`` with arguments after grabbing the DF core suspend lock.
+ Suspending is necessary for accessing a consistent state of DF memory.
+
+ Returned values and errors are propagated through after releasing
+ the lock. It is safe to nest suspends.
+
+ Every thread is allowed only one suspend per DF frame, so it is best
+ to group operations together in one big critical section. A plugin
+ can choose to run all lua code inside a C++-side suspend lock.
+
+* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])``
+
+ Invokes ``fn`` with ``args``, and after it returns or throws an
+ error calls ``cleanup_fn`` with ``cleanup_args``. Any return values from
+ ``fn`` are propagated, and errors are re-thrown.
+
+ The ``num_cleanup_args`` integer specifies the number of ``cleanup_args``,
+ and the ``always`` boolean specifies if cleanup should be called in any case,
+ or only in case of an error.
+
+* ``dfhack.with_finalize(cleanup_fn,fn[,args...])``
+
+ Calls ``fn`` with arguments, then finalizes with ``cleanup_fn``.
+ Implemented using ``call_with_finalizer(0,true,...)``.
+
+* ``dfhack.with_onerror(cleanup_fn,fn[,args...])``
+
+ Calls ``fn`` with arguments, then finalizes with ``cleanup_fn`` on any thrown error.
+ Implemented using ``call_with_finalizer(0,false,...)``.
+
+* ``dfhack.with_temp_object(obj,fn[,args...])``
+
+ Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``.
+
+
Persistent configuration storage
--------------------------------
@@ -1312,6 +1322,42 @@ Features:
Invokes all listeners contained in the event in an arbitrary
order using ``dfhack.safecall``.
+
+=======
+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:
+
+* ``mkmodule(name)``
+
+ Creates an environment table for the module. Intended to be used as::
+
+ local _ENV = mkmodule('foo')
+ ...
+ return _ENV
+
+ If called the second time, returns the same table; thus providing reload support.
+
+* ``reload(name)``
+
+ Reloads a previously ``require``-d module *"name"* from the file.
+ Intended as a help for module development.
+
+* ``dfhack.BASE_G``
+
+ This variable contains the root global environment table, which is
+ used as a base for all module and script environments. Its contents
+ should be kept limited to the standard Lua library and API described
+ in this document.
+
+
=======
Plugins
=======
@@ -1373,3 +1419,40 @@ sort
Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.
+
+
+=======
+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.
+
+**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.
+When calling such scripts, always use '/' as the separator for
+directories, e.g. ``devel/lua-example``.
+
+Scripts are re-read from disk every time they are used
+(this may be changed later to check the file change time); however
+the global variable values persist in memory between calls.
+Every script gets its own separate environment for global
+variables.
+
+Arguments are passed in to the scripts via the **...** built-in
+quasi-variable; when the script is called by the DFHack core,
+they are all guaranteed to be non-nil strings.
+
+DFHack core invokes the scripts in the *core context* (see above);
+however it is possible to call them from any lua code (including
+from other scripts) in any context, via the same function the core uses:
+
+* ``dfhack.run_script(name[,args...])``
+
+ Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
+ The ``name`` argument should be the name stem, as would be used on the command line.
+
+Note that this function lets errors propagate to the caller.
diff --git a/Lua API.html b/Lua API.html
index 47cf08ab6..04e899366 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -320,7 +320,7 @@ ul.auto-toc {
-
-
+
The current version of DFHack has extensive support for
+the Lua scripting language, providing access to:
+
+- Raw data structures used by the game.
+- Many C++ functions for high-level access to these
+structures, and interaction with dfhack itself.
+- Some functions exported by C++ plugins.
+
+
Lua code can be used both for writing scripts, which
+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
+implemented by Lua files located in hack/lua/...
+
+
DF structures described by the xml files in library/xml are exported
to lua code as a tree of objects and functions under the df global,
which broadly maps to the df namespace in C++.
@@ -764,28 +780,8 @@ string, global environment and command-line history file.
-
-
-
-dfhack.run_script(name[,args...])
-Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
-The name argument should be the name stem, as would be used on the command line.
-Note that the script is re-read from the file every time it is called, and errors
-are propagated to the caller.
-
-dfhack.with_suspend(f[,args...])
-Calls f with arguments after grabbing the DF core suspend lock.
-Suspending is necessary for accessing a consistent state of DF memory.
-Returned values and errors are propagated through after releasing
-the lock. It is safe to nest suspends.
-Every thread is allowed only one suspend per DF frame, so it is best
-to group operations together in one big critical section. A plugin
-can choose to run all lua code inside a C++-side suspend lock.
-
-
-
-
+
dfhack.error(msg[,level[,verbose]])
Throws a dfhack exception object with location and stack trace.
@@ -808,25 +804,6 @@ returning. Intended as a convenience function.
dfhack.saferesume(coroutine[,args...])
Compares to coroutine.resume like dfhack.safecall vs pcall.
-dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])
-Invokes fn with args, and after it returns or throws an
-error calls cleanup_fn with cleanup_args. Any return values from
-fn are propagated, and errors are re-thrown.
-The num_cleanup_args integer specifies the number of cleanup_args,
-and the always boolean specifies if cleanup should be called in any case,
-or only in case of an error.
-
-dfhack.with_finalize(cleanup_fn,fn[,args...])
-Calls fn with arguments, then finalizes with cleanup_fn.
-Implemented using call_with_finalizer(0,true,...).
-
-dfhack.with_onerror(cleanup_fn,fn[,args...])
-Calls fn with arguments, then finalizes with cleanup_fn on any thrown error.
-Implemented using call_with_finalizer(0,false,...).
-
-dfhack.with_temp_object(obj,fn[,args...])
-Calls fn(obj,args...), then finalizes with obj:delete().
-
dfhack.exception
Metatable of error objects used by dfhack. The objects have the
following properties:
@@ -859,6 +836,39 @@ following properties:
+
+
+
+dfhack.with_suspend(f[,args...])
+Calls f with arguments after grabbing the DF core suspend lock.
+Suspending is necessary for accessing a consistent state of DF memory.
+Returned values and errors are propagated through after releasing
+the lock. It is safe to nest suspends.
+Every thread is allowed only one suspend per DF frame, so it is best
+to group operations together in one big critical section. A plugin
+can choose to run all lua code inside a C++-side suspend lock.
+
+dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])
+Invokes fn with args, and after it returns or throws an
+error calls cleanup_fn with cleanup_args. Any return values from
+fn are propagated, and errors are re-thrown.
+The num_cleanup_args integer specifies the number of cleanup_args,
+and the always boolean specifies if cleanup should be called in any case,
+or only in case of an error.
+
+dfhack.with_finalize(cleanup_fn,fn[,args...])
+Calls fn with arguments, then finalizes with cleanup_fn.
+Implemented using call_with_finalizer(0,true,...).
+
+dfhack.with_onerror(cleanup_fn,fn[,args...])
+Calls fn with arguments, then finalizes with cleanup_fn on any thrown error.
+Implemented using call_with_finalizer(0,false,...).
+
+dfhack.with_temp_object(obj,fn[,args...])
+Calls fn(obj,args...), then finalizes with obj:delete().
+
+
+
This api is intended for storing configuration options in the world itself.
@@ -1470,15 +1480,45 @@ order using dfhack.safecall.
+
+
+
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:
+
+mkmodule(name)
+Creates an environment table for the module. Intended to be used as:
+
+local _ENV = mkmodule('foo')
+...
+return _ENV
+
+If called the second time, returns the same table; thus providing reload support.
+
+reload(name)
+Reloads a previously require-d module "name" from the file.
+Intended as a help for module development.
+
+dfhack.BASE_G
+This variable contains the root global environment table, which is
+used as a base for all module and script environments. Its contents
+should be kept limited to the standard Lua library and API described
+in this document.
+
+
+
-
+
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.
-
+
Implements extended burrow manipulations.
Events:
-
+
Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.
+
+
+
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.
+
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.
+When calling such scripts, always use '/' as the separator for
+directories, e.g. devel/lua-example.
+
Scripts are re-read from disk every time they are used
+(this may be changed later to check the file change time); however
+the global variable values persist in memory between calls.
+Every script gets its own separate environment for global
+variables.
+
Arguments are passed in to the scripts via the ... built-in
+quasi-variable; when the script is called by the DFHack core,
+they are all guaranteed to be non-nil strings.
+
DFHack core invokes the scripts in the core context (see above);
+however it is possible to call them from any lua code (including
+from other scripts) in any context, via the same function the core uses:
+
+dfhack.run_script(name[,args...])
+Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
+The name argument should be the name stem, as would be used on the command line.
+
+
+
Note that this function lets errors propagate to the caller.
+