From: Pasi Kallinen Date: Fri, 21 May 2021 14:54:53 +0000 (+0300) Subject: Lua: nhcore script with function callbacks X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=29868036f11ac0929d9eb3336208f40f2f26dbb3;p=nethack Lua: nhcore script with function callbacks Adds possible callbacks for "start_new_game", "restore_old_game", "moveloop_turn", and "game_exit" which when defined, will be called from core code at the appropriate time. Adds lua hooks for dump_fmtstr (only if DUMPLOG), dnum_name, u.moves, u.uhave_amulet, and u.depth. --- diff --git a/dat/nhcore.lua b/dat/nhcore.lua new file mode 100644 index 000000000..3d82b382b --- /dev/null +++ b/dat/nhcore.lua @@ -0,0 +1,74 @@ + +-- This file contains lua code used by NetHack core. +-- Is it loaded once, at game start, and kept in memory until game exit. + + +-- This is an example of generating an external file during gameplay, +-- which is updated periodically. +-- Intended for public servers using dgamelaunch as their login manager. +local prev_dgl_extrainfo = 0; +function mk_dgl_extrainfo() + if ((prev_dgl_extrainfo == 0) or (prev_dgl_extrainfo + 50 < u.moves)) then + local filename = nh.dump_fmtstr("/tmp/nethack.%n.%d.log"); + local extrai, err = io.open(filename, "w"); + if extrai then + local sortval = 0; + local dname = nh.dnum_name(u.dnum); + local dstr = ""; + local astr = " "; + if u.uhave_amulet == 1 then + sortval = sortval + 1024; + astr = "A"; + end + if dname == "Fort Ludios" then + dstr = "Knx"; + sortval = sortval + 245; + elseif dname == "The Quest" then + dstr = "Q" .. u.dlevel; + sortval = sortval + 250 + u.dlevel; + elseif dname == "The Elemental Planes" then + dstr = "End"; + sortval = sortval + 256; + elseif dname == "Vlad's Tower" then + dstr = "T" .. u.dlevel; + sortval = sortval + 235 + u.depth; + elseif dname == "Sokoban" then + dstr = "S" .. u.dlevel; + sortval = sortval + 225 + u.depth; + elseif dname == "The Gnomish Mines" then + dstr = "M" .. u.dlevel; + sortval = sortval + 215 + u.dlevel; + else + dstr = "D" .. u.depth; + sortval = sortval + u.depth; + end + local str = sortval .. "|" .. astr .. " " .. dstr; + + extrai:write(str); + extrai:close(); + else + -- failed to open the file. + nh.pline("Failed to open dgl extrainfo file: " .. err); + end + prev_dgl_extrainfo = u.moves; + end +end + + +-- Callback functions +nhcore = { + -- start_new_game called once, when starting a new game + -- after "Welcome to NetHack" message has been given. + -- start_new_game = function() nh.pline("NEW GAME!"); end, + + -- restore_old_game called once, when restoring a saved game + -- after "Welcome back to NetHack" message has been given. + -- restore_old_game = function() nh.pline("RESTORED OLD GAME!"); end, + + -- moveloop_turn is called once per turn. + -- moveloop_turn = mk_dgl_extrainfo, + + -- game_exit is called when the game exits (quit, saved, ...) + -- game_exit = function() end, +}; + diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 01531c166..33fe34b78 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -890,6 +890,7 @@ split off some of the functionality that was in makedefs (compiled-in options and accessed on the target platform replace quest.txt and associated conversion to quest.dat via makedefs with Lua quest texts loaded at runtime +callback lua functions from core at certain game actions some altars are displayed in different colors (for tty and curses at least) add 'quick_farsight' option to provide some control over random clairvoyance where pausing to be able to browse temporarily visible aspects of the diff --git a/doc/lua.adoc b/doc/lua.adoc index 564205d02..1acfd2a9a 100644 --- a/doc/lua.adoc +++ b/doc/lua.adoc @@ -14,6 +14,35 @@ Example: local str = nh.an("unicorn"); +=== dnum_name + +Returns the full dungeon name (as defined in dungeon.lua) for the dungeon +number given as parameter. + +Example: + + local dungeon_name = nh.dnum_name(u.dnum); + +=== dump_fmtstr + +Returns a string replacing special format chars with game data. +Only available if NetHack was compiled with DUMPLOG. + +|=== +| %% | literal '%' +| %t | game start, timestamp +| %T | current time, timestamp +| %d | game start, YYYYMMDDhhmmss +| %D | current time, YYYYMMDDhhmmss +| %v | game version, eg. '3.7.0-0' +| %u | UID +| %n | player name +| %N | first character of player name +|=== + +Example: + + local filename = nh.dump_fmtstr("/tmp/nethack.%n.%d.log"); === getlin diff --git a/include/decl.h b/include/decl.h index 4ef27b6b5..8614bdf2d 100644 --- a/include/decl.h +++ b/include/decl.h @@ -995,6 +995,9 @@ struct instance_globals { int lusername_size; #endif + /* nhlua.c */ + genericptr_t luacore; /* lua_State * */ + /* o_init.c */ short disco[NUM_OBJECTS]; diff --git a/include/extern.h b/include/extern.h index de1b834f7..cec6fdbf8 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1657,6 +1657,9 @@ extern int l_obj_register(lua_State *); /* ### nhlua.c ### */ #if !defined(CROSSCOMPILE) || defined(CROSSCOMPILE_TARGET) +extern void l_nhcore_init(void); +extern void l_nhcore_done(void); +extern void l_nhcore_call(int); extern lua_State * nhl_init(void); extern void nhl_done(lua_State *); extern boolean nhl_loadlua(lua_State *, const char *); diff --git a/include/hack.h b/include/hack.h index cb311d646..e4cf6b77d 100644 --- a/include/hack.h +++ b/include/hack.h @@ -421,6 +421,16 @@ typedef struct sortloot_item Loot; #define SUPPRESS_HISTORY 4 #define URGENT_MESSAGE 8 +/* Lua callback functions */ +enum nhcore_calls { + NHCORE_START_NEW_GAME = 0, + NHCORE_RESTORE_OLD_GAME, + NHCORE_MOVELOOP_TURN, + NHCORE_GAME_EXIT, + + NUM_NHCORE_CALLS +}; + /* Macros for messages referring to hands, eyes, feet, etc... */ enum bodypart_types { ARM = 0, diff --git a/src/allmain.c b/src/allmain.c index 43f436faf..a3af3976a 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -201,6 +201,8 @@ moveloop(boolean resuming) /* once-per-turn things go here */ /********************************/ + l_nhcore_call(NHCORE_MOVELOOP_TURN); + if (Glib) glibr(); nh_timeout(); @@ -633,6 +635,8 @@ newgame(void) * any artifacts */ u_init(); + l_nhcore_init(); + #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done1); #endif @@ -709,6 +713,8 @@ welcome(boolean new_game) /* false => restoring an old game */ : "%s %s, the%s %s %s, welcome back to NetHack!", Hello((struct monst *) 0), g.plname, buf, g.urace.adj, (currentgend && g.urole.name.f) ? g.urole.name.f : g.urole.name.m); + + l_nhcore_call(new_game ? NHCORE_START_NEW_GAME : NHCORE_RESTORE_OLD_GAME); } #ifdef POSITIONBAR diff --git a/src/decl.c b/src/decl.c index 81d5e9e52..e8a8b818e 100644 --- a/src/decl.c +++ b/src/decl.c @@ -517,6 +517,9 @@ const struct instance_globals g_init = { MAX_LAN_USERNAME, /* lusername_size */ #endif /* MAX_LAN_USERNAME */ + /* nhlua.c */ + UNDEFINED_VALUE, /* luacore */ + /* o_init.c */ DUMMY, /* disco */ diff --git a/src/end.c b/src/end.c index 020ad15a2..ce17c9036 100644 --- a/src/end.c +++ b/src/end.c @@ -1753,6 +1753,8 @@ void nh_terminate(int status) { g.program_state.in_moveloop = 0; /* won't be returning to normal play */ + + l_nhcore_call(NHCORE_GAME_EXIT); #ifdef MAC getreturn("to exit"); #endif @@ -1761,6 +1763,7 @@ nh_terminate(int status) if (!g.program_state.panicking) { freedynamicdata(); dlb_cleanup(); + l_nhcore_done(); } #ifdef VMS diff --git a/src/nhlua.c b/src/nhlua.c index a762bf8cd..2b1e0044d 100644 --- a/src/nhlua.c +++ b/src/nhlua.c @@ -14,6 +14,10 @@ /* */ /* lua_CFunction prototypes */ +#ifdef DUMPLOG +static int nhl_dump_fmtstr(lua_State *); +#endif /* DUMPLOG */ +static int nhl_dnum_name(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); @@ -46,6 +50,68 @@ 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) { @@ -780,6 +846,40 @@ get_table_option(lua_State *L, 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; +} + /* test( { x = 123, y = 456 } ); */ @@ -831,6 +931,10 @@ static const struct luaL_Reg nhl_functions[] = { {"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}, {NULL, NULL} }; @@ -927,6 +1031,15 @@ nhl_meta_u_index(lua_State *L) } 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"); diff --git a/sys/unix/Makefile.top b/sys/unix/Makefile.top index 2fb444645..0dda82395 100644 --- a/sys/unix/Makefile.top +++ b/sys/unix/Makefile.top @@ -86,7 +86,7 @@ DATHELP = help hh cmdhelp keyhelp history opthelp wizhelp SPEC_LEVS = asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua \ juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua \ minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua \ - tower?.lua valley.lua wizard?.lua nhlib.lua themerms.lua \ + tower?.lua valley.lua wizard?.lua nhcore.lua nhlib.lua themerms.lua \ astral.lua air.lua earth.lua fire.lua water.lua QUEST_LEVS = ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua diff --git a/sys/unix/NetHack.xcodeproj/project.pbxproj b/sys/unix/NetHack.xcodeproj/project.pbxproj index 06b864cb5..dfe8a2167 100644 --- a/sys/unix/NetHack.xcodeproj/project.pbxproj +++ b/sys/unix/NetHack.xcodeproj/project.pbxproj @@ -1140,6 +1140,7 @@ "$(NH_DAT_DIR)/Mon-goal.lua", "$(NH_DAT_DIR)/Mon-loca.lua", "$(NH_DAT_DIR)/Mon-strt.lua", + "$(NH_DAT_DIR)/nhcore.lua", "$(NH_DAT_DIR)/nhlib.lua", "$(NH_DAT_DIR)/oracle.lua", "$(NH_DAT_DIR)/orcus.lua", @@ -1206,7 +1207,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"${NH_DAT_DIR}\"\n\"${NH_UTIL_DIR}\"/dlb cf nhdat help hh cmdhelp keyhelp history opthelp wizhelp dungeon.lua tribute asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua tower?.lua valley.lua wizard?.lua nhlib.lua themerms.lua astral.lua air.lua earth.lua fire.lua water.lua ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua bogusmon data engrave epitaph oracles options quest.lua rumors\n"; + shellScript = "cd \"${NH_DAT_DIR}\"\n\"${NH_UTIL_DIR}\"/dlb cf nhdat help hh cmdhelp keyhelp history opthelp wizhelp dungeon.lua tribute asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua tower?.lua valley.lua wizard?.lua nhcore.lua nhlib.lua themerms.lua astral.lua air.lua earth.lua fire.lua water.lua ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua bogusmon data engrave epitaph oracles options quest.lua rumors\n"; }; 3192867021A39F6A00325BEB /* Install */ = { isa = PBXShellScriptBuildPhase;