]> granicus.if.org Git - nethack/commitdiff
Lua: nhcore script with function callbacks
authorPasi Kallinen <paxed@alt.org>
Fri, 21 May 2021 14:54:53 +0000 (17:54 +0300)
committerPasi Kallinen <paxed@alt.org>
Fri, 21 May 2021 18:24:59 +0000 (21:24 +0300)
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.

12 files changed:
dat/nhcore.lua [new file with mode: 0644]
doc/fixes37.0
doc/lua.adoc
include/decl.h
include/extern.h
include/hack.h
src/allmain.c
src/decl.c
src/end.c
src/nhlua.c
sys/unix/Makefile.top
sys/unix/NetHack.xcodeproj/project.pbxproj

diff --git a/dat/nhcore.lua b/dat/nhcore.lua
new file mode 100644 (file)
index 0000000..3d82b38
--- /dev/null
@@ -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,
+};
+
index 01531c166182f4a60a3a40922ec64580c093b33c..33fe34b78df18f7ba1a706c6c7255627c5cb752e 100644 (file)
@@ -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
index 564205d025b57108b7d3239ee3d32a5b93c4ae71..1acfd2a9a28e0819d6f3ff62de30ab08f43983cf 100644 (file)
@@ -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
 
index 4ef27b6b52eb4b53d21bc706254633bc1bd0fe79..8614bdf2d4ea25e5494ee2df118e74fd53069f29 100644 (file)
@@ -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];
 
index de1b834f7791c2939d90f34b994619c2037f4dee..cec6fdbf8137c6e6cc2939bf0209d6bb558902c6 100644 (file)
@@ -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 *);
index cb311d6467b498724b9126e4eba04dae28205570..e4cf6b77d38caf134074ac6faa65f42ca810c6e1 100644 (file)
@@ -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,
index 43f436fafbfef41fecd519af34fc66351e853fc4..a3af3976ab1bdae1acb4c0ba8d0a778d06d79171 100644 (file)
@@ -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
index 81d5e9e526bbc854a8ed62935270d78f222ed3d0..e8a8b818ece56409c0e8d4b206f43b4a24aec9c7 100644 (file)
@@ -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 */
 
index 020ad15a2612d7b0ec9fbb7f97b2a9e3aad9ff57..ce17c903640747d06c2854a1386d20edd2bc4fbe 100644 (file)
--- 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
index a762bf8cd7df568ac75115353a82d8af9ae03ea7..2b1e0044d2d70244abbe2d21d51f4474733c1573 100644 (file)
 /*  */
 
 /* 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");
index 2fb4446452f867524f65bb9c5436b0cf3142a32d..0dda82395bceb02e78e3cfe294940d4875f103b5 100644 (file)
@@ -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
 
index 06b864cb58c5329361d8cbad0e0a289d0afdee25..dfe8a2167749c163e937078b9bdc8d8b907051ba 100644 (file)
                                "$(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",
                        );
                        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;