Lua tests: generation of each object

Test generation of every object, both via des.object and obj.new.
Expose FIRST_OBJECT and LAST_OBJECT numbers to lua.
Add lua nh.int_to_objname, a function to convert integer value to
object base name and class.
Allow creating new nethack lua object by specifying id and class.
This commit is contained in:
Pasi Kallinen
2026-01-26 18:00:33 +02:00
parent b5ca1a3ed8
commit 11bed1f55b
7 changed files with 122 additions and 16 deletions
+19 -4
View File
@@ -259,6 +259,17 @@ Example:
local str = nh.ing_suffix("foo"); local str = nh.ing_suffix("foo");
=== int_to_objname
Convert integer value to object name and class.
Returns two strings, the object base name and the class character.
The returned strings may be empty if an error occurred.
Example:
local oname, oclass = nh.int_to_objname(45);
=== int_to_pmname === int_to_pmname
Convert integer value to monster type name. Convert integer value to monster type name.
@@ -850,7 +861,7 @@ Example:
=== object === object
Create an object. Returns the object as an <<Obj>> class. Create an object and place it somewhere on the map. Returns the object as an <<Obj>> class.
The table parameter accepts the following: The table parameter accepts the following:
[options="header"] [options="header"]
@@ -874,8 +885,8 @@ The table parameter accepts the following:
| greased | boolean | Is the object greased? | greased | boolean | Is the object greased?
| broken | boolean | Is the object broken? | broken | boolean | Is the object broken?
| achievement | boolean | Is there an achievement attached to the object? | achievement | boolean | Is there an achievement attached to the object?
| x, y | int | Coordinates on the level | x, y | int | Coordinates on the level; defaults to a random location.
| coord | table | x,y coordinates in table format | coord | table | x,y coordinates in table format; defaults to a random location.
| montype | string | Monster id or class | montype | string | Monster id or class
| historic | boolean | Is statue historic? | historic | boolean | Is statue historic?
| male | boolean | Is statue male? | male | boolean | Is statue male?
@@ -1380,11 +1391,13 @@ Handling objects via obj-class.
=== new === new
Create a new object via wishing routine. Create a new object, either via wishing routine, or specifying object name and class.
Unlike des.object, does not place the object anywhere.
Example: Example:
local o = obj.new("rock"); local o = obj.new("rock");
local o = obj.new({ id = "invisibility", class = "!" });
=== isnull === isnull
@@ -1576,6 +1589,8 @@ These constants are in the `nhc` table.
| NUMMONS | Number of different monster types | NUMMONS | Number of different monster types
| LOW_PM | First monster type id. See <<_int_to_pmname>>. | LOW_PM | First monster type id. See <<_int_to_pmname>>.
| HIGH_PM | Last monster type id. See <<_int_to_pmname>>. | HIGH_PM | Last monster type id. See <<_int_to_pmname>>.
| FIRST_OBJECT | First object type id. See <<_int_to_objname>>.
| LAST_OBJECT | Number of object type ids. See <<_int_to_objname>>.
| DLB | 1 or 0, depending if NetHack is compiled with DLB | DLB | 1 or 0, depending if NetHack is compiled with DLB
|=== |===
+2
View File
@@ -3033,6 +3033,8 @@ extern int nhl_abs_coord(lua_State *) NONNULLARG1;
extern void update_croom(void); extern void update_croom(void);
extern const char *get_trapname_bytype(int); extern const char *get_trapname_bytype(int);
extern void l_register_des(lua_State *) NONNULLARG1; extern void l_register_des(lua_State *) NONNULLARG1;
extern int get_table_objclass(lua_State *) NONNULLARG1;
extern int get_table_objtype(lua_State *) NONNULLARG1;
#endif /* !CROSSCOMPILE || CROSSCOMPILE_TARGET */ #endif /* !CROSSCOMPILE || CROSSCOMPILE_TARGET */
/* ### spell.c ### */ /* ### spell.c ### */
+18 -1
View File
@@ -345,12 +345,13 @@ DISABLE_WARNING_UNREACHABLE_CODE
/* create a new object via wishing routine */ /* create a new object via wishing routine */
/* local o = obj.new("rock"); */ /* local o = obj.new("rock"); */
/* local o = obj.new({ id = "food ration", class = "%" }); */
staticfn int staticfn int
l_obj_new_readobjnam(lua_State *L) l_obj_new_readobjnam(lua_State *L)
{ {
int argc = lua_gettop(L); int argc = lua_gettop(L);
if (argc == 1) { if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) {
char buf[BUFSZ]; char buf[BUFSZ];
struct obj *otmp; struct obj *otmp;
@@ -360,6 +361,22 @@ l_obj_new_readobjnam(lua_State *L)
otmp = NULL; otmp = NULL;
(void) l_obj_push(L, otmp); (void) l_obj_push(L, otmp);
return 1; return 1;
} else if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) {
short id = get_table_objtype(L);
xint16 class = get_table_objclass(L);
struct obj *otmp;
if (id >= FIRST_OBJECT) {
otmp = mksobj(id, TRUE, FALSE);
} else {
class = def_char_to_objclass(class);
if (class >= MAXOCLASSES)
class = RANDOM_CLASS;
otmp = mkobj(class, FALSE);
}
lua_pop(L, 1);
(void) l_obj_push(L, otmp);
return 1;
} else } else
nhl_error(L, "l_obj_new_readobjname: Wrong args"); nhl_error(L, "l_obj_new_readobjname: Wrong args");
/*NOTREACHED*/ /*NOTREACHED*/
+29
View File
@@ -32,6 +32,7 @@ staticfn int nhl_dump_fmtstr(lua_State *);
#endif /* DUMPLOG */ #endif /* DUMPLOG */
staticfn int nhl_dnum_name(lua_State *); staticfn int nhl_dnum_name(lua_State *);
staticfn int nhl_int_to_pm_name(lua_State *); staticfn int nhl_int_to_pm_name(lua_State *);
staticfn int nhl_int_to_obj_name(lua_State *);
staticfn int nhl_stairways(lua_State *); staticfn int nhl_stairways(lua_State *);
staticfn int nhl_pushkey(lua_State *); staticfn int nhl_pushkey(lua_State *);
staticfn int nhl_doturn(lua_State *); staticfn int nhl_doturn(lua_State *);
@@ -1185,6 +1186,31 @@ nhl_int_to_pm_name(lua_State *L)
return 1; return 1;
} }
/* convert integer to object type name and class */
/* local oname,oclass = int_to_objname(25); */
staticfn int
nhl_int_to_obj_name(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
char buf[8];
lua_Integer i = luaL_checkinteger(L, 1);
if (i >= 0 && i < NUM_OBJECTS && OBJ_NAME(objects[i])) {
lua_pushstring(L, OBJ_NAME(objects[i]));
buf[0] = def_oc_syms[(int)objects[i].oc_class].sym;
buf[1] = '\0';
lua_pushstring(L, buf);
} else {
lua_pushstring(L, "");
lua_pushstring(L, "");
}
} else
nhl_error(L, "Expected an integer parameter");
return 2;
}
DISABLE_WARNING_UNREACHABLE_CODE DISABLE_WARNING_UNREACHABLE_CODE
/* because nhl_error() does not return */ /* because nhl_error() does not return */
@@ -1858,6 +1884,7 @@ static const struct luaL_Reg nhl_functions[] = {
#endif /* DUMPLOG */ #endif /* DUMPLOG */
{ "dnum_name", nhl_dnum_name }, { "dnum_name", nhl_dnum_name },
{ "int_to_pmname", nhl_int_to_pm_name }, { "int_to_pmname", nhl_int_to_pm_name },
{ "int_to_objname", nhl_int_to_obj_name },
{ "variable", nhl_variable }, { "variable", nhl_variable },
{ "stairways", nhl_stairways }, { "stairways", nhl_stairways },
{ "pushkey", nhl_pushkey }, { "pushkey", nhl_pushkey },
@@ -1876,6 +1903,8 @@ static const struct {
{ "NUMMONS", NUMMONS }, { "NUMMONS", NUMMONS },
{ "LOW_PM", LOW_PM }, { "LOW_PM", LOW_PM },
{ "HIGH_PM", HIGH_PM }, { "HIGH_PM", HIGH_PM },
{ "FIRST_OBJECT", FIRST_OBJECT },
{ "LAST_OBJECT", NUM_OBJECTS-1 },
#ifdef DLB #ifdef DLB
{ "DLB", 1 }, { "DLB", 1 },
#else #else
+14 -11
View File
@@ -117,9 +117,7 @@ staticfn int find_montype(lua_State *, const char *, int *);
staticfn int get_table_montype(lua_State *, int *); staticfn int get_table_montype(lua_State *, int *);
staticfn lua_Integer get_table_int_or_random(lua_State *, const char *, int); staticfn lua_Integer get_table_int_or_random(lua_State *, const char *, int);
staticfn int get_table_buc(lua_State *); staticfn int get_table_buc(lua_State *);
staticfn int get_table_objclass(lua_State *); staticfn int find_objtype(lua_State *, const char *, char);
staticfn int find_objtype(lua_State *, const char *);
staticfn int get_table_objtype(lua_State *);
staticfn const char *get_mkroom_name(int) NONNULL; staticfn const char *get_mkroom_name(int) NONNULL;
staticfn int get_table_roomtype_opt(lua_State *, const char *, int); staticfn int get_table_roomtype_opt(lua_State *, const char *, int);
staticfn int get_table_traptype_opt(lua_State *, const char *, int); staticfn int get_table_traptype_opt(lua_State *, const char *, int);
@@ -3443,7 +3441,7 @@ get_table_buc(lua_State *L)
return curse_state; return curse_state;
} }
staticfn int int
get_table_objclass(lua_State *L) get_table_objclass(lua_State *L)
{ {
char *s = get_table_str_opt(L, "class", NULL); char *s = get_table_str_opt(L, "class", NULL);
@@ -3455,13 +3453,14 @@ get_table_objclass(lua_State *L)
return ret; return ret;
} }
/* find object otyp by text s (optionally considering oclass) */
staticfn int staticfn int
find_objtype(lua_State *L, const char *s) find_objtype(lua_State *L, const char *s, char oclass)
{ {
if (s && *s) { if (s && *s) {
int i; int i;
const char *objname; const char *objname;
char class = 0; char class = def_char_to_objclass(oclass);
/* In objects.h, some item classes are defined without prefixes /* In objects.h, some item classes are defined without prefixes
(such as "scroll of ") in their names, making some names (such (such as "scroll of ") in their names, making some names (such
@@ -3479,6 +3478,9 @@ find_objtype(lua_State *L, const char *s)
{ NULL, 0 } { NULL, 0 }
}; };
if (class == MAXOCLASSES)
class = 0;
if (strstri(s, " of ")) { if (strstri(s, " of ")) {
for (i = 0; class_prefixes[i].prefix; i++) { for (i = 0; class_prefixes[i].prefix; i++) {
const char *p = class_prefixes[i].prefix; const char *p = class_prefixes[i].prefix;
@@ -3523,11 +3525,12 @@ find_objtype(lua_State *L, const char *s)
return STRANGE_OBJECT; return STRANGE_OBJECT;
} }
staticfn int int
get_table_objtype(lua_State *L) get_table_objtype(lua_State *L)
{ {
char *s = get_table_str_opt(L, "id", NULL); char *s = get_table_str_opt(L, "id", NULL);
int ret = find_objtype(L, s); char oclass = get_table_objclass(L);
int ret = find_objtype(L, s, oclass);
Free(s); Free(s);
return ret; return ret;
@@ -3586,7 +3589,7 @@ lspo_object(lua_State *L)
tmpobj.id = STRANGE_OBJECT; tmpobj.id = STRANGE_OBJECT;
} else { } else {
tmpobj.class = -1; tmpobj.class = -1;
tmpobj.id = find_objtype(L, paramstr); tmpobj.id = find_objtype(L, paramstr, -1);
} }
} else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING
&& lua_type(L, 2) == LUA_TTABLE) { && lua_type(L, 2) == LUA_TTABLE) {
@@ -3599,7 +3602,7 @@ lspo_object(lua_State *L)
tmpobj.id = STRANGE_OBJECT; tmpobj.id = STRANGE_OBJECT;
} else { } else {
tmpobj.class = -1; tmpobj.class = -1;
tmpobj.id = find_objtype(L, paramstr); tmpobj.id = find_objtype(L, paramstr, -1);
} }
} else if (argc == 3 && lua_type(L, 2) == LUA_TNUMBER } else if (argc == 3 && lua_type(L, 2) == LUA_TNUMBER
&& lua_type(L, 3) == LUA_TNUMBER) { && lua_type(L, 3) == LUA_TNUMBER) {
@@ -3613,7 +3616,7 @@ lspo_object(lua_State *L)
tmpobj.id = STRANGE_OBJECT; tmpobj.id = STRANGE_OBJECT;
} else { } else {
tmpobj.class = -1; tmpobj.class = -1;
tmpobj.id = find_objtype(L, paramstr); tmpobj.id = find_objtype(L, paramstr, -1);
} }
} else { } else {
lcheck_param_table(L); lcheck_param_table(L);
+21
View File
@@ -180,6 +180,27 @@ function test_object()
des.object({ name = "Random object" }); des.object({ name = "Random object" });
des.object({ class = "*", name = "Random stone" }); des.object({ class = "*", name = "Random stone" });
des.object({ id ="broadsword", name = "Dragonbane" }) des.object({ id ="broadsword", name = "Dragonbane" })
for i = nhc.FIRST_OBJECT, nhc.LAST_OBJECT do
local oid, oclass = nh.int_to_objname(i);
if (oid ~= "") then
local o = des.object({ id = oid, class = oclass });
local o_t = o:totable();
-- crysknife reverts to worm tooth on the floor
if not(oid == "crysknife" and o_t.otyp_name == "worm tooth") then
if (o_t.otyp_name ~= oid) then
error("object name \"" .. o_t.otyp_name .. "\" created, wanted \"" .. oid .. "\"");
end
if (o_t.oclass ~= oclass) then
local str = string.format("object class \"%s\" created, wanted \"%s\" (%s)", o_t.oclass, oclass, oid);
error(str);
end
end
end
end
des.reset_level(); des.reset_level();
des.level_init(); des.level_init();
end end
+19
View File
@@ -82,3 +82,22 @@ box:addcontent(o5);
local o6 = obj.new("statue"); local o6 = obj.new("statue");
o6:addcontent(obj.new("spellbook")); o6:addcontent(obj.new("spellbook"));
-- generate one of each object, check the name and class matches
for i = nhc.FIRST_OBJECT, nhc.LAST_OBJECT do
local oid, oclass = nh.int_to_objname(i);
if (oid ~= "") then
local oi = obj.new({ id = oid, class = oclass });
local oi_t = oi:totable();
if (oi_t.otyp_name ~= oid) then
error("object name \"" .. oi_t.otyp_name .. "\" created, wanted \"" .. oid .. "\"");
end
if (oi_t.oclass ~= oclass) then
local str = string.format("object class \"%s\" created, wanted \"%s\" (%s)", oi_t.oclass, oclass, oid);
error(str);
end
end
end