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.
This commit is contained in:
Pasi Kallinen
2021-05-21 17:54:53 +03:00
parent 0e9bf2e03c
commit 29868036f1
12 changed files with 248 additions and 2 deletions

74
dat/nhcore.lua Normal file
View File

@@ -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,
};

View 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

View 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

View 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];

View 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 *);

View 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,

View 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

View 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 */

View File

@@ -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

View File

@@ -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");

View 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

View File

@@ -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;