Files
nethack/src/nhlua.c
copperwater c5b5a869d7 Enable themed rooms to be constrained by level difficulty
The system of themed rooms currently makes it so that any themed room
can potentially generate anywhere a themed room can be placed. This is
problematic in the long run, since it makes it difficult to design new
rooms that are an appropriate amount of challenge at all levels of the
dungeon. (A few themed rooms already have this problem: a hero starting
out on level 1 probably won't live very long when the neighboring room
is full of giant spiders, or an arch-lich has generated in a mausoleum
nearby).

This commit adds optional "mindiff" and "maxdiff" properties for
themerooms defined as tables and exposes level_difficulty() to Lua. A
themeroom whose mindiff exceeds the current level difficulty, or whose
maxdiff is lower than the current level difficulty, is prevented from
being selected.

Because the set of rooms eligible to generate on a given level is no
longer fixed, the total frequency of all the rooms can't be computed
once per game when the file is first parsed, as it was before. In place
of this, the themerooms_generate() function now uses a reservoir
sampling algorithm to choose a room from among the eligible rooms,
weighted by frequency.
2020-09-29 17:30:11 +03:00

1226 lines
30 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 */
static int FDECL(nhl_test, (lua_State *));
static int FDECL(nhl_getmap, (lua_State *));
static void FDECL(nhl_add_table_entry_bool, (lua_State *, const char *, BOOLEAN_P));
static char FDECL(splev_typ2chr, (SCHAR_P));
static int FDECL(nhl_gettrap, (lua_State *));
static int FDECL(nhl_deltrap, (lua_State *));
#if 0
static int FDECL(nhl_setmap, (lua_State *));
#endif
static int FDECL(nhl_pline, (lua_State *));
static int FDECL(nhl_verbalize, (lua_State *));
static int FDECL(nhl_menu, (lua_State *));
static int FDECL(nhl_getlin, (lua_State *));
static int FDECL(nhl_makeplural, (lua_State *));
static int FDECL(nhl_makesingular, (lua_State *));
static int FDECL(nhl_s_suffix, (lua_State *));
static int FDECL(nhl_ing_suffix, (lua_State *));
static int FDECL(nhl_an, (lua_State *));
static int FDECL(nhl_rn2, (lua_State *));
static int FDECL(nhl_random, (lua_State *));
static int FDECL(nhl_level_difficulty, (lua_State *));
static void FDECL(init_nhc_data, (lua_State *));
static int FDECL(nhl_push_anything, (lua_State *, int, void *));
static int FDECL(nhl_meta_u_index, (lua_State *));
static int FDECL(nhl_meta_u_newindex, (lua_State *));
static int FDECL(nhl_u_clear_inventory, (lua_State *));
static int FDECL(nhl_u_giveobj, (lua_State *));
static void FDECL(init_u_data, (lua_State *));
static int FDECL(nhl_set_package_path, (lua_State *, const char *));
static int FDECL(traceback_handler, (lua_State *));
void
nhl_error(L, msg)
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*/
}
/* Check that parameters are nothing but single table,
or if no parameters given, put empty table there */
void
lcheck_param_table(L)
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(L, name)
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(L, name, defval)
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(L, name, value)
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(L, name, value)
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(L, name, value)
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(L, name, value)
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(c)
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(s)
const char *s;
{
if (s && strlen(s) == 1)
return splev_chr2typ(s[0]);
return INVALID_TYPE;
}
static char
splev_typ2chr(typ)
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(L)
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(L)
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;
}
/* local loc = getmap(x,y) */
static int
nhl_getmap(L)
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;
}
/* pline("It hits!") */
static int
nhl_pline(L)
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(L)
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;
}
/*
str = getlin("What do you want to call this dungeon level?");
*/
static int
nhl_getlin(L)
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(L)
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, NO_GLYPH, &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(L)
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(L)
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(L)
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(L)
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(L)
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(L)
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(L)
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(L)
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(L, name)
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(L, name, defval)
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(L, name)
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(L, name, defval)
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(L, name)
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(L, name, defval)
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;
}
int
get_table_option(L, name, defval, opts)
lua_State *L;
const char *name;
const char *defval;
const char *const opts[]; /* NULL-terminated list */
{
int ret;
lua_getfield(L, -1, name);
ret = luaL_checkoption(L, -1, defval, opts);
lua_pop(L, 1);
return ret;
}
/*
test( { x = 123, y = 456 } );
*/
static int
nhl_test(L)
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;
}
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},
{NULL, NULL}
};
static const struct {
const char *name;
long value;
} nhl_consts[] = {
{ "COLNO", COLNO },
{ "ROWNO", ROWNO },
{ NULL, 0 },
};
/* register and init the constants table */
static void
init_nhc_data(L)
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(L, anytype, src)
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(L)
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;
}
nhl_error(L, "Unknown u table index");
return 0;
}
static int
nhl_meta_u_newindex(L)
lua_State *L;
{
nhl_error(L, "Cannot set u table values");
return 0;
}
static int
nhl_u_clear_inventory(L)
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(L)
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(L)
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(L, 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(L)
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(L, fname)
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, "r");
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()
{
lua_State *L = luaL_newstate();
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")) {
lua_close(L);
return (lua_State *) 0;
}
return L;
}
boolean
load_lua(name)
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:
lua_close(L);
return ret;
}
const char *
get_lua_version()
{
size_t len = (size_t) 0;
const char *vs = (const char *) 0;
lua_State *L;
if (g.lua_ver[0] == 0) {
L = nhl_init();
if (L) {
lua_getglobal(L, "_VERSION");
if (lua_isstring(L, -1))
vs = lua_tolstring (L, -1, &len);
if (vs && len < sizeof g.lua_ver) {
if (!strncmpi(vs, "Lua", 3)) {
vs += 3;
if (*vs == '-' || *vs == ' ')
vs += 1;
}
Strcpy(g.lua_ver, vs);
}
}
lua_close(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;
}