summaryrefslogtreecommitdiffstats
path: root/trunk/source/clua.cc
diff options
context:
space:
mode:
authordshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-02 12:54:15 +0000
committerdshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-02 12:54:15 +0000
commitd5e5340c3926d1cf97f6cba151ffaecb20bfb35f (patch)
treed1faf7d5b27df8f3c523a8dd33357804118e62b1 /trunk/source/clua.cc
parent7b2204d69f21d7075e4666ee032d7a129081bc4b (diff)
downloadcrawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.tar.gz
crawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.zip
Integrated travel patch as of 20060727
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'trunk/source/clua.cc')
-rw-r--r--trunk/source/clua.cc2330
1 files changed, 2330 insertions, 0 deletions
diff --git a/trunk/source/clua.cc b/trunk/source/clua.cc
new file mode 100644
index 0000000000..21f2566c15
--- /dev/null
+++ b/trunk/source/clua.cc
@@ -0,0 +1,2330 @@
+#include "AppHdr.h"
+
+#ifdef CLUA_BINDINGS
+
+#include "clua.h"
+
+#include "abl-show.h"
+#include "command.h"
+#include "chardump.h"
+#include "food.h"
+#include "invent.h"
+#include "initfile.h"
+#include "itemname.h"
+#include "items.h"
+#include "item_use.h"
+#include "libutil.h"
+#include "macro.h"
+#include "message.h"
+#include "mon-util.h"
+#include "output.h"
+#include "player.h"
+#include "randart.h"
+#include "skills2.h"
+#include "spl-util.h"
+#include "stuff.h"
+#include "wpn-misc.h"
+
+#include <cstring>
+
+#ifdef HASH_CONTAINERS
+# include <hash_map>
+# define CHMAP HASH_CONTAINER_NS::hash_map
+#else
+# include <map>
+# define CHMAP std::map
+#endif
+
+#include <cctype>
+
+#define CL_RESETSTACK_RETURN(ls, oldtop, retval) \
+ if (true) {\
+ if (oldtop != lua_gettop(ls)) { \
+ lua_settop(ls, oldtop); \
+ } \
+ return (retval); \
+ } \
+ else
+
+CLua clua;
+
+CLua::CLua() : _state(NULL), sourced_files(), uniqindex(0L)
+{
+}
+
+CLua::~CLua()
+{
+ if (_state)
+ lua_close(_state);
+}
+
+// This has the disadvantage of repeatedly trying init_lua if it fails.
+lua_State *CLua::state()
+{
+ if (!_state)
+ init_lua();
+ return _state;
+}
+
+void CLua::setglobal(const char *name)
+{
+ lua_setglobal(state(), name);
+}
+
+void CLua::getglobal(const char *name)
+{
+ lua_getglobal(state(), name);
+}
+
+std::string CLua::setuniqregistry()
+{
+ char name[100];
+ snprintf(name, sizeof name, "__cru%lu", uniqindex++);
+ lua_pushstring(state(), name);
+ lua_insert(state(), -2);
+ lua_settable(state(), LUA_REGISTRYINDEX);
+
+ return (name);
+}
+
+void CLua::setregistry(const char *name)
+{
+ lua_pushstring(state(), name);
+ // Slide name round before the value
+ lua_insert(state(), -2);
+ lua_settable(state(), LUA_REGISTRYINDEX);
+}
+
+void CLua::getregistry(const char *name)
+{
+ lua_pushstring(state(), name);
+ lua_gettable(state(), LUA_REGISTRYINDEX);
+}
+
+void CLua::save(const char *file)
+{
+ if (!_state)
+ return;
+
+ CLuaSave clsave = { file, NULL };
+ callfn("c_save", "u", &clsave);
+ if (clsave.handle)
+ fclose(clsave.handle);
+}
+
+int CLua::file_write(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Expected filehandle at arg 1");
+ return (0);
+ }
+ CLuaSave *sf = static_cast<CLuaSave *>( lua_touserdata(ls, 1) );
+ if (!sf)
+ return (0);
+
+ FILE *f = sf->get_file();
+ if (!f)
+ return (0);
+
+ const char *text = luaL_checkstring(ls, 2);
+ if (text)
+ fprintf(f, "%s", text);
+ return (0);
+}
+
+FILE *CLua::CLuaSave::get_file()
+{
+ if (!handle)
+ handle = fopen(filename, "w");
+
+ return (handle);
+}
+
+void CLua::set_error(int err, lua_State *ls)
+{
+ if (!err)
+ {
+ error.clear();
+ return;
+ }
+ if (!ls && !(ls = _state))
+ {
+ error = "<LUA not initialized>";
+ return;
+ }
+ const char *serr = lua_tostring(ls, -1);
+ lua_pop(ls, 1);
+ error = serr? serr : "<Unknown error>";
+}
+
+int CLua::execstring(const char *s, const char *context)
+{
+ lua_State *ls = state();
+ int err = luaL_loadbuffer(ls, s, strlen(s), context);
+ if (err)
+ {
+ set_error(err, ls);
+ return err;
+ }
+ err = lua_pcall(ls, 0, 0, 0);
+ set_error(err, ls);
+ return err;
+}
+
+int CLua::execfile(const char *filename)
+{
+ if (sourced_files.find(filename) != sourced_files.end())
+ return 0;
+
+ sourced_files.insert(filename);
+
+ FILE *f = fopen(filename, "r");
+ if (f)
+ fclose(f);
+ else
+ {
+ error = std::string("Can't read ") + filename;
+ return -1;
+ }
+
+ lua_State *ls = state();
+ if (!ls)
+ return -1;
+
+ int err = luaL_loadfile(ls, filename);
+ if (!err)
+ err = lua_pcall(ls, 0, 0, 0);
+ set_error(err);
+ return (err);
+}
+
+bool CLua::runhook(const char *hook, const char *params, ...)
+{
+ error.clear();
+
+ lua_State *ls = state();
+ if (!ls)
+ return (false);
+
+ // Remember top of stack, for debugging porpoises
+ int stack_top = lua_gettop(ls);
+ lua_getglobal(ls, hook);
+ if (!lua_istable(ls, -1))
+ {
+ lua_pop(ls, 1);
+ CL_RESETSTACK_RETURN( ls, stack_top, false );
+ }
+ for (int i = 1; ; ++i)
+ {
+ int currtop = lua_gettop(ls);
+ lua_rawgeti(ls, -1, i);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_pop(ls, 1);
+ break;
+ }
+
+ // So what's on top *is* a function. Call it with the args we have.
+ va_list args;
+ va_start(args, params);
+ calltopfn(ls, params, args);
+ va_end(args);
+
+ lua_settop(ls, currtop);
+ }
+ CL_RESETSTACK_RETURN( ls, stack_top, true );
+}
+
+void CLua::fnreturns(const char *format, ...)
+{
+ lua_State *ls = _state;
+
+ if (!format || !ls)
+ return;
+
+ va_list args;
+ va_start(args, format);
+
+ vfnreturns(format, args);
+
+ va_end(args);
+}
+
+void CLua::vfnreturns(const char *format, va_list args)
+{
+ lua_State *ls = _state;
+ int nrets = return_count(ls, format);
+ int sp = -nrets - 1;
+
+ const char *gs = strchr(format, '>');
+ if (gs)
+ format = gs + 1;
+ else if ((gs = strchr(format, ':')))
+ format = gs + 1;
+
+ for (const char *run = format; *run; ++run)
+ {
+ char argtype = *run;
+ ++sp;
+ switch (argtype)
+ {
+ case 'u':
+ if (lua_islightuserdata(ls, sp))
+ *(va_arg(args, void**)) = lua_touserdata(ls, sp);
+ break;
+ case 'd':
+ if (lua_isnumber(ls, sp))
+ *(va_arg(args, int*)) = luaL_checkint(ls, sp);
+ break;
+ case 'b':
+ *(va_arg(args, bool *)) = lua_toboolean(ls, sp);
+ break;
+ case 's':
+ {
+ const char *s = lua_tostring(ls, sp);
+ if (s)
+ *(va_arg(args, std::string *)) = s;
+ break;
+ }
+ default:
+ break;
+ }
+
+ }
+ // Pop args off the stack
+ lua_pop(ls, nrets);
+}
+
+static void push_monster(lua_State *ls, monsters *mons);
+static int push_activity_interrupt(lua_State *ls, activity_interrupt_t *t);
+int CLua::push_args(lua_State *ls, const char *format, va_list args,
+ va_list *targ)
+{
+ if (!format)
+ {
+ if (targ)
+ va_copy(*targ, args);
+ return (0);
+ }
+
+ const char *cs = strchr(format, ':');
+ if (cs)
+ format = cs + 1;
+
+ int argc = 0;
+ for (const char *run = format; *run; run++) {
+ if (*run == '>')
+ break;
+
+ char argtype = *run;
+ ++argc;
+ switch (argtype) {
+ case 'u': // Light userdata
+ lua_pushlightuserdata(ls, va_arg(args, void*));
+ break;
+ case 's': // String
+ {
+ const char *s = va_arg(args, const char *);
+ if (s)
+ lua_pushstring(ls, s);
+ else
+ lua_pushnil(ls);
+ break;
+ }
+ case 'd': // Integer
+ lua_pushnumber(ls, va_arg(args, int));
+ break;
+ case 'L':
+ lua_pushnumber(ls, va_arg(args, long));
+ break;
+ case 'b':
+ lua_pushboolean(ls, va_arg(args, int));
+ break;
+ case 'M':
+ push_monster(ls, va_arg(args, monsters *));
+ break;
+ case 'A':
+ argc += push_activity_interrupt(
+ ls, va_arg(args, activity_interrupt_t *));
+ break;
+ default:
+ --argc;
+ break;
+ }
+ }
+ if (targ)
+ va_copy(*targ, args);
+ return (argc);
+}
+
+int CLua::return_count(lua_State *ls, const char *format)
+{
+ if (!format)
+ return (0);
+
+ const char *gs = strchr(format, '>');
+ if (gs)
+ return (strlen(gs + 1));
+
+ const char *cs = strchr(format, ':');
+ if (cs && isdigit(*format))
+ {
+ char *es = NULL;
+ int ci = strtol(format, &es, 10);
+ // We're capping return at 10 here, which is arbitrary, but avoids
+ // blowing the stack.
+ if (ci < 0)
+ ci = 0;
+ else if (ci > 5)
+ ci = 10;
+ return (ci);
+ }
+ return (0);
+}
+
+bool CLua::calltopfn(lua_State *ls, const char *params, va_list args,
+ int retc, va_list *copyto)
+{
+ // We guarantee to remove the function from the stack
+ int argc = push_args(ls, params, args, copyto);
+ if (retc == -1)
+ retc = return_count(ls, params);
+ int err = lua_pcall(ls, argc, retc, 0);
+ set_error(err, ls);
+ return (!err);
+}
+
+bool CLua::callbooleanfn(bool def, const char *fn, const char *params, ...)
+{
+ error.clear();
+ lua_State *ls = state();
+ if (!ls)
+ return (def);
+
+ int stacktop = lua_gettop(ls);
+
+ lua_getglobal(ls, fn);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_pop(ls, 1);
+ CL_RESETSTACK_RETURN(ls, stacktop, def);
+ }
+
+ va_list args;
+ va_start(args, params);
+ bool ret = calltopfn(ls, params, args, 1);
+ if (!ret)
+ CL_RESETSTACK_RETURN(ls, stacktop, def);
+
+ def = lua_toboolean(ls, -1);
+ CL_RESETSTACK_RETURN(ls, stacktop, def);
+}
+
+bool CLua::proc_returns(const char *par) const
+{
+ return (strchr(par, '>') != NULL);
+}
+
+bool CLua::callfn(const char *fn, const char *params, ...)
+{
+ error.clear();
+ lua_State *ls = state();
+ if (!ls)
+ return (false);
+
+ lua_getglobal(ls, fn);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_pop(ls, 1);
+ return (false);
+ }
+
+ va_list args;
+ va_list fnret;
+ va_start(args, params);
+ bool ret = calltopfn(ls, params, args, -1, &fnret);
+ if (ret)
+ {
+ // If we have a > in format, gather return params now.
+ if (proc_returns(params))
+ vfnreturns(params, fnret);
+ }
+ va_end(args);
+ va_end(fnret);
+ return (ret);
+}
+
+bool CLua::callfn(const char *fn, int nargs, int nret)
+{
+ error.clear();
+ lua_State *ls = state();
+ if (!ls)
+ return (false);
+
+ lua_getglobal(ls, fn);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_settop(ls, -nargs - 2);
+ return (false);
+ }
+
+ // Slide the function in front of its args and call it.
+ if (nargs)
+ lua_insert(ls, -nargs - 1);
+ int err = lua_pcall(ls, nargs, nret, 0);
+ set_error(err, ls);
+ return !err;
+}
+
+// Defined in Kills.cc because the kill bindings refer to Kills.cc local
+// structs
+extern void lua_open_kills(lua_State *ls);
+
+void lua_open_you(lua_State *ls);
+void lua_open_item(lua_State *ls);
+void lua_open_food(lua_State *ls);
+void lua_open_crawl(lua_State *ls);
+void lua_open_file(lua_State *ls);
+void lua_open_options(lua_State *ls);
+void lua_open_monsters(lua_State *ls);
+void lua_open_globals(lua_State *ls);
+
+
+void CLua::init_lua()
+{
+ if (_state)
+ return;
+
+ _state = lua_open();
+ if (!_state)
+ return;
+ luaopen_base(_state);
+ luaopen_string(_state);
+ luaopen_table(_state);
+
+ // Open Crawl bindings
+ lua_open_kills(_state);
+ lua_open_you(_state);
+ lua_open_item(_state);
+ lua_open_food(_state);
+ lua_open_crawl(_state);
+ lua_open_file(_state);
+ lua_open_options(_state);
+ lua_open_monsters(_state);
+
+ lua_open_globals(_state);
+
+ load_cmacro();
+ load_chooks();
+}
+
+void CLua::load_chooks()
+{
+ // All hook names must be chk_????
+ static const char *c_hooks =
+ "chk_startgame = { }"
+ ;
+ execstring(c_hooks, "base");
+}
+
+void CLua::load_cmacro()
+{
+ static const char *c_macro =
+ "function c_macro(fn)"
+ " if fn == nil then"
+ " if c_macro_coroutine ~= nil then"
+ " local coret, mret"
+ " coret, mret = coroutine.resume(c_macro_coroutine)"
+ " if not coret or not mret then"
+ " c_macro_coroutine = nil"
+ " c_macro_name = nil"
+ " end"
+ " if not coret and mret then"
+ " error(mret)"
+ " end"
+ " return (coret and mret)"
+ " end"
+ " return false"
+ " end"
+ " if _G[fn] == nil or type(_G[fn]) ~= 'function' then"
+ " return false"
+ " end"
+ " c_macro_name = fn"
+ " c_macro_coroutine = coroutine.create(_G[fn]) "
+ " return c_macro() "
+ "end";
+ execstring(c_macro, "base");
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#define LUAWRAP(name, wrapexpr) \
+ static int name(lua_State *ls) \
+ { \
+ wrapexpr; \
+ return (0); \
+ }
+
+#define LUARET1(name, type, val) \
+ static int name(lua_State *ls) \
+ { \
+ lua_push##type(ls, val); \
+ return (1); \
+ }
+
+#define LUARET2(name, type, val1, val2) \
+ static int name(lua_State *ls) \
+ { \
+ lua_push##type(ls, val1); \
+ lua_push##type(ls, val2); \
+ return (2); \
+ }
+
+template <class T> T *util_get_userdata(lua_State *ls, int ndx)
+{
+ return (lua_islightuserdata(ls, ndx))?
+ static_cast<T *>( lua_touserdata(ls, ndx) )
+ : NULL;
+}
+
+template <class T> T *clua_get_userdata(lua_State *ls, const char *mt)
+{
+ return static_cast<T*>( luaL_checkudata( ls, 1, mt ) );
+}
+
+static void clua_register_metatable(lua_State *ls, const char *tn,
+ const luaL_reg *lr,
+ int (*gcfn)(lua_State *ls) = NULL)
+{
+ int top = lua_gettop(ls);
+
+ luaL_newmetatable(ls, tn);
+ lua_pushstring(ls, "__index");
+ lua_pushvalue(ls, -2);
+ lua_settable(ls, -3);
+
+ if (gcfn)
+ {
+ lua_pushstring(ls, "__gc");
+ lua_pushcfunction(ls, gcfn);
+ lua_settable(ls, -3);
+ }
+
+ luaL_openlib(ls, NULL, lr, 0);
+
+ lua_settop(ls, top);
+}
+
+template <class T> T *clua_new_userdata(
+ lua_State *ls, const char *mt)
+{
+ void *udata = lua_newuserdata( ls, sizeof(T) );
+ luaL_getmetatable(ls, mt);
+ lua_setmetatable(ls, -2);
+ return static_cast<T*>( udata );
+}
+
+/////////////////////////////////////////////////////////////////////
+// Bindings to get information on the player
+//
+
+static const char *transform_name()
+{
+ switch (you.attribute[ATTR_TRANSFORMATION])
+ {
+ case TRAN_SPIDER:
+ return "spider";
+ case TRAN_BLADE_HANDS:
+ return "blade";
+ case TRAN_STATUE:
+ return "statue";
+ case TRAN_ICE_BEAST:
+ return "ice";
+ case TRAN_DRAGON:
+ return "dragon";
+ case TRAN_LICH:
+ return "lich";
+ case TRAN_SERPENT_OF_HELL:
+ return "serpent";
+ case TRAN_AIR:
+ return "air";
+ default:
+ return "";
+ }
+}
+
+LUARET1(you_turn_is_over, boolean, you.turn_is_over)
+LUARET1(you_name, string, you.your_name)
+LUARET1(you_race, string, species_name(you.species, you.experience_level))
+LUARET1(you_class, string, get_class_name(you.char_class))
+LUARET2(you_hp, number, you.hp, you.hp_max)
+LUARET2(you_mp, number, you.magic_points, you.max_magic_points)
+LUARET1(you_hunger, string, hunger_level())
+LUARET2(you_strength, number, you.strength, you.max_strength)
+LUARET2(you_intelligence, number, you.intel, you.max_intel)
+LUARET2(you_dexterity, number, you.dex, you.max_dex)
+LUARET1(you_exp, number, you.experience_level)
+LUARET1(you_exp_points, number, you.experience)
+LUARET1(you_res_poison, number, player_res_poison(false))
+LUARET1(you_res_fire, number, player_res_fire(false))
+LUARET1(you_res_cold, number, player_res_cold(false))
+LUARET1(you_res_draining, number, player_prot_life(false))
+LUARET1(you_res_shock, number, player_res_electricity(false))
+LUARET1(you_res_statdrain, number, player_sust_abil(false))
+LUARET1(you_res_mutation, number, wearing_amulet(AMU_RESIST_MUTATION, false))
+LUARET1(you_res_slowing, number, wearing_amulet(AMU_RESIST_SLOW, false))
+LUARET1(you_gourmand, boolean, wearing_amulet(AMU_THE_GOURMAND, false))
+LUARET1(you_levitating, boolean,
+ player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
+LUARET1(you_flying, boolean,
+ player_is_levitating() && wearing_amulet(AMU_CONTROLLED_FLIGHT))
+LUARET1(you_transform, string, transform_name())
+LUAWRAP(you_stop_activity, interrupt_activity(AI_FORCE_INTERRUPT))
+
+void lua_push_floor_items(lua_State *ls);
+static int you_floor_items(lua_State *ls)
+{
+ lua_push_floor_items(ls);
+ return (1);
+}
+
+static int l_you_spells(lua_State *ls)
+{
+ lua_newtable(ls);
+ int index = 0;
+ for (int i = 0; i < 52; ++i)
+ {
+ const int spell = get_spell_by_letter( index_to_letter(i) );
+ if (spell == SPELL_NO_SPELL)
+ continue;
+
+ lua_pushstring(ls, spell_title(spell));
+ lua_rawseti(ls, -2, ++index);
+ }
+ return (1);
+}
+
+static int l_you_abils(lua_State *ls)
+{
+ lua_newtable(ls);
+
+ std::vector<const char *>abils = get_ability_names();
+ for (int i = 0, size = abils.size(); i < size; ++i)
+ {
+ lua_pushstring(ls, abils[i]);
+ lua_rawseti(ls, -2, i + 1);
+ }
+ return (1);
+}
+
+static const struct luaL_reg you_lib[] =
+{
+ { "turn_is_over", you_turn_is_over },
+ { "spells" , l_you_spells },
+ { "abilities" , l_you_abils },
+ { "name" , you_name },
+ { "race" , you_race },
+ { "class" , you_class },
+ { "hp" , you_hp },
+ { "mp" , you_mp },
+ { "hunger" , you_hunger },
+ { "strength" , you_strength },
+ { "intelligence", you_intelligence },
+ { "dexterity" , you_dexterity },
+ { "exp" , you_exp },
+ { "exp_points" , you_exp_points },
+
+ { "res_poison" , you_res_poison },
+ { "res_fire" , you_res_fire },
+ { "res_cold" , you_res_cold },
+ { "res_draining", you_res_draining },
+ { "res_shock" , you_res_shock },
+ { "res_statdrain", you_res_statdrain },
+ { "res_mutation", you_res_mutation },
+ { "res_slowing", you_res_slowing },
+ { "gourmand", you_gourmand },
+ { "levitating", you_levitating },
+ { "flying", you_flying },
+ { "transform", you_transform },
+
+ { "stop_activity", you_stop_activity },
+
+ { "floor_items", you_floor_items },
+ { NULL, NULL },
+};
+
+void lua_open_you(lua_State *ls)
+{
+ luaL_openlib(ls, "you", you_lib, 0);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Bindings to get information on items. We must be extremely careful
+// to only hand out information the player already has.
+//
+
+static const item_def *excl_item = NULL;
+
+#define LUA_ITEM(name, n) \
+ if (!lua_islightuserdata(ls, n)) \
+ { \
+ luaL_argerror(ls, n, "Unexpected arg type"); \
+ return (0); \
+ } \
+ \
+ item_def *name = static_cast<item_def *>( lua_touserdata(ls, n ) ); \
+ if (excl_item && name != excl_item) \
+ { \
+ luaL_argerror(ls, n, "Unexpected item"); \
+ return (0); \
+ }
+
+void lua_push_inv_items(lua_State *ls);
+
+void lua_set_exclusive_item(const item_def *item)
+{
+ excl_item = item;
+}
+
+static int l_item_inventory(lua_State *ls)
+{
+ lua_push_inv_items(ls);
+ return (1);
+}
+
+static int l_item_index_to_letter(lua_State *ls)
+{
+ int index = luaL_checkint(ls, 1);
+ char sletter[2] = "?";
+ if (index >= 0 && index <= ENDOFPACK)
+ *sletter = index_to_letter(index);
+ lua_pushstring(ls, sletter);
+ return (1);
+}
+
+static int l_item_letter_to_index(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s || !*s || s[1])
+ return (0);
+ lua_pushnumber(ls, letter_to_index(*s));
+ return (1);
+}
+
+static int l_item_swap_slots(lua_State *ls)
+{
+ int slot1 = luaL_checkint(ls, 1),
+ slot2 = luaL_checkint(ls, 2);
+ bool verbose = lua_toboolean(ls, 3);
+ if (slot1 < 0 || slot1 >= ENDOFPACK ||
+ slot2 < 0 || slot2 >= ENDOFPACK ||
+ slot1 == slot2 || !is_valid_item(you.inv[slot1]))
+ return (0);
+
+ swap_inv_slots(slot1, slot2, verbose);
+
+ return (0);
+}
+
+static int l_item_wield(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ int slot = -1;
+ if (item && is_valid_item(*item) && in_inventory(*item))
+ slot = item->link;
+ bool res = wield_weapon(true, slot);
+ lua_pushboolean(ls, res);
+ return (1);
+}
+
+static int l_item_wear(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ return (0);
+
+ bool success = do_wear_armour(item->link, false);
+ lua_pushboolean(ls, success);
+ return (1);
+}
+
+static int l_item_puton(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ return (0);
+
+ lua_pushboolean(ls, puton_ring(item->link, false));
+ return (1);
+}
+
+static int l_item_remove(lua_State *ls)
+{
+ if (you.turn_is_over)
+ {
+ mpr("Turn is over");
+ return (0);
+ }
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ {
+ mpr("Bad item");
+ return (0);
+ }
+
+ int eq = get_equip_slot(item);
+ if (eq < 0 || eq >= NUM_EQUIP)
+ {
+ mpr("Item is not equipped");
+ return (0);
+ }
+
+ bool result = false;
+ if (eq == EQ_WEAPON)
+ result = wield_weapon(true, -1);
+ else if (eq == EQ_LEFT_RING || eq == EQ_RIGHT_RING || eq == EQ_AMULET)
+ result = remove_ring(item->link);
+ else
+ result = takeoff_armour(item->link);
+ lua_pushboolean(ls, result);
+ return (1);
+}
+
+static int l_item_drop(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ return (0);
+
+ int eq = get_equip_slot(item);
+ if (eq >= 0 && eq < NUM_EQUIP)
+ {
+ lua_pushboolean(ls, false);
+ lua_pushstring(ls, "Can't drop worn items");
+ return (2);
+ }
+
+ int qty = item->quantity;
+ if (lua_isnumber(ls, 2))
+ {
+ int q = luaL_checkint(ls, 2);
+ if (q >= 1 && q <= item->quantity)
+ qty = q;
+ }
+ lua_pushboolean(ls, drop_item(item->link, qty));
+ return (1);
+}
+
+static int item_on_floor(const item_def &item, int x, int y);
+
+static item_def *dmx_get_item(lua_State *ls, int ndx, int subndx)
+{
+ if (lua_istable(ls, ndx))
+ {
+ lua_rawgeti(ls, ndx, subndx);
+ item_def *item = util_get_userdata<item_def>(ls, -1);
+ lua_pop(ls, 1);
+
+ return (item);
+ }
+ return util_get_userdata<item_def>(ls, ndx);
+}
+
+static int dmx_get_qty(lua_State *ls, int ndx, int subndx)
+{
+ int qty = -1;
+ if (lua_istable(ls, ndx))
+ {
+ lua_rawgeti(ls, ndx, subndx);
+ if (lua_isnumber(ls, -1))
+ qty = luaL_checkint(ls, -1);
+ lua_pop(ls, 1);
+ }
+ else if (lua_isnumber(ls, ndx))
+ {
+ qty = luaL_checkint(ls, ndx);
+ }
+ return (qty);
+}
+
+static bool l_item_pickup2(item_def *item, int qty)
+{
+ if (!item || in_inventory(*item))
+ return (false);
+
+ int floor_link = item_on_floor(*item, you.x_pos, you.y_pos);
+ if (floor_link == NON_ITEM)
+ return (false);
+
+ return pickup_single_item(floor_link, qty);
+}
+
+static int l_item_pickup(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ if (lua_islightuserdata(ls, 1))
+ {
+ LUA_ITEM(item, 1);
+ int qty = item->quantity;
+ if (lua_isnumber(ls, 2))
+ qty = luaL_checkint(ls, 2);
+
+ if (l_item_pickup2(item, qty))
+ lua_pushnumber(ls, 1);
+ else
+ lua_pushnil(ls);
+ return (1);
+ }
+ else if (lua_istable(ls, 1))
+ {
+ int dropped = 0;
+ for (int i = 1; ; ++i)
+ {
+ lua_rawgeti(ls, 1, i);
+ item_def *item = dmx_get_item(ls, -1, 1);
+ int qty = dmx_get_qty(ls, -1, 2);
+ lua_pop(ls, 1);
+
+ if (l_item_pickup2(item, qty))
+ dropped++;
+ else
+ {
+ // Yes, we bail out on first failure.
+ break;
+ }
+ }
+ if (dropped)
+ lua_pushnumber(ls, dropped);
+ else
+ lua_pushnil(ls);
+ return (1);
+ }
+ return (0);
+}
+
+static int l_item_equipped(lua_State *ls)
+{
+ int eq = -1;
+ if (lua_isnumber(ls, 1))
+ eq = luaL_checkint(ls, 1);
+ else if (lua_isstring(ls, 1))
+ {
+ const char *eqname = lua_tostring(ls, 1);
+ if (!eqname)
+ return (0);
+ eq = equip_name_to_slot(eqname);
+ }
+
+ if (eq < 0 || eq >= NUM_EQUIP)
+ return (0);
+
+ if (you.equip[eq] != -1)
+ lua_pushlightuserdata(ls, &you.inv[you.equip[eq]]);
+ else
+ lua_pushnil(ls);
+
+ return (1);
+}
+
+static int l_item_class(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ bool terse = false;
+ if (lua_isboolean(ls, 2))
+ terse = lua_toboolean(ls, 2);
+
+ std::string s = item_class_name(item->base_type, terse);
+ lua_pushstring(ls, s.c_str());
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+// FIXME: Fold this back into itemname.cc.
+static const char *ring_types[] = {
+ "regeneration",
+ "protection",
+ "protection from fire",
+ "poison resistance",
+ "protection from cold",
+ "strength",
+ "slaying",
+ "see invisible",
+ "invisibility",
+ "hunger",
+ "teleportation",
+ "evasion",
+ "sustain abilities",
+ "sustenance",
+ "dexterity",
+ "intelligence",
+ "wizardry",
+ "magical power",
+ "levitation",
+ "life protection",
+ "protection from magic",
+ "fire",
+ "ice",
+ "teleport control",
+};
+
+static const char *amulet_types[] = {
+ "rage", "resist slowing", "clarity", "warding", "resist corrosion",
+ "gourmand", "conservation", "controlled flight", "inaccuracy",
+ "resist mutation"
+};
+
+static int l_item_subtype(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ if (item_type_known(*item))
+ {
+ const char *s = NULL;
+ if (item->base_type == OBJ_JEWELLERY)
+ {
+ if (item->sub_type < AMU_RAGE)
+ s = ring_types[item->sub_type];
+ else
+ s = amulet_types[ item->sub_type - AMU_RAGE ];
+ }
+
+ if (s)
+ lua_pushstring(ls, s);
+ else
+ lua_pushnil(ls);
+
+ lua_pushnumber(ls, item->sub_type);
+ return (2);
+ }
+ }
+
+ lua_pushnil(ls);
+ lua_pushnil(ls);
+ return (2);
+}
+
+static int l_item_cursed(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ bool cursed = item && item_ident(*item, ISFLAG_KNOW_CURSE)
+ && item_cursed(*item);
+ lua_pushboolean(ls, cursed);
+ return (1);
+}
+
+
+static int l_item_worn(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ int worn = get_equip_slot(item);
+ if (worn != -1)
+ lua_pushnumber(ls, worn);
+ else
+ lua_pushnil(ls);
+ if (worn != -1)
+ lua_pushstring(ls, equip_slot_to_name(worn));
+ else
+ lua_pushnil(ls);
+ return (2);
+}
+
+static int desc_code(const char *desc)
+{
+ if (!desc)
+ return DESC_PLAIN;
+
+ if (!strcmp("The", desc))
+ return DESC_CAP_THE;
+ else if (!strcmp("the", desc))
+ return DESC_NOCAP_THE;
+ else if (!strcmp("A", desc))
+ return DESC_CAP_A;
+ else if (!strcmp("a", desc))
+ return DESC_NOCAP_A;
+ else if (!strcmp("Your", desc))
+ return DESC_CAP_YOUR;
+ else if (!strcmp("your", desc))
+ return DESC_NOCAP_YOUR;
+ else if (!strcmp("its", desc))
+ return DESC_NOCAP_ITS;
+ else if (!strcmp("worn", desc))
+ return DESC_INVENTORY_EQUIP;
+ else if (!strcmp("inv", desc))
+ return DESC_INVENTORY;
+
+ return DESC_PLAIN;
+}
+
+static int l_item_name(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ int ndesc = DESC_PLAIN;
+ if (lua_isstring(ls, 2))
+ ndesc = desc_code(lua_tostring(ls, 2));
+ bool terse = lua_toboolean(ls, 3);
+ char bufitemname[ITEMNAME_SIZE];
+ item_name(*item, ndesc, bufitemname, terse);
+
+ lua_pushstring(ls, bufitemname);
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+static int l_item_quantity(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ lua_pushnumber(ls, item? item->quantity : 0);
+ return (1);
+}
+
+static int l_item_inslot(lua_State *ls)
+{
+ int index = luaL_checkint(ls, 1);
+ if (index >= 0 && index < 52 && is_valid_item(you.inv[index]))
+ lua_pushlightuserdata(ls, &you.inv[index]);
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+static int l_item_slot(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ int slot = in_inventory(*item)? item->link :
+ letter_to_index(item->slot);
+ lua_pushnumber(ls, slot);
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+static int l_item_ininventory(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ lua_pushboolean(ls, item && in_inventory(*item));
+ return (1);
+}
+
+static int l_item_equip_type(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item))
+ return (0);
+
+ int eq = -1;
+ if (item->base_type == OBJ_WEAPONS || item->base_type == OBJ_STAVES)
+ eq = EQ_WEAPON;
+ else if (item->base_type == OBJ_ARMOUR)
+ eq = armour_equip_slot(*item);
+ else if (item->base_type == OBJ_JEWELLERY)
+ eq = item->sub_type >= AMU_RAGE? EQ_AMULET : EQ_RINGS;
+
+ if (eq != -1)
+ lua_pushnumber(ls, eq);
+ else
+ lua_pushnil(ls);
+ if (eq != -1)
+ lua_pushstring(ls, equip_slot_to_name(eq));
+ else
+ lua_pushnil(ls);
+ return (2);
+}
+
+static int l_item_weap_skill(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item))
+ return (0);
+
+ int skill = weapon_skill( item->base_type, item->sub_type );
+ if (skill == SK_FIGHTING)
+ return (0);
+
+ lua_pushstring(ls, skill_name(skill));
+ return (1);
+}
+
+static int l_item_artifact(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item))
+ return (0);
+
+ lua_pushboolean(ls, item_ident(*item, ISFLAG_KNOW_PROPERTIES)
+ && (is_random_artefact(*item) || is_fixed_artefact(*item)));
+ return (1);
+}
+
+static int l_item_branded(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item) || !item_ident(*item, ISFLAG_KNOW_TYPE))
+ return (0);
+
+ bool branded = false;
+ switch (item->base_type)
+ {
+ case OBJ_WEAPONS:
+ branded = get_weapon_brand(*item) != SPWPN_NORMAL;
+ break;
+ case OBJ_ARMOUR:
+ branded = get_armour_ego_type(*item) != SPARM_NORMAL;
+ break;
+ case OBJ_MISSILES:
+ branded = get_ammo_brand(*item) != SPMSL_NORMAL;
+ break;
+ }
+ lua_pushboolean(ls, branded);
+ return (1);
+}
+
+static const struct luaL_reg item_lib[] =
+{
+ { "artifact", l_item_artifact },
+ { "branded", l_item_branded },
+ { "class", l_item_class },
+ { "subtype", l_item_subtype },
+ { "cursed", l_item_cursed },
+ { "worn", l_item_worn },
+ { "name", l_item_name },
+ { "quantity", l_item_quantity },
+ { "inslot", l_item_inslot },
+ { "slot", l_item_slot },
+ { "ininventory", l_item_ininventory },
+ { "inventory", l_item_inventory },
+ { "letter_to_index", l_item_letter_to_index },
+ { "index_to_letter", l_item_index_to_letter },
+ { "swap_slots", l_item_swap_slots },
+ { "wield", l_item_wield },
+ { "wear", l_item_wear },
+ { "puton", l_item_puton },
+ { "remove", l_item_remove },
+ { "drop", l_item_drop },
+ { "pickup", l_item_pickup },
+ { "equipped_at", l_item_equipped },
+ { "equip_type", l_item_equip_type },
+ { "weap_skill", l_item_weap_skill },
+
+ { NULL, NULL },
+};
+
+void lua_open_item(lua_State *ls)
+{
+ luaL_openlib(ls, "item", item_lib, 0);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Food information. Some of this information is spoily (such as whether
+// a given chunk is poisonous), but that can't be helped.
+//
+
+static int food_do_eat(lua_State *ls)
+{
+ bool eaten = false;
+ if (!you.turn_is_over)
+ eaten = eat_food(false);
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+static int food_prompt_floor(lua_State *ls)
+{
+ bool eaten = false;
+ if (!you.turn_is_over && (eaten = eat_from_floor()))
+ burden_change();
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+static int food_prompt_inventory(lua_State *ls)
+{
+ bool eaten = false;
+ if (!you.turn_is_over)
+ eaten = prompt_eat_from_inventory();
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+static int food_can_eat(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ bool hungercheck = true;
+
+ if (lua_isboolean(ls, 2))
+ hungercheck = lua_toboolean(ls, 2);
+
+ bool edible = item && can_ingest(item->base_type,
+ item->sub_type,
+ true,
+ true,
+ hungercheck);
+ lua_pushboolean(ls, edible);
+ return (1);
+}
+
+static int item_on_floor(const item_def &item, int x, int y)
+{
+ // Check if the item is on the floor and reachable
+ for (int link = igrd[x][y]; link != NON_ITEM; link = mitm[link].link)
+ {
+ if (&mitm[link] == &item)
+ return (link);
+ }
+ return (NON_ITEM);
+}
+
+static bool eat_item(const item_def &item)
+{
+ if (in_inventory(item))
+ {
+ eat_from_inventory(item.link);
+ burden_change();
+ you.turn_is_over = 1;
+
+ return (true);
+ }
+ else
+ {
+ int ilink = item_on_floor(item, you.x_pos, you.y_pos);
+
+ if (ilink != NON_ITEM)
+ {
+ eat_floor_item(ilink);
+ return (true);
+ }
+ return (false);
+ }
+}
+
+static int food_eat(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+
+ bool eaten = false;
+ if (!you.turn_is_over)
+ {
+ // When we get down to eating, we don't care if the eating is courtesy
+ // an un-ided amulet of the gourmand.
+ bool edible = item && can_ingest(item->base_type,
+ item->sub_type,
+ false,
+ false);
+ if (edible)
+ eaten = eat_item(*item);
+ }
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+// Giving away chunk type information is spoily.
+/*
+static int food_chunktype(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item && item->base_type == OBJ_FOOD && item->sub_type == FOOD_CHUNK)
+ {
+ int mons_type = item->plus;
+ int chunk_type = mons_corpse_thingy(mons_type);
+ const char *schunktype = "unknown";
+ switch (chunk_type)
+ {
+ case CE_HCL:
+ case CE_MUTAGEN_GOOD:
+ case CE_MUTAGEN_BAD:
+ case CE_MUTAGEN_RANDOM:
+ schunktype = "mutagenic";
+ break;
+ case CE_POISONOUS:
+ schunktype = "poisonous";
+ break;
+ case CE_CONTAMINATED:
+ schunktype = "contaminated";
+ break;
+ case CE_CLEAN:
+ schunktype = "clean";
+ break;
+ }
+ lua_pushstring(ls, schunktype);
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+*/
+
+static int food_rotting(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ bool rotting = false;
+ if (item && item->base_type == OBJ_FOOD && item->sub_type == FOOD_CHUNK)
+ {
+ rotting = item->special < 100;
+ }
+ lua_pushboolean(ls, rotting);
+ return (1);
+}
+
+static int food_ischunk(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ lua_pushboolean(ls,
+ item && item->base_type == OBJ_FOOD
+ && item->sub_type == FOOD_CHUNK);
+ return (1);
+}
+
+static const struct luaL_reg food_lib[] =
+{
+ { "do_eat", food_do_eat },
+ { "prompt_floor", food_prompt_floor },
+ { "prompt_inventory", food_prompt_inventory },
+ { "can_eat", food_can_eat },
+ { "eat", food_eat },
+ { "rotting", food_rotting },
+ { "ischunk", food_ischunk },
+ { NULL, NULL },
+};
+
+void lua_open_food(lua_State *ls)
+{
+ luaL_openlib(ls, "food", food_lib, 0);
+}
+
+/////////////////////////////////////////////////////////////////////
+// General game bindings.
+//
+
+static int crawl_mpr(lua_State *ls)
+{
+ const char *message = luaL_checkstring(ls, 1);
+ if (!message)
+ return (0);
+
+ int ch = MSGCH_PLAIN;
+ const char *channel = lua_tostring(ls, 2);
+ if (channel)
+ ch = str_to_channel(channel);
+ if (ch == -1)
+ ch = MSGCH_PLAIN;
+
+ mpr(message, ch);
+
+ return (0);
+}
+
+LUAWRAP(crawl_mesclr, mesclr())
+LUAWRAP(crawl_redraw_screen, redraw_screen())
+
+static int crawl_input_line(lua_State *ls)
+{
+ // This is arbitrary, but anybody entering so many characters is psychotic.
+ char linebuf[500];
+
+ get_input_line(linebuf, sizeof linebuf);
+ lua_pushstring(ls, linebuf);
+ return (1);
+}
+
+static int crawl_c_input_line(lua_State *ls)
+{
+ char linebuf[500];
+
+ bool valid = cancelable_get_line(linebuf, sizeof linebuf);
+ if (valid)
+ lua_pushstring(ls, linebuf);
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+LUARET1(crawl_getch, number, getch())
+LUARET1(crawl_kbhit, number, kbhit())
+LUAWRAP(crawl_flush_input, flush_input_buffer(FLUSH_LUA))
+
+static void crawl_sendkeys_proc(lua_State *ls, int argi)
+{
+ if (lua_isstring(ls, argi))
+ {
+ const char *keys = luaL_checkstring(ls, argi);
+ if (!keys)
+ return;
+
+ for ( ; *keys; ++keys)
+ macro_buf_add(*keys);
+ }
+ else if (lua_istable(ls, argi))
+ {
+ for (int i = 1; ; ++i)
+ {
+ lua_rawgeti(ls, argi, i);
+ if (lua_isnil(ls, -1))
+ {
+ lua_pop(ls, 1);
+ return;
+ }
+
+ crawl_sendkeys_proc(ls, lua_gettop(ls));
+ lua_pop(ls, 1);
+ }
+ }
+ else if (lua_isnumber(ls, argi))
+ {
+ int key = luaL_checkint(ls, argi);
+ macro_buf_add(key);
+ }
+}
+
+static int crawl_sendkeys(lua_State *ls)
+{
+ int top = lua_gettop(ls);
+ for (int i = 1; i <= top; ++i)
+ crawl_sendkeys_proc(ls, i);
+ return (0);
+}
+
+static int crawl_playsound(lua_State *ls)
+{
+ const char *sf = luaL_checkstring(ls, 1);
+ if (!sf)
+ return (0);
+ play_sound(sf);
+ return (0);
+}
+
+static int crawl_runmacro(lua_State *ls)
+{
+ const char *macroname = luaL_checkstring(ls, 1);
+ if (!macroname)
+ return (0);
+ run_macro(macroname);
+ return (0);
+}
+
+static int crawl_setopt(lua_State *ls)
+{
+ if (!lua_isstring(ls, 1))
+ return (0);
+
+ const char *s = lua_tostring(ls, 1);
+ if (s)
+ {
+ // Note that the conditional script can contain nested Lua[ ]Lua code.
+ read_options(s, true);
+ }
+
+ return (0);
+}
+
+static int crawl_bindkey(lua_State *ls)
+{
+ const char *s = NULL;
+ if (lua_isstring(ls, 1))
+ {
+ s = lua_tostring(ls, 1);
+ }
+
+ if (!s || !lua_isfunction(ls, 2) || !lua_gettop(ls) == 2)
+ return (0);
+
+ lua_pushvalue(ls, 2);
+ std::string name = clua.setuniqregistry();
+ if (lua_gettop(ls) != 2)
+ {
+ fprintf(stderr, "Stack top has changed!\n");
+ lua_settop(ls, 2);
+ }
+ macro_userfn(s, name.c_str());
+ return (0);
+}
+
+static int crawl_msgch_num(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s)
+ return (0);
+ int ch = str_to_channel(s);
+ if (ch == -1)
+ return (0);
+
+ lua_pushnumber(ls, ch);
+ return (1);
+}
+
+static int crawl_msgch_name(lua_State *ls)
+{
+ int num = luaL_checkint(ls, 1);
+ std::string name = channel_to_str(num);
+ lua_pushstring(ls, name.c_str());
+ return (1);
+}
+
+#define REGEX_METATABLE "crawl.regex"
+#define MESSF_METATABLE "crawl.messf"
+
+static int crawl_regex(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s)
+ return (0);
+
+
+ text_pattern **tpudata =
+ clua_new_userdata< text_pattern* >(ls, REGEX_METATABLE);
+ if (tpudata)
+ {
+ *tpudata = new text_pattern(s);
+ return (1);
+ }
+ return (0);
+}
+
+static int crawl_regex_find(lua_State *ls)
+{
+ text_pattern **pattern =
+ clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
+ if (!pattern)
+ return (0);
+
+ const char *text = luaL_checkstring(ls, -1);
+ if (!text)
+ return (0);
+
+ lua_pushboolean(ls, (*pattern)->matches(text));
+ return (1);
+}
+
+static int crawl_regex_gc(lua_State *ls)
+{
+ text_pattern **pattern =
+ clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
+ if (pattern)
+ delete *pattern;
+ return (0);
+}
+
+static const luaL_reg crawl_regex_ops[] =
+{
+ { "matches", crawl_regex_find },
+ { NULL, NULL }
+};
+
+static int crawl_message_filter(lua_State *ls)
+{
+ const char *pattern = luaL_checkstring(ls, 1);
+ if (!pattern)
+ return (0);
+
+ int num = lua_isnumber(ls, 2)? luaL_checkint(ls, 2) : -1;
+ message_filter **mf =
+ clua_new_userdata< message_filter* >( ls, MESSF_METATABLE );
+ if (mf)
+ {
+ *mf = new message_filter( num, pattern );
+ return (1);
+ }
+ return (0);
+}
+
+static int crawl_messf_matches(lua_State *ls)
+{
+ message_filter **mf =
+ clua_get_userdata< message_filter* >(ls, MESSF_METATABLE);
+ if (!mf)
+ return (0);
+
+ const char *pattern = luaL_checkstring(ls, 2);
+ int ch = luaL_checkint(ls, 3);
+ if (pattern)
+ {
+ bool filt = (*mf)->is_filtered(ch, pattern);
+ lua_pushboolean(ls, filt);
+ return (1);
+ }
+ return (0);
+}
+
+static int crawl_messf_gc(lua_State *ls)
+{
+ message_filter **pattern =
+ clua_get_userdata< message_filter* >(ls, REGEX_METATABLE);
+ if (pattern)
+ delete *pattern;
+ return (0);
+}
+
+static const luaL_reg crawl_messf_ops[] =
+{
+ { "matches", crawl_messf_matches },
+ { NULL, NULL }
+};
+
+static int crawl_trim(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s)
+ return (0);
+ std::string text = s;
+ trim_string(text);
+ lua_pushstring(ls, text.c_str());
+ return (1);
+}
+
+static int crawl_split(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1),
+ *token = luaL_checkstring(ls, 2);
+ if (!s || !token)
+ return (0);
+
+ std::vector<std::string> segs = split_string(token, s);
+ lua_newtable(ls);
+ for (int i = 0, count = segs.size(); i < count; ++i)
+ {
+ lua_pushstring(ls, segs[i].c_str());
+ lua_rawseti(ls, -2, i + 1);
+ }
+ return (1);
+}
+
+static const struct luaL_reg crawl_lib[] =
+{
+ { "mpr", crawl_mpr },
+ { "mesclr", crawl_mesclr },
+ { "redraw_screen", crawl_redraw_screen },
+ { "input_line", crawl_input_line },
+ { "c_input_line", crawl_c_input_line},
+ { "getch", crawl_getch },
+ { "kbhit", crawl_kbhit },
+ { "flush_input", crawl_flush_input },
+ { "sendkeys", crawl_sendkeys },
+ { "playsound", crawl_playsound },
+ { "runmacro", crawl_runmacro },
+ { "bindkey", crawl_bindkey },
+ { "setopt", crawl_setopt },
+ { "msgch_num", crawl_msgch_num },
+ { "msgch_name", crawl_msgch_name },
+
+ { "regex", crawl_regex },
+ { "message_filter", crawl_message_filter },
+ { "trim", crawl_trim },
+ { "split", crawl_split },
+ { NULL, NULL },
+};
+
+void lua_open_crawl(lua_State *ls)
+{
+ clua_register_metatable(ls, REGEX_METATABLE, crawl_regex_ops,
+ crawl_regex_gc);
+ clua_register_metatable(ls, MESSF_METATABLE, crawl_messf_ops,
+ crawl_messf_gc);
+
+ luaL_openlib(ls, "crawl", crawl_lib, 0);
+}
+
+///////////////////////////////////////////////////////////
+// File operations
+
+
+static const struct luaL_reg file_lib[] =
+{
+ { "write", CLua::file_write },
+ { NULL, NULL },
+};
+
+void lua_open_file(lua_State *ls)
+{
+ luaL_openlib(ls, "file", file_lib, 0);
+}
+
+
+////////////////////////////////////////////////////////////////
+// Option handling
+
+typedef int (*ohandler)(lua_State *ls, const char *name, void *data, bool get);
+struct option_handler
+{
+ const char *option;
+ void *data;
+ ohandler handler;
+};
+
+static int option_hboolean(lua_State *ls, const char *name, void *data,
+ bool get)
+{
+ if (get)
+ {
+ lua_pushboolean(ls, *static_cast<bool*>( data ));
+ return (1);
+ }
+ else
+ {
+ if (lua_isboolean(ls, 3))
+ *static_cast<bool*>( data ) = lua_toboolean(ls, 3);
+ return (0);
+ }
+}
+
+static option_handler handlers[] =
+{
+ // Boolean options come first
+ { "easy_open", &Options.easy_open, option_hboolean },
+ { "verbose_dump", &Options.verbose_dump, option_hboolean },
+ { "detailed_stat_dump", &Options.detailed_stat_dump, option_hboolean },
+ { "colour_map", &Options.colour_map, option_hboolean },
+ { "clean_map", &Options.clean_map, option_hboolean },
+ { "show_uncursed", &Options.show_uncursed, option_hboolean },
+ { "always_greet", &Options.always_greet, option_hboolean },
+ { "easy_open", &Options.easy_open, option_hboolean },
+ { "easy_armour", &Options.easy_armour, option_hboolean },
+ { "easy_butcher", &Options.easy_butcher, option_hboolean },
+ { "terse_hand", &Options.terse_hand, option_hboolean },
+ { "delay_message_clear", &Options.delay_message_clear, option_hboolean },
+ { "no_dark_brand", &Options.no_dark_brand, option_hboolean },
+ { "auto_list", &Options.auto_list, option_hboolean },
+ { "lowercase_invocations", &Options.lowercase_invocations,
+ option_hboolean },
+ { "pickup_thrown", &Options.pickup_thrown, option_hboolean },
+ { "pickup_dropped", &Options.pickup_dropped, option_hboolean },
+ { "show_waypoints", &Options.show_waypoints, option_hboolean },
+ { "item_colour", &Options.item_colour, option_hboolean },
+ { "target_zero_exp", &Options.target_zero_exp, option_hboolean },
+ { "target_wrap", &Options.target_wrap, option_hboolean },
+ { "easy_exit_menu", &Options.easy_exit_menu, option_hboolean },
+ { "dos_use_background_intensity", &Options.dos_use_background_intensity,
+ option_hboolean },
+};
+
+static const option_handler *get_handler(const char *optname)
+{
+ if (optname)
+ {
+ for (int i = 0, count = sizeof(handlers) / sizeof(*handlers);
+ i < count; ++i)
+ {
+ if (!strcmp(handlers[i].option, optname))
+ return &handlers[i];
+ }
+ }
+ return (NULL);
+}
+
+static int option_get(lua_State *ls)
+{
+ const char *opt = luaL_checkstring(ls, 2);
+ if (!opt)
+ return (0);
+
+ // Is this a Lua named option?
+ game_options::opt_map::iterator i = Options.named_options.find(opt);
+ if (i != Options.named_options.end())
+ {
+ const std::string &ov = i->second;
+ lua_pushstring(ls, ov.c_str());
+ return (1);
+ }
+
+ const option_handler *oh = get_handler(opt);
+ if (oh)
+ return (oh->handler(ls, opt, oh->data, true));
+
+ return (0);
+}
+
+static int option_set(lua_State *ls)
+{
+ const char *opt = luaL_checkstring(ls, 2);
+ if (!opt)
+ return (0);
+
+ const option_handler *oh = get_handler(opt);
+ if (oh)
+ oh->handler(ls, opt, oh->data, false);
+
+ return (0);
+}
+
+#define OPT_METATABLE "options.optaccess"
+void lua_open_options(lua_State *ls)
+{
+ int top = lua_gettop(ls);
+
+ luaL_newmetatable(ls, OPT_METATABLE);
+ lua_pushstring(ls, "__index");
+ lua_pushcfunction(ls, option_get);
+ lua_settable(ls, -3);
+
+ luaL_getmetatable(ls, OPT_METATABLE);
+ lua_pushstring(ls, "__newindex");
+ lua_pushcfunction(ls, option_set);
+ lua_settable(ls, -3);
+
+ lua_settop(ls, top);
+
+ // Create dummy userdata to front for our metatable
+ int *dummy = static_cast<int *>( lua_newuserdata(ls, sizeof(int)) );
+ // Mystic number
+ *dummy = 42;
+
+ luaL_getmetatable(ls, OPT_METATABLE);
+ lua_setmetatable(ls, -2);
+
+ clua.setglobal("options");
+}
+
+/////////////////////////////////////////////////////////////////////
+// Monster handling
+
+#define MONS_METATABLE "monster.monsaccess"
+struct MonsterWrap
+{
+ monsters *mons;
+ long turn;
+};
+
+static int l_mons_name(lua_State *ls, monsters *mons, const char *attr)
+{
+ char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
+ moname(mons->type, true, DESC_PLAIN, monnamebuf);
+ lua_pushstring(ls, monnamebuf);
+ return (1);
+}
+
+static int l_mons_x(lua_State *ls, monsters *mons, const char *attr)
+{
+ lua_pushnumber(ls, int(mons->x) - int(you.x_pos));
+ return (1);
+}
+
+static int l_mons_y(lua_State *ls, monsters *mons, const char *attr)
+{
+ lua_pushnumber(ls, int(mons->y) - int(you.y_pos));
+ return (1);
+}
+
+struct MonsAccessor {
+ const char *attribute;
+ int (*accessor)(lua_State *ls, monsters *mons, const char *attr);
+};
+
+static MonsAccessor mons_attrs[] =
+{
+ { "name", l_mons_name },
+ { "x" , l_mons_x },
+ { "y" , l_mons_y },
+};
+
+static int monster_get(lua_State *ls)
+{
+ MonsterWrap *mw = clua_get_userdata< MonsterWrap >(ls, MONS_METATABLE);
+ if (!mw || mw->turn != you.num_turns || !mw->mons)
+ return (0);
+
+ const char *attr = luaL_checkstring(ls, 2);
+ if (!attr)
+ return (0);
+
+ for (unsigned i = 0; i < sizeof(mons_attrs) / sizeof(mons_attrs[0]); ++i)
+ {
+ if (!strcmp(attr, mons_attrs[i].attribute))
+ return (mons_attrs[i].accessor(ls, mw->mons, attr));
+ }
+
+ return (0);
+}
+
+// We currently permit no set operations on monsters
+static int monster_set(lua_State *ls)
+{
+ return (0);
+}
+
+static int push_activity_interrupt(lua_State *ls, activity_interrupt_t *t)
+{
+ if (!t->data)
+ {
+ lua_pushnil(ls);
+ return 0;
+ }
+
+ switch (t->apt)
+ {
+ case AIP_HP_LOSS:
+ {
+ const ait_hp_loss *ahl = (const ait_hp_loss *) t->data;
+ lua_pushnumber(ls, ahl->hp);
+ lua_pushnumber(ls, ahl->hurt_type);
+ return 1;
+ }
+ case AIP_INT:
+ lua_pushnumber(ls, *(const int *) t->data);
+ break;
+ case AIP_STRING:
+ lua_pushstring(ls, (const char *) t->data);
+ break;
+ case AIP_MONSTER:
+ // FIXME: We're casting away the const...
+ push_monster(ls, (monsters *) t->data);
+ break;
+ default:
+ lua_pushnil(ls);
+ break;
+ }
+ return 0;
+}
+
+static void push_monster(lua_State *ls, monsters *mons)
+{
+ MonsterWrap *mw = clua_new_userdata< MonsterWrap >(ls, MONS_METATABLE);
+ mw->turn = you.num_turns;
+ mw->mons = mons;
+}
+
+void lua_open_monsters(lua_State *ls)
+{
+ luaL_newmetatable(ls, MONS_METATABLE);
+ lua_pushstring(ls, "__index");
+ lua_pushcfunction(ls, monster_get);
+ lua_settable(ls, -3);
+
+ lua_pushstring(ls, "__newindex");
+ lua_pushcfunction(ls, monster_set);
+ lua_settable(ls, -3);
+
+ // Pop the metatable off the stack.
+ lua_pop(ls, 1);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Miscellaneous globals
+
+#define PATTERN_FLUSH_CEILING 100
+
+typedef CHMAP<std::string, text_pattern> pattern_map;
+static pattern_map pattern_cache;
+
+static text_pattern &get_text_pattern(const std::string &s, bool checkcase)
+{
+ pattern_map::iterator i = pattern_cache.find(s);
+ if (i != pattern_cache.end())
+ return i->second;
+
+ if (pattern_cache.size() > PATTERN_FLUSH_CEILING)
+ pattern_cache.clear();
+
+ pattern_cache[s] = text_pattern(s, !checkcase);
+ return pattern_cache[s];
+}
+
+static int lua_pmatch(lua_State *ls)
+{
+ const char *pattern = luaL_checkstring(ls, 1);
+ if (!pattern)
+ return (0);
+
+ const char *text = luaL_checkstring(ls, 2);
+ if (!text)
+ return (0);
+
+ bool checkcase = true;
+ if (lua_isboolean(ls, 3))
+ checkcase = lua_toboolean(ls, 3);
+
+ text_pattern &tp = get_text_pattern(pattern, checkcase);
+ lua_pushboolean( ls, tp.matches(text) );
+ return (1);
+}
+
+void lua_open_globals(lua_State *ls)
+{
+ lua_pushcfunction(ls, lua_pmatch);
+ lua_setglobal(ls, "pmatch");
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// lua_text_pattern
+
+// We could simplify this a great deal by just using lex and yacc, but I
+// don't know if we want to introduce them.
+
+struct lua_pat_op {
+ const char *token;
+ const char *luatok;
+
+ bool pretext; // Does this follow a pattern?
+ bool posttext; // Is this followed by a pattern?
+};
+
+static lua_pat_op pat_ops[] = {
+ { "<<", " ( ", false, true },
+ { ">>", " ) ", true, false },
+ { "!!", " not ", false, true },
+ { "==", " == ", true, true },
+ { "^^", " ~= ", true, true },
+ { "&&", " and ", true, true },
+ { "||", " or ", true, true },
+};
+
+unsigned long lua_text_pattern::lfndx = 0L;
+
+bool lua_text_pattern::is_lua_pattern(const std::string &s)
+{
+ for (int i = 0, size = sizeof(pat_ops) / sizeof(*pat_ops);
+ i < size; ++i)
+ {
+ if (s.find(pat_ops[i].token) != std::string::npos)
+ return (true);
+ }
+ return (false);
+}
+
+lua_text_pattern::lua_text_pattern(const std::string &_pattern)
+ : translated(false), isvalid(true), pattern(_pattern), lua_fn_name()
+{
+ lua_fn_name = new_fn_name();
+}
+
+lua_text_pattern::~lua_text_pattern()
+{
+ if (translated && !lua_fn_name.empty())
+ {
+ lua_State *ls = clua;
+ if (ls)
+ {
+ lua_pushnil(ls);
+ clua.setglobal(lua_fn_name.c_str());
+ }
+ }
+}
+
+bool lua_text_pattern::valid() const
+{
+ return translated? isvalid : translate();
+}
+
+bool lua_text_pattern::matches( const std::string &s ) const
+{
+ if (isvalid && !translated)
+ translate();
+
+ if (!isvalid)
+ return (false);
+
+ return clua.callbooleanfn(false, lua_fn_name.c_str(), "s", s.c_str());
+}
+
+void lua_text_pattern::pre_pattern(std::string &pat, std::string &fn) const
+{
+ // Trim trailing spaces
+ pat.erase( pat.find_last_not_of(" \t\n\r") + 1 );
+
+ fn += " pmatch([[";
+ fn += pat;
+ fn += "]], text, false) ";
+
+ pat.clear();
+}
+
+void lua_text_pattern::post_pattern(std::string &pat, std::string &fn) const
+{
+ pat.erase( 0, pat.find_first_not_of(" \t\n\r") );
+
+ fn += " pmatch([[";
+ fn += pat;
+ fn += "]], text, false) ";
+
+ pat.clear();
+}
+
+std::string lua_text_pattern::new_fn_name()
+{
+ char buf[100];
+ snprintf(buf, sizeof buf, "__ch_stash_search_%lu", lfndx++);
+ return (buf);
+}
+
+bool lua_text_pattern::translate() const
+{
+ if (translated || !isvalid)
+ return false;
+
+ std::string textp;
+ std::string luafn;
+ const lua_pat_op *currop = NULL;
+ for (std::string::size_type i = 0; i < pattern.length(); ++i)
+ {
+ bool match = false;
+ for (unsigned p = 0; p < sizeof pat_ops / sizeof *pat_ops; ++p)
+ {
+ const lua_pat_op &lop = pat_ops[p];
+ if (pattern.find(lop.token, i) == i)
+ {
+ match = true;
+ if (lop.pretext && (!currop || currop->posttext))
+ {
+ if (currop)
+ textp.erase(0, textp.find_first_not_of(" \r\n\t"));
+ pre_pattern(textp, luafn);
+ }
+
+ currop = &lop;
+ luafn += lop.luatok;
+
+ i += strlen( lop.token ) - 1;
+
+ break;
+ }
+ }
+
+ if (match)
+ continue;
+
+ textp += pattern[i];
+ }
+
+ if (currop && currop->posttext)
+ post_pattern(textp, luafn);
+
+ luafn = "function " + lua_fn_name + "(text) return " + luafn + " end";
+
+ const_cast<lua_text_pattern *>(this)->translated = true;
+
+ int err = clua.execstring( luafn.c_str(), "stash-search" );
+ if (err)
+ {
+ lua_text_pattern *self = const_cast<lua_text_pattern *>(this);
+ self->isvalid = self->translated = false;
+ }
+
+ return translated;
+}
+
+#endif // CLUA_BINDINGS