From 6abb12aee05b4bd10697cf226bd7f6cbdbad82d7 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 15 Jan 2023 10:32:31 +0200 Subject: [PATCH] Lua: Persistent variables Add a way for the lua scripts to set and retrieve variables that are persistent - saved and restored with the game. Invalidates saves. --- dat/nhcore.lua | 9 +++ dat/nhlib.lua | 32 +++++++++++ doc/lua.adoc | 13 +++++ include/extern.h | 3 + include/patchlevel.h | 2 +- src/nhlua.c | 134 +++++++++++++++++++++++++++++++++++++++++++ src/restore.c | 1 + src/save.c | 1 + 8 files changed, 194 insertions(+), 1 deletion(-) diff --git a/dat/nhcore.lua b/dat/nhcore.lua index 3899a21ef..5eb94b737 100644 --- a/dat/nhcore.lua +++ b/dat/nhcore.lua @@ -4,6 +4,15 @@ -- This file contains lua code used by NetHack core. -- Is it loaded once, at game start, and kept in memory until game exit. +-- Data in nh_lua_variables table can be set and queried with nh.variable() +-- This table is saved and restored along with the game. +nh_lua_variables = { +}; + +-- wrapper to simplify calling from nethack core +function get_variables_string() + return "nh_lua_variables=" .. table_stringify(nh_lua_variables) .. ";"; +end -- This is an example of generating an external file during gameplay, -- which is updated periodically. diff --git a/dat/nhlib.lua b/dat/nhlib.lua index 9cb3f0a2e..74f94cb4c 100644 --- a/dat/nhlib.lua +++ b/dat/nhlib.lua @@ -107,3 +107,35 @@ end function pline(fmt, ...) nh.pline(string.format(fmt, table.unpack({...}))); end + +-- wrapper to make calling from nethack core easier +function nh_set_variables_string(key, tbl) + return "nh_lua_variables[\"" .. key .. "\"]=" .. table_stringify(tbl) .. ";"; +end + +-- wrapper to make calling from nethack core easier +function nh_get_variables_string(tbl) + return "return " .. table_stringify(tbl) .. ";"; +end + +-- return the (simple) table tbl converted into a string +function table_stringify(tbl) + local str = ""; + for key, value in pairs(tbl) do + local typ = type(value); + if (typ == "table") then + str = str .. "[\"" .. key .. "\"]=" .. table_stringify(value); + elseif (typ == "string") then + str = str .. "[\"" .. key .. "\"]=[[" .. value .. "]]"; + elseif (typ == "boolean") then + str = str .. "[\"" .. key .. "\"]=" .. tostring(value); + elseif (typ == "number") then + str = str .. "[\"" .. key .. "\"]=" .. value; + elseif (typ == "nil") then + str = str .. "[\"" .. key .. "\"]=nil"; + end + str = str .. ","; + end + -- pline("table_stringify:(%s)", str); + return "{" .. str .. "}"; +end diff --git a/doc/lua.adoc b/doc/lua.adoc index 705991b8e..8e0c7aaf6 100644 --- a/doc/lua.adoc +++ b/doc/lua.adoc @@ -376,6 +376,19 @@ Example: nh.stop_timer_at({x=5,y=6}, "melt-ice"); +=== variable + +Set or get a global variable. These are persistent, saved and restored along with the game. +Supports only strings, booleans, numbers, or tables. + +Example: + + nh.variable("test", 10); + local ten = nh.variable("test"); + nh.variable("tbl", { a = 1, b = "foo" }); + local tbl = nh.variable("tbl"); + + === verbalize Show the text in the message area as if someone said it, obeying eg. hero's deafness. diff --git a/include/extern.h b/include/extern.h index cf6b51aee..bc3f0e97a 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1839,6 +1839,9 @@ extern void l_nhcore_call(int); extern lua_State * nhl_init(nhl_sandbox_info *); extern void nhl_done(lua_State *); extern boolean nhl_loadlua(lua_State *, const char *); +extern char *get_nh_lua_variables(void); +extern void save_luadata(NHFILE *); +extern void restore_luadata(NHFILE *); extern int nhl_pcall(lua_State *, int, int); extern boolean load_lua(const char *, nhl_sandbox_info *); ATTRNORETURN extern void nhl_error(lua_State *, const char *) NORETURN; diff --git a/include/patchlevel.h b/include/patchlevel.h index 2f6ab8463..181788e0c 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -17,7 +17,7 @@ * Incrementing EDITLEVEL can be used to force invalidation of old bones * and save files. */ -#define EDITLEVEL 68 +#define EDITLEVEL 69 /* * Development status possibilities. diff --git a/src/nhlua.c b/src/nhlua.c index 0069c0cf0..620159f9a 100644 --- a/src/nhlua.c +++ b/src/nhlua.c @@ -1030,6 +1030,139 @@ nhl_dnum_name(lua_State *L) return 1; } +/* set or get variables which are saved and restored along the game. + nh.variable("test", 10); + local ten = nh.variable("test"); */ +static int +nhl_variable(lua_State *L) +{ + int argc = lua_gettop(L); + int typ; + const char *key; + + if (!gl.luacore) { + nhl_error(L, "nh luacore not inited"); + return 0; + } + + lua_getglobal(gl.luacore, "nh_lua_variables"); + if (!lua_istable(gl.luacore, -1)) { + impossible("nh_lua_variables is not a lua table"); + return 0; + } + + if (argc == 1) { + key = luaL_checkstring(L, 1); + + lua_getfield(gl.luacore, -1, key); + typ = lua_type(gl.luacore, -1); + if (typ == LUA_TSTRING) + lua_pushstring(L, lua_tostring(gl.luacore, -1)); + else if (typ == LUA_TNIL) + lua_pushnil(L); + else if (typ == LUA_TBOOLEAN) + lua_pushboolean(L, lua_toboolean(gl.luacore, -1)); + else if (typ == LUA_TNUMBER) + lua_pushinteger(L, lua_tointeger(gl.luacore, -1)); + else if (typ == LUA_TTABLE) { + lua_getglobal(gl.luacore, "nh_get_variables_string"); + lua_pushvalue(gl.luacore, -2); + nhl_pcall(gl.luacore, 1, 1); + luaL_loadstring(L, lua_tostring(gl.luacore, -1)); + nhl_pcall(L, 0, 1); + } else + nhl_error(L, "Cannot get variable of that type"); + return 1; + } else if (argc == 2) { + /* set nh_lua_variables[key] = value; + nh.variable("key", value); */ + key = luaL_checkstring(L, 1); + //pline("SETVAR:%s", key); + typ = lua_type(L, -1); + + if (typ == LUA_TSTRING) { + lua_pushstring(gl.luacore, lua_tostring(L, -1)); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TNIL) { + lua_pushnil(gl.luacore); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TBOOLEAN) { + lua_pushboolean(gl.luacore, lua_toboolean(L, -1)); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TNUMBER) { + lua_pushinteger(gl.luacore, lua_tointeger(L, -1)); + lua_setfield(gl.luacore, -2, key); + } else if (typ == LUA_TTABLE) { + lua_getglobal(L, "nh_set_variables_string"); + lua_pushstring(L, key); + lua_pushvalue(L, -3); /* copy value to top */ + nhl_pcall(L, 2, 1); + luaL_loadstring(gl.luacore, lua_tostring(L, -1)); + nhl_pcall(gl.luacore, 0, 0); + } else + nhl_error(L, "Cannot set variable of that type"); + return 0; + } else + nhl_error(L, "Wrong number of arguments"); + return 1; +} + +/* return nh_lua_variable lua table as a string */ +char * +get_nh_lua_variables(void) +{ + char *key = NULL; + + if (!gl.luacore) { + nhl_error(gl.luacore, "nh luacore not inited"); + return key; + } + + lua_getglobal(gl.luacore, "nh_lua_variables"); + if (!lua_istable(gl.luacore, -1)) { + impossible("nh_lua_variables is not a lua table"); + return key; + } + + lua_getglobal(gl.luacore, "get_variables_string"); + if (lua_type(gl.luacore, -1) == LUA_TFUNCTION) { + if (nhl_pcall(gl.luacore, 0, 1)) { + impossible("Lua error: %s", lua_tostring(gl.luacore, -1)); + return key; + } + key = dupstr(lua_tostring(gl.luacore, -1)); + } + return key; +} + +/* save nh_lua_variables table to file */ +void +save_luadata(NHFILE *nhfp) +{ + char *lua_data = get_nh_lua_variables(); + long lua_data_len = strlen(lua_data) + 1; + + bwrite(nhfp->fd, (genericptr_t) &lua_data_len, sizeof lua_data_len); + bwrite(nhfp->fd, (genericptr_t) lua_data, strlen(lua_data) + 1); + free(lua_data); +} + +/* restore nh_lua_variables table from file */ +void +restore_luadata(NHFILE *nhfp) +{ + long lua_data_len; + char *lua_data; + + mread(nhfp->fd, (genericptr_t) &lua_data_len, sizeof lua_data_len); + lua_data = (char *) alloc(lua_data_len); + mread(nhfp->fd, (genericptr_t) lua_data, lua_data_len); + if (!gl.luacore) + l_nhcore_init(); + luaL_loadstring(gl.luacore, lua_data); + nhl_pcall(gl.luacore, 0, 0); +} + /* local stairs = stairways(); */ static int nhl_stairways(lua_State *L) @@ -1319,6 +1452,7 @@ static const struct luaL_Reg nhl_functions[] = { {"dump_fmtstr", nhl_dump_fmtstr}, #endif /* DUMPLOG */ {"dnum_name", nhl_dnum_name}, + {"variable", nhl_variable}, {"stairways", nhl_stairways}, {"pushkey", nhl_pushkey}, {"doturn", nhl_doturn}, diff --git a/src/restore.c b/src/restore.c index 3172202ed..397a8573d 100644 --- a/src/restore.c +++ b/src/restore.c @@ -688,6 +688,7 @@ restgamestate(NHFILE* nhfp, unsigned int* stuckid, unsigned int* steedid) restnames(nhfp); restore_msghistory(nhfp); restore_gamelog(nhfp); + restore_luadata(nhfp); /* must come after all mons & objs are restored */ relink_timers(FALSE); relink_light_sources(FALSE); diff --git a/src/save.c b/src/save.c index 3266f4a3c..e9ef617ab 100644 --- a/src/save.c +++ b/src/save.c @@ -344,6 +344,7 @@ savegamestate(NHFILE *nhfp) savenames(nhfp); save_msghistory(nhfp); save_gamelog(nhfp); + save_luadata(nhfp); if (nhfp->structlevel) bflush(nhfp->fd); gp.program_state.saving--;