/* 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 #- include #- include */ /* */ /* 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_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; } } 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*/ } /* 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; } 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}, {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(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()" and "" 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(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) { 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) { 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); } } 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