Files
nethack/src/nhlua.c
PatR edf0e3e673 add Lua to Qt's "About nethack"
Add "Lua" and its version number of the 'About' popup.  No copyright
information is included since neither nethack's nor Qt's is shown.

Lua copyright text is included in the output of '#version'.
2021-12-31 18:15:34 -08:00

1464 lines
37 KiB
C

/* NetHack 3.7 nhlua.c $NHDT-Date: 1580506559 2020/01/31 21:35:59 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.32 $ */
/* Copyright (c) 2018 by Pasi Kallinen */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "dlb.h"
/*
#- include <lua5.3/lua.h>
#- include <lua5.3/lualib.h>
#- include <lua5.3/lauxlib.h>
*/
/* */
/* lua_CFunction prototypes */
#ifdef DUMPLOG
static int nhl_dump_fmtstr(lua_State *);
#endif /* DUMPLOG */
static int nhl_dnum_name(lua_State *);
static int nhl_stairways(lua_State *);
static int nhl_pushkey(lua_State *);
static int nhl_doturn(lua_State *);
static int nhl_debug_flags(lua_State *);
static int nhl_test(lua_State *);
static int nhl_getmap(lua_State *);
static void nhl_add_table_entry_bool(lua_State *, const char *, boolean);
static char splev_typ2chr(schar);
static int nhl_gettrap(lua_State *);
static int nhl_deltrap(lua_State *);
#if 0
static int nhl_setmap(lua_State *);
#endif
static int nhl_pline(lua_State *);
static int nhl_verbalize(lua_State *);
static int nhl_parse_config(lua_State *);
static int nhl_menu(lua_State *);
static int nhl_getlin(lua_State *);
static int nhl_makeplural(lua_State *);
static int nhl_makesingular(lua_State *);
static int nhl_s_suffix(lua_State *);
static int nhl_ing_suffix(lua_State *);
static int nhl_an(lua_State *);
static int nhl_rn2(lua_State *);
static int nhl_random(lua_State *);
static int nhl_level_difficulty(lua_State *);
static void init_nhc_data(lua_State *);
static int nhl_push_anything(lua_State *, int, void *);
static int nhl_meta_u_index(lua_State *);
static int nhl_meta_u_newindex(lua_State *);
static int nhl_u_clear_inventory(lua_State *);
static int nhl_u_giveobj(lua_State *);
static void init_u_data(lua_State *);
static int nhl_set_package_path(lua_State *, const char *);
static int traceback_handler(lua_State *);
static const char *nhcore_call_names[NUM_NHCORE_CALLS] = {
"start_new_game",
"restore_old_game",
"moveloop_turn",
"game_exit",
};
static boolean nhcore_call_available[NUM_NHCORE_CALLS];
void
l_nhcore_init(void)
{
if ((g.luacore = nhl_init()) != 0) {
if (!nhl_loadlua(g.luacore, "nhcore.lua")) {
g.luacore = (lua_State *) 0;
} else {
int i;
for (i = 0; i < NUM_NHCORE_CALLS; i++)
nhcore_call_available[i] = TRUE;
}
}
}
void
l_nhcore_done(void)
{
if (g.luacore) {
nhl_done(g.luacore);
g.luacore = 0;
}
}
void
l_nhcore_call(int callidx)
{
int ltyp;
if (callidx < 0 || callidx >= NUM_NHCORE_CALLS
|| !g.luacore || !nhcore_call_available[callidx])
return;
lua_getglobal(g.luacore, "nhcore");
if (!lua_istable(g.luacore, -1)) {
/*impossible("nhcore is not a lua table");*/
nhl_done(g.luacore);
g.luacore = 0;
return;
}
lua_getfield(g.luacore, -1, nhcore_call_names[callidx]);
ltyp = lua_type(g.luacore, -1);
if (ltyp == LUA_TFUNCTION) {
lua_remove(g.luacore, -2); /* nhcore_call_names[callidx] */
lua_remove(g.luacore, -2); /* nhcore */
lua_call(g.luacore, 0, 1);
} else {
/*impossible("nhcore.%s is not a lua function",
nhcore_call_names[callidx]);*/
nhcore_call_available[callidx] = FALSE;
}
}
DISABLE_WARNING_UNREACHABLE_CODE
void
nhl_error(lua_State *L, const char *msg)
{
lua_Debug ar;
char buf[BUFSZ];
lua_getstack(L, 1, &ar);
lua_getinfo(L, "lS", &ar);
Sprintf(buf, "%s (line %d ", msg, ar.currentline);
Sprintf(eos(buf), "%.*s)",
/* (max length of ar.short_src is actually LUA_IDSIZE
so this is overkill for it, but crucial for ar.source) */
(int) (sizeof buf - (strlen(buf) + sizeof ")")),
ar.short_src); /* (used to be 'ar.source' here) */
lua_pushstring(L, buf);
#if 0 /* defined(PANICTRACE) && !defined(NO_SIGNALS) */
panictrace_setsignals(FALSE);
#endif
(void) lua_error(L);
/*NOTREACHED*/
/* UNREACHABLE_CODE */
}
RESTORE_WARNING_UNREACHABLE_CODE
/* Check that parameters are nothing but single table,
or if no parameters given, put empty table there */
void
lcheck_param_table(lua_State *L)
{
int argc = lua_gettop(L);
if (argc < 1)
lua_createtable(L, 0, 0);
/* discard any extra arguments passed in */
lua_settop(L, 1);
luaL_checktype(L, 1, LUA_TTABLE);
}
schar
get_table_mapchr(lua_State *L, const char *name)
{
char *ter;
xchar typ;
ter = get_table_str(L, name);
typ = check_mapchr(ter);
if (typ == INVALID_TYPE)
nhl_error(L, "Erroneous map char");
if (ter)
free(ter);
return typ;
}
schar
get_table_mapchr_opt(lua_State *L, const char *name, schar defval)
{
char *ter;
xchar typ;
ter = get_table_str_opt(L, name, emptystr);
if (name && *ter) {
typ = check_mapchr(ter);
if (typ == INVALID_TYPE)
nhl_error(L, "Erroneous map char");
} else
typ = defval;
if (ter)
free(ter);
return typ;
}
void
nhl_add_table_entry_int(lua_State *L, const char *name, int value)
{
lua_pushstring(L, name);
lua_pushinteger(L, value);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_char(lua_State *L, const char *name, char value)
{
char buf[2];
Sprintf(buf, "%c", value);
lua_pushstring(L, name);
lua_pushstring(L, buf);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_str(lua_State *L, const char *name, const char *value)
{
lua_pushstring(L, name);
lua_pushstring(L, value);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_bool(lua_State *L, const char *name, boolean value)
{
lua_pushstring(L, name);
lua_pushboolean(L, value);
lua_rawset(L, -3);
}
/* converting from special level "map character" to levl location type
and back. order here is important. */
const struct {
char ch;
schar typ;
} char2typ[] = {
{ ' ', STONE },
{ '#', CORR },
{ '.', ROOM },
{ '-', HWALL },
{ '-', TLCORNER },
{ '-', TRCORNER },
{ '-', BLCORNER },
{ '-', BRCORNER },
{ '-', CROSSWALL },
{ '-', TUWALL },
{ '-', TDWALL },
{ '-', TLWALL },
{ '-', TRWALL },
{ '-', DBWALL },
{ '|', VWALL },
{ '+', DOOR },
{ 'A', AIR },
{ 'C', CLOUD },
{ 'S', SDOOR },
{ 'H', SCORR },
{ '{', FOUNTAIN },
{ '\\', THRONE },
{ 'K', SINK },
{ '}', MOAT },
{ 'P', POOL },
{ 'L', LAVAPOOL },
{ 'I', ICE },
{ 'W', WATER },
{ 'T', TREE },
{ 'F', IRONBARS }, /* Fe = iron */
{ 'x', MAX_TYPE }, /* "see-through" */
{ 'B', CROSSWALL }, /* hack: boundary location */
{ 'w', MATCH_WALL }, /* IS_STWALL() */
{ '\0', STONE },
};
schar
splev_chr2typ(char c)
{
int i;
for (i = 0; char2typ[i].ch; i++)
if (c == char2typ[i].ch)
return char2typ[i].typ;
return (INVALID_TYPE);
}
schar
check_mapchr(const char *s)
{
if (s && strlen(s) == 1)
return splev_chr2typ(s[0]);
return INVALID_TYPE;
}
static char
splev_typ2chr(schar typ)
{
int i;
for (i = 0; char2typ[i].typ < MAX_TYPE; i++)
if (typ == char2typ[i].typ)
return char2typ[i].ch;
return 'x';
}
/* local t = gettrap(x,y); */
static int
nhl_gettrap(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 2) {
int x = (int) lua_tointeger(L, 1);
int y = (int) lua_tointeger(L, 2);
if (x >= 0 && x < COLNO && y >= 0 && y < ROWNO) {
struct trap *ttmp = t_at(x,y);
if (ttmp) {
lua_newtable(L);
nhl_add_table_entry_int(L, "tx", ttmp->tx);
nhl_add_table_entry_int(L, "ty", ttmp->ty);
nhl_add_table_entry_int(L, "ttyp", ttmp->ttyp);
nhl_add_table_entry_str(L, "ttyp_name",
get_trapname_bytype(ttmp->ttyp));
nhl_add_table_entry_bool(L, "tseen", ttmp->tseen);
nhl_add_table_entry_bool(L, "madeby_u", ttmp->madeby_u);
switch (ttmp->ttyp) {
case SQKY_BOARD:
nhl_add_table_entry_int(L, "tnote", ttmp->tnote);
break;
case ROLLING_BOULDER_TRAP:
nhl_add_table_entry_int(L, "launchx", ttmp->launch.x);
nhl_add_table_entry_int(L, "launchy", ttmp->launch.y);
nhl_add_table_entry_int(L, "launch2x", ttmp->launch2.x);
nhl_add_table_entry_int(L, "launch2y", ttmp->launch2.y);
break;
case PIT:
case SPIKED_PIT:
nhl_add_table_entry_int(L, "conjoined", ttmp->conjoined);
break;
}
return 1;
} else
nhl_error(L, "No trap at location");
} else
nhl_error(L, "Coordinates out of range");
} else
nhl_error(L, "Wrong args");
return 0;
}
/* deltrap(x,y); */
static int
nhl_deltrap(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 2) {
int x = (int) lua_tointeger(L, 1);
int y = (int) lua_tointeger(L, 2);
if (x >= 0 && x < COLNO && y >= 0 && y < ROWNO) {
struct trap *ttmp = t_at(x,y);
if (ttmp)
deltrap(ttmp);
}
}
return 0;
}
DISABLE_WARNING_UNREACHABLE_CODE
/* local loc = getmap(x,y) */
static int
nhl_getmap(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 2) {
int x = (int) lua_tointeger(L, 1);
int y = (int) lua_tointeger(L, 2);
if (x >= 0 && x < COLNO && y >= 0 && y < ROWNO) {
char buf[BUFSZ];
lua_newtable(L);
/* FIXME: some should be boolean values */
nhl_add_table_entry_int(L, "glyph", levl[x][y].glyph);
nhl_add_table_entry_int(L, "typ", levl[x][y].typ);
nhl_add_table_entry_str(L, "typ_name",
levltyp_to_name(levl[x][y].typ));
Sprintf(buf, "%c", splev_typ2chr(levl[x][y].typ));
nhl_add_table_entry_str(L, "mapchr", buf);
nhl_add_table_entry_int(L, "seenv", levl[x][y].seenv);
nhl_add_table_entry_bool(L, "horizontal", levl[x][y].horizontal);
nhl_add_table_entry_bool(L, "lit", levl[x][y].lit);
nhl_add_table_entry_bool(L, "waslit", levl[x][y].waslit);
nhl_add_table_entry_int(L, "roomno", levl[x][y].roomno);
nhl_add_table_entry_bool(L, "edge", levl[x][y].edge);
nhl_add_table_entry_bool(L, "candig", levl[x][y].candig);
nhl_add_table_entry_bool(L, "has_trap", t_at(x,y) ? 1 : 0);
/* TODO: FIXME: levl[x][y].flags */
lua_pushliteral(L, "flags");
lua_newtable(L);
if (IS_DOOR(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "nodoor",
(levl[x][y].flags == D_NODOOR));
nhl_add_table_entry_bool(L, "broken",
(levl[x][y].flags & D_BROKEN));
nhl_add_table_entry_bool(L, "isopen",
(levl[x][y].flags & D_ISOPEN));
nhl_add_table_entry_bool(L, "closed",
(levl[x][y].flags & D_CLOSED));
nhl_add_table_entry_bool(L, "locked",
(levl[x][y].flags & D_LOCKED));
nhl_add_table_entry_bool(L, "trapped",
(levl[x][y].flags & D_TRAPPED));
} else if (IS_ALTAR(levl[x][y].typ)) {
/* TODO: bits 0, 1, 2 */
nhl_add_table_entry_bool(L, "shrine",
(levl[x][y].flags & AM_SHRINE));
} else if (IS_THRONE(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "looted",
(levl[x][y].flags & T_LOOTED));
} else if (levl[x][y].typ == TREE) {
nhl_add_table_entry_bool(L, "looted",
(levl[x][y].flags & TREE_LOOTED));
nhl_add_table_entry_bool(L, "swarm",
(levl[x][y].flags & TREE_SWARM));
} else if (IS_FOUNTAIN(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "looted",
(levl[x][y].flags & F_LOOTED));
nhl_add_table_entry_bool(L, "warned",
(levl[x][y].flags & F_WARNED));
} else if (IS_SINK(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "pudding",
(levl[x][y].flags & S_LPUDDING));
nhl_add_table_entry_bool(L, "dishwasher",
(levl[x][y].flags & S_LDWASHER));
nhl_add_table_entry_bool(L, "ring",
(levl[x][y].flags & S_LRING));
}
/* TODO: drawbridges, walls, ladders, room=>ICED_xxx */
lua_settable(L, -3);
return 1;
} else {
/* TODO: return zerorm instead? */
nhl_error(L, "Coordinates out of range");
return 0;
}
} else {
nhl_error(L, "Incorrect arguments");
return 0;
}
return 1;
}
RESTORE_WARNING_CONDEXPR_IS_CONSTANT
/* pline("It hits!") */
static int
nhl_pline(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
pline("%s", luaL_checkstring(L, 1));
else
nhl_error(L, "Wrong args");
return 0;
}
/* verbalize("Fool!") */
static int
nhl_verbalize(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
verbalize("%s", luaL_checkstring(L, 1));
else
nhl_error(L, "Wrong args");
return 0;
}
/* parse_config("OPTIONS=!color") */
static int
nhl_parse_config(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
parse_conf_str(luaL_checkstring(L, 1), parse_config_line);
else
nhl_error(L, "Wrong args");
return 0;
}
/* local windowtype = get_config("windowtype"); */
static int
nhl_get_config(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
lua_pushstring(L, get_option_value(luaL_checkstring(L, 1)));
return 1;
} else
nhl_error(L, "Wrong args");
return 0;
}
/*
str = getlin("What do you want to call this dungeon level?");
*/
static int
nhl_getlin(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
const char *prompt = luaL_checkstring(L, 1);
char buf[BUFSZ];
getlin(prompt, buf);
lua_pushstring(L, buf);
return 1;
}
nhl_error(L, "Wrong args");
return 0;
}
/*
selected = menu("prompt", default, pickX, { "a" = "option a", "b" = "option b", ...})
pickX = 0,1,2, or "none", "one", "any" (PICK_X in code)
selected = menu("prompt", default, pickX,
{ {key:"a", text:"option a"}, {key:"b", text:"option b"}, ... } ) */
static int
nhl_menu(lua_State *L)
{
static const char *const pickX[] = {"none", "one", "any"}; /* PICK_x */
int argc = lua_gettop(L);
const char *prompt;
const char *defval = "";
int pick = PICK_ONE, pick_cnt;
winid tmpwin;
anything any;
menu_item *picks = (menu_item *) 0;
if (argc < 2 || argc > 4) {
nhl_error(L, "Wrong args");
return 0;
}
prompt = luaL_checkstring(L, 1);
if (lua_isstring(L, 2))
defval = luaL_checkstring(L, 2);
if (lua_isstring(L, 3))
pick = luaL_checkoption(L, 3, "one", pickX);
luaL_checktype(L, argc, LUA_TTABLE);
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
lua_pushnil(L); /* first key */
while (lua_next(L, argc) != 0) {
const char *str = "";
const char *key = "";
/* key @ index -2, value @ index -1 */
if (lua_istable(L, -1)) {
lua_pushliteral(L, "key");
lua_gettable(L, -2);
key = lua_tostring(L, -1);
lua_pop(L, 1);
lua_pushliteral(L, "text");
lua_gettable(L, -2);
str = lua_tostring(L, -1);
lua_pop(L, 1);
/* TODO: glyph, attr, accel, group accel (all optional) */
} else if (lua_isstring(L, -1)) {
str = luaL_checkstring(L, -1);
key = luaL_checkstring(L, -2);
}
any = cg.zeroany;
if (*key)
any.a_char = key[0];
add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, str,
(*defval && *key && defval[0] == key[0])
? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
lua_pop(L, 1); /* removes 'value'; keeps 'key' for next iteration */
}
end_menu(tmpwin, prompt);
pick_cnt = select_menu(tmpwin, pick, &picks);
destroy_nhwindow(tmpwin);
if (pick_cnt > 0) {
char buf[2];
buf[0] = picks[0].item.a_char;
if (pick == PICK_ONE && pick_cnt > 1
&& *defval && defval[0] == picks[0].item.a_char)
buf[0] = picks[1].item.a_char;
buf[1] = '\0';
lua_pushstring(L, buf);
/* TODO: pick any */
} else {
char buf[2];
buf[0] = defval[0];
buf[1] = '\0';
lua_pushstring(L, buf);
}
return 1;
}
/* makeplural("zorkmid") */
static int
nhl_makeplural(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, makeplural(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* makesingular("zorkmids") */
static int
nhl_makesingular(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, makesingular(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* s_suffix("foo") */
static int
nhl_s_suffix(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, s_suffix(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* ing_suffix("foo") */
static int
nhl_ing_suffix(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, ing_suffix(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* an("foo") */
static int
nhl_an(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, an(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* rn2(10) */
static int
nhl_rn2(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushinteger(L, rn2((int) luaL_checkinteger(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* random(10); -- is the same as rn2(10); */
/* random(5,8); -- same as 5 + rn2(8); */
static int
nhl_random(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushinteger(L, rn2((int) luaL_checkinteger(L, 1)));
else if (argc == 2)
lua_pushinteger(L, luaL_checkinteger(L, 1) + rn2((int) luaL_checkinteger(L, 2)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* level_difficulty() */
static int
nhl_level_difficulty(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 0) {
lua_pushinteger(L, level_difficulty());
}
else {
nhl_error(L, "level_difficulty should not have any args");
}
return 1;
}
/* get mandatory integer value from table */
int
get_table_int(lua_State *L, const char *name)
{
int ret;
lua_getfield(L, -1, name);
ret = (int) luaL_checkinteger(L, -1);
lua_pop(L, 1);
return ret;
}
/* get optional integer value from table */
int
get_table_int_opt(lua_State *L, const char *name, int defval)
{
int ret = defval;
lua_getfield(L, -1, name);
if (!lua_isnil(L, -1)) {
ret = (int) luaL_checkinteger(L, -1);
}
lua_pop(L, 1);
return ret;
}
char *
get_table_str(lua_State *L, const char *name)
{
char *ret;
lua_getfield(L, -1, name);
ret = dupstr(luaL_checkstring(L, -1));
lua_pop(L, 1);
return ret;
}
/* get optional string value from table.
return value must be freed by caller. */
char *
get_table_str_opt(lua_State *L, const char *name, char *defval)
{
const char *ret;
lua_getfield(L, -1, name);
ret = luaL_optstring(L, -1, defval);
if (ret) {
lua_pop(L, 1);
return dupstr(ret);
}
lua_pop(L, 1);
return NULL;
}
int
get_table_boolean(lua_State *L, const char *name)
{
static const char *const boolstr[] = {
"true", "false", "yes", "no", NULL
};
/* static const int boolstr2i[] = { TRUE, FALSE, TRUE, FALSE, -1 }; */
int ltyp;
int ret = -1;
lua_getfield(L, -1, name);
ltyp = lua_type(L, -1);
if (ltyp == LUA_TSTRING) {
ret = luaL_checkoption(L, -1, NULL, boolstr);
/* nhUse(boolstr2i[0]); */
} else if (ltyp == LUA_TBOOLEAN) {
ret = lua_toboolean(L, -1);
} else if (ltyp == LUA_TNUMBER) {
ret = (int) luaL_checkinteger(L, -1);
if ( ret < 0 || ret > 1)
ret = -1;
}
lua_pop(L, 1);
if (ret == -1)
nhl_error(L, "Expected a boolean");
return ret;
}
int
get_table_boolean_opt(lua_State *L, const char *name, int defval)
{
int ret = defval;
lua_getfield(L, -1, name);
if (lua_type(L, -1) != LUA_TNIL) {
lua_pop(L, 1);
return get_table_boolean(L, name);
}
lua_pop(L, 1);
return ret;
}
/* opts[] is a null-terminated list */
int
get_table_option(lua_State *L,
const char *name,
const char *defval,
const char *const opts[])
{
int ret;
lua_getfield(L, -1, name);
ret = luaL_checkoption(L, -1, defval, opts);
lua_pop(L, 1);
return ret;
}
#ifdef DUMPLOG
/* local fname = dump_fmtstr("/tmp/nethack.%n.%d.log"); */
static int
nhl_dump_fmtstr(lua_State *L)
{
int argc = lua_gettop(L);
char buf[512];
if (argc == 1)
lua_pushstring(L, dump_fmtstr(luaL_checkstring(L, 1), buf, TRUE));
else
nhl_error(L, "Expected a string parameter");
return 1;
}
#endif /* DUMPLOG */
/* local dungeon_name = dnum_name(u.dnum); */
static int
nhl_dnum_name(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
int dnum = luaL_checkinteger(L, 1);
if (dnum >= 0 && dnum < g.n_dgns)
lua_pushstring(L, g.dungeons[dnum].dname);
else
lua_pushstring(L, "");
} else
nhl_error(L, "Expected an integer parameter");
return 1;
}
/* local stairs = stairways(); */
static int
nhl_stairways(lua_State *L)
{
stairway *tmp = g.stairs;
int i = 1; /* lua arrays should start at 1 */
lua_newtable(L);
while (tmp) {
lua_pushinteger(L, i);
lua_newtable(L);
nhl_add_table_entry_bool(L, "up", tmp->up);
nhl_add_table_entry_bool(L, "ladder", tmp->isladder);
nhl_add_table_entry_int(L, "x", tmp->sx);
nhl_add_table_entry_int(L, "y", tmp->sy);
nhl_add_table_entry_int(L, "dnum", tmp->tolev.dnum);
nhl_add_table_entry_int(L, "dlevel", tmp->tolev.dlevel);
lua_settable(L, -3);
tmp = tmp->next;
i++;
}
return 1;
}
/*
test( { x = 123, y = 456 } );
*/
static int
nhl_test(lua_State *L)
{
int x, y;
char *name, Player[] = "Player";
/* discard any extra arguments passed in */
lua_settop(L, 1);
luaL_checktype(L, 1, LUA_TTABLE);
x = get_table_int(L, "x");
y = get_table_int(L, "y");
name = get_table_str_opt(L, "name", Player);
pline("TEST:{ x=%i, y=%i, name=\"%s\" }", x,y, name);
free(name);
return 1;
}
/* push a key into command queue */
/* nh.pushkey("i"); */
static int
nhl_pushkey(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
const char *key = luaL_checkstring(L, 1);
cmdq_add_key(key[0]);
}
return 0;
}
/* do a turn of moveloop, or until g.multi is done if param is true. */
/* nh.doturn(); nh.doturn(true); */
static int
nhl_doturn(lua_State *L)
{
int argc = lua_gettop(L);
boolean domulti = FALSE;
if (argc == 1)
domulti = lua_toboolean(L, 1);
do {
moveloop_core();
} while (domulti && g.multi);
return 0;
}
/* set debugging flags. debugging use only, of course. */
/* nh.debug_flags({ mongen = false,
hunger = false,
overwrite_stairs = true }); */
static int
nhl_debug_flags(lua_State *L)
{
int val;
lcheck_param_table(L);
/* disable monster generation */
val = get_table_boolean_opt(L, "mongen", -1);
if (val != -1) {
iflags.debug_mongen = !(boolean)val; /* value in lua is negated */
if (iflags.debug_mongen) {
register struct monst *mtmp, *mtmp2;
for (mtmp = fmon; mtmp; mtmp = mtmp2) {
mtmp2 = mtmp->nmon;
if (DEADMONSTER(mtmp))
continue;
mongone(mtmp);
}
}
}
/* prevent hunger */
val = get_table_boolean_opt(L, "hunger", -1);
if (val != -1) {
iflags.debug_hunger = !(boolean)val; /* value in lua is negated */
}
/* allow overwriting stairs */
val = get_table_boolean_opt(L, "overwrite_stairs", -1);
if (val != -1) {
iflags.debug_overwrite_stairs = (boolean)val;
}
return 0;
}
static const struct luaL_Reg nhl_functions[] = {
{"test", nhl_test},
{"getmap", nhl_getmap},
#if 0
{"setmap", nhl_setmap},
#endif
{"gettrap", nhl_gettrap},
{"deltrap", nhl_deltrap},
{"pline", nhl_pline},
{"verbalize", nhl_verbalize},
{"menu", nhl_menu},
{"getlin", nhl_getlin},
{"makeplural", nhl_makeplural},
{"makesingular", nhl_makesingular},
{"s_suffix", nhl_s_suffix},
{"ing_suffix", nhl_ing_suffix},
{"an", nhl_an},
{"rn2", nhl_rn2},
{"random", nhl_random},
{"level_difficulty", nhl_level_difficulty},
{"parse_config", nhl_parse_config},
{"get_config", nhl_get_config},
{"get_config_errors", l_get_config_errors},
#ifdef DUMPLOG
{"dump_fmtstr", nhl_dump_fmtstr},
#endif /* DUMPLOG */
{"dnum_name", nhl_dnum_name},
{"stairways", nhl_stairways},
{"pushkey", nhl_pushkey},
{"doturn", nhl_doturn},
{"debug_flags", nhl_debug_flags},
{NULL, NULL}
};
static const struct {
const char *name;
long value;
} nhl_consts[] = {
{ "COLNO", COLNO },
{ "ROWNO", ROWNO },
#ifdef DLB
{ "DLB", 1},
#else
{ "DLB", 0},
#endif /* DLB */
{ NULL, 0 },
};
/* register and init the constants table */
static void
init_nhc_data(lua_State *L)
{
int i;
lua_newtable(L);
for (i = 0; nhl_consts[i].name; i++) {
lua_pushstring(L, nhl_consts[i].name);
lua_pushinteger(L, nhl_consts[i].value);
lua_rawset(L, -3);
}
lua_setglobal(L, "nhc");
}
static int
nhl_push_anything(lua_State *L, int anytype, void *src)
{
anything any = cg.zeroany;
switch (anytype) {
case ANY_INT: any.a_int = *(int *) src;
lua_pushinteger(L, any.a_int);
break;
case ANY_UCHAR: any.a_uchar = *(uchar *) src;
lua_pushinteger(L, any.a_uchar);
break;
case ANY_SCHAR: any.a_schar = *(schar *) src;
lua_pushinteger(L, any.a_schar);
break;
}
return 1;
}
static int
nhl_meta_u_index(lua_State *L)
{
static const struct {
const char *name;
void *ptr;
int type;
} ustruct[] = {
{ "ux", &(u.ux), ANY_UCHAR },
{ "uy", &(u.uy), ANY_UCHAR },
{ "dx", &(u.dx), ANY_SCHAR },
{ "dy", &(u.dy), ANY_SCHAR },
{ "dz", &(u.dz), ANY_SCHAR },
{ "tx", &(u.tx), ANY_UCHAR },
{ "ty", &(u.ty), ANY_UCHAR },
{ "ulevel", &(u.ulevel), ANY_INT },
{ "ulevelmax", &(u.ulevelmax), ANY_INT },
{ "uhunger", &(u.uhunger), ANY_INT },
{ "nv_range", &(u.nv_range), ANY_INT },
{ "xray_range", &(u.xray_range), ANY_INT },
{ "umonster", &(u.umonster), ANY_INT },
{ "umonnum", &(u.umonnum), ANY_INT },
{ "mh", &(u.mh), ANY_INT },
{ "mhmax", &(u.mhmax), ANY_INT },
{ "mtimedone", &(u.mtimedone), ANY_INT },
{ "dlevel", &(u.uz.dlevel), ANY_SCHAR }, /* actually xchar */
{ "dnum", &(u.uz.dnum), ANY_SCHAR }, /* actually xchar */
{ "uluck", &(u.uluck), ANY_SCHAR },
{ "uhp", &(u.uhp), ANY_INT },
{ "uhpmax", &(u.uhpmax), ANY_INT },
{ "uen", &(u.uen), ANY_INT },
{ "uenmax", &(u.uenmax), ANY_INT },
};
const char *tkey = luaL_checkstring(L, 2);
int i;
/* FIXME: doesn't really work, eg. negative values for u.dx */
for (i = 0; i < SIZE(ustruct); i++)
if (!strcmp(tkey, ustruct[i].name)) {
return nhl_push_anything(L, ustruct[i].type, ustruct[i].ptr);
}
if (!strcmp(tkey, "inventory")) {
nhl_push_obj(L, g.invent);
return 1;
} else if (!strcmp(tkey, "role")) {
lua_pushstring(L, g.urole.name.m);
return 1;
} else if (!strcmp(tkey, "moves")) {
lua_pushinteger(L, g.moves);
return 1;
} else if (!strcmp(tkey, "uhave_amulet")) {
lua_pushinteger(L, u.uhave.amulet);
return 1;
} else if (!strcmp(tkey, "depth")) {
lua_pushinteger(L, depth(&u.uz));
return 1;
}
nhl_error(L, "Unknown u table index");
return 0;
}
static int
nhl_meta_u_newindex(lua_State *L)
{
nhl_error(L, "Cannot set u table values");
return 0;
}
static int
nhl_u_clear_inventory(lua_State *L UNUSED)
{
while (g.invent)
useupall(g.invent);
return 0;
}
/* Put object into player's inventory */
/* u.giveobj(obj.new("rock")); */
static int
nhl_u_giveobj(lua_State *L)
{
return nhl_obj_u_giveobj(L);
}
static const struct luaL_Reg nhl_u_functions[] = {
{ "clear_inventory", nhl_u_clear_inventory },
{ "giveobj", nhl_u_giveobj },
{ NULL, NULL }
};
static void
init_u_data(lua_State *L)
{
lua_newtable(L);
luaL_setfuncs(L, nhl_u_functions, 0);
lua_newtable(L);
lua_pushcfunction(L, nhl_meta_u_index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, nhl_meta_u_newindex);
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
lua_setglobal(L, "u");
}
static int
nhl_set_package_path(lua_State *L, const char *path)
{
lua_getglobal(L, "package");
lua_pushstring(L, path);
lua_setfield(L, -2, "path");
lua_pop(L, 1);
return 0;
}
static int
traceback_handler(lua_State *L)
{
luaL_traceback(L, L, lua_tostring(L, 1), 0);
/* TODO: call impossible() if fuzzing? */
return 1;
}
/* read lua code/data from a dlb module or an external file
into a string buffer and feed that to lua */
boolean
nhl_loadlua(lua_State *L, const char *fname)
{
#define LOADCHUNKSIZE (1L << 13) /* 8K */
boolean ret = TRUE;
dlb *fh;
char *buf = (char *) 0, *bufin, *bufout, *p, *nl, *altfname;
long buflen, ct, cnt;
int llret;
altfname = (char *) alloc(strlen(fname) + 3); /* 3: '('...')\0' */
/* don't know whether 'fname' is inside a dlb container;
if we did, we could choose between "nhdat(<fname>)" and "<fname>"
but since we don't, compromise */
Sprintf(altfname, "(%s)", fname);
fh = dlb_fopen(fname, RDBMODE);
if (!fh) {
impossible("nhl_loadlua: Error loading %s", altfname);
ret = FALSE;
goto give_up;
}
dlb_fseek(fh, 0L, SEEK_END);
buflen = dlb_ftell(fh);
dlb_fseek(fh, 0L, SEEK_SET);
/* extra +1: room to add final '\n' if missing */
buf = bufout = (char *) alloc(buflen + 1 + 1);
buf[0] = '\0';
bufin = bufout = buf;
ct = 0L;
while (buflen > 0 || ct) {
/*
* Semi-arbitrarily limit reads to 8K at a time. That's big
* enough to cover the majority of our Lua files in one bite
* but small enough to fully exercise the partial record
* handling (when processing the castle's level description).
*
* [For an external file (non-DLB), VMS may only be able to
* read at most 32K-1 at a time depending on the file format
* in use, and fseek(SEEK_END) only yields an upper bound on
* the actual amount of data in that situation.]
*/
if ((cnt = dlb_fread(bufin, 1, min(buflen, LOADCHUNKSIZE), fh)) < 0L)
break;
buflen -= cnt; /* set up for next iteration, if any */
if (cnt == 0L) {
*bufin = '\n'; /* very last line is unterminated? */
cnt = 1;
}
bufin[cnt] = '\0'; /* fread() doesn't do this */
/* in case partial line was leftover from previous fread */
bufin -= ct, cnt += ct, ct = 0;
while (cnt > 0) {
if ((nl = index(bufin, '\n')) != 0) {
/* normal case, newline is present */
ct = (long) (nl - bufin + 1L); /* +1: keep the newline */
for (p = bufin; p <= nl; ++p)
*bufout++ = *bufin++;
if (*bufin == '\r')
++bufin, ++ct;
/* update for next loop iteration */
cnt -= ct;
ct = 0;
} else if (strlen(bufin) < LOADCHUNKSIZE) {
/* no newline => partial record; move unprocessed chars
to front of input buffer (bufin portion of buf[]) */
ct = cnt = (long) (eos(bufin) - bufin);
for (p = bufout; cnt > 0; --cnt)
*p++ = *bufin++;
*p = '\0';
bufin = p; /* next fread() populates buf[] starting here */
/* cnt==0 so inner loop will terminate */
} else {
/* LOADCHUNKSIZE portion of buffer already completely full */
impossible("(%s) line too long", altfname);
goto give_up;
}
}
}
*bufout = '\0';
(void) dlb_fclose(fh);
llret = luaL_loadbuffer(L, buf, strlen(buf), altfname);
if (llret != LUA_OK) {
impossible("luaL_loadbuffer: Error loading %s: %s",
altfname, lua_tostring(L, -1));
ret = FALSE;
goto give_up;
} else {
lua_pushcfunction(L, traceback_handler);
lua_insert(L, 1);
if (lua_pcall(L, 0, LUA_MULTRET, -2)) {
impossible("Lua error: %s", lua_tostring(L, -1));
ret = FALSE;
goto give_up;
}
}
give_up:
if (altfname)
free((genericptr_t) altfname);
if (buf)
free((genericptr_t) buf);
return ret;
}
lua_State *
nhl_init(void)
{
lua_State *L = luaL_newstate();
iflags.in_lua = TRUE;
luaL_openlibs(L);
nhl_set_package_path(L, "./?.lua");
/* register nh -table, and functions for it */
lua_newtable(L);
luaL_setfuncs(L, nhl_functions, 0);
lua_setglobal(L, "nh");
/* init nhc -table */
init_nhc_data(L);
/* init u -table */
init_u_data(L);
l_selection_register(L);
l_register_des(L);
l_obj_register(L);
if (!nhl_loadlua(L, "nhlib.lua")) {
nhl_done(L);
return (lua_State *) 0;
}
return L;
}
void
nhl_done(lua_State *L)
{
if (L)
lua_close(L);
iflags.in_lua = FALSE;
}
boolean
load_lua(const char *name)
{
boolean ret = TRUE;
lua_State *L = nhl_init();
if (!L) {
ret = FALSE;
goto give_up;
}
if (!nhl_loadlua(L, name)) {
ret = FALSE;
goto give_up;
}
give_up:
nhl_done(L);
return ret;
}
DISABLE_WARNING_CONDEXPR_IS_CONSTANT
const char *
get_lua_version(void)
{
if (g.lua_ver[0] == 0) {
lua_State *L = nhl_init();
if (L) {
size_t len = 0;
const char *vs = (const char *) 0;
/* LUA_VERSION yields "<major>.<minor>" although we check to see
whether it is "Lua-<major>.<minor>" and strip prefix if so;
LUA_RELEASE is <LUA_VERSION>.<LUA_VERSION_RELEASE> but doesn't
get set up as a lua global */
lua_getglobal(L, "_RELEASE");
if (lua_isstring(L, -1))
vs = lua_tolstring (L, -1, &len);
#ifdef LUA_RELEASE
else
vs = LUA_RELEASE, len = strlen(vs);
#endif
if (!vs) {
lua_getglobal(L, "_VERSION");
if (lua_isstring(L, -1))
vs = lua_tolstring (L, -1, &len);
#ifdef LUA_VERSION
else
vs = LUA_VERSION, len = strlen(vs);
#endif
}
if (vs && len < sizeof g.lua_ver) {
if (!strncmpi(vs, "Lua", 3)) {
vs += 3;
if (*vs == '-' || *vs == ' ')
vs += 1;
}
Strcpy(g.lua_ver, vs);
}
}
nhl_done(L);
#ifdef LUA_COPYRIGHT
if (sizeof LUA_COPYRIGHT <= sizeof g.lua_copyright)
Strcpy(g.lua_copyright, LUA_COPYRIGHT);
#endif
}
return (const char *) g.lua_ver;
}
RESTORE_WARNINGS