From 7043e2134af145fef7b4d86d55aa1793f88e49b3 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Fri, 6 Dec 2019 21:29:12 +0200 Subject: [PATCH] Lua NetHack obj bindings --- include/extern.h | 8 + src/nhlobj.c | 356 ++++++++++++++++++++++++++++++++++++++ src/nhlua.c | 15 ++ sys/msdos/Makefile.GCC | 3 +- sys/msdos/Makefile2.cross | 2 +- sys/unix/Makefile.src | 4 +- sys/winnt/Makefile.gcc | 3 +- sys/winnt/Makefile.msc | 3 +- test/test_obj.lua | 41 +++++ test/testwish.lua | 47 +++++ 10 files changed, 476 insertions(+), 6 deletions(-) create mode 100644 src/nhlobj.c create mode 100644 test/test_obj.lua create mode 100644 test/testwish.lua diff --git a/include/extern.h b/include/extern.h index 02823455c..836784059 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1671,6 +1671,11 @@ E struct selectionvar *FDECL(l_selection_check, (lua_State *, int)); E int FDECL(l_selection_register, (lua_State *)); #endif +/* ### nhlobj.c ### */ +#if !defined(CROSSCOMPILE) || defined(CROSSCOMPILE_TARGET) +E int FDECL(l_obj_register, (lua_State *)); +#endif + /* ### nhlua.c ### */ #if !defined(CROSSCOMPILE) || defined(CROSSCOMPILE_TARGET) @@ -1681,6 +1686,9 @@ E void FDECL(nhl_error, (lua_State *, const char *)) NORETURN; E void FDECL(lcheck_param_table, (lua_State *)); E schar FDECL(get_table_mapchr, (lua_State *, const char *)); E schar FDECL(get_table_mapchr_opt, (lua_State *, const char *, SCHAR_P)); +E void FDECL(nhl_add_table_entry_int, (lua_State *, const char *, int)); +E void FDECL(nhl_add_table_entry_char, (lua_State *, const char *, CHAR_P)); +E void FDECL(nhl_add_table_entry_str, (lua_State *, const char *, const char *)); E schar FDECL(splev_chr2typ, (CHAR_P)); E schar FDECL(check_mapchr, (const char *)); E int FDECL(get_table_int, (lua_State *, const char *)); diff --git a/src/nhlobj.c b/src/nhlobj.c new file mode 100644 index 000000000..68e64a8dc --- /dev/null +++ b/src/nhlobj.c @@ -0,0 +1,356 @@ +/* NetHack 3.7 nhlobj.c $NHDT-Date: 1574646948 2019/11/25 01:55:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.4 $ */ +/* Copyright (c) 2019 by Pasi Kallinen */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" +#include "sp_lev.h" + +struct _lua_obj { + int state; /* UNUSED */ + struct obj *obj; +}; + +struct _lua_obj * +l_obj_check(L, index) +lua_State *L; +int index; +{ + struct _lua_obj *lo; + + luaL_checktype(L, index, LUA_TUSERDATA); + lo = (struct _lua_obj *)luaL_checkudata(L, index, "obj"); + if (!lo) + nhl_error(L, "Obj error"); + return lo; +} + +static int +l_obj_gc(L) +lua_State *L; +{ + struct _lua_obj *lo = l_obj_check(L, 1); + + if (lo && lo->obj) { + /* free-floating objects are deallocated */ + if (lo->obj->where == OBJ_FREE) { + if (Has_contents(lo->obj)) { + struct obj *otmp; + while ((otmp = lo->obj->cobj) != 0) { + obj_extract_self(otmp); + dealloc_obj(otmp); + } + } + dealloc_obj(lo->obj); + } + lo->obj = NULL; + } + + return 0; +} + +static struct _lua_obj * +l_obj_push(L, otmp) +lua_State *L; +struct obj *otmp; +{ + struct _lua_obj *lo = (struct _lua_obj *)lua_newuserdata(L, sizeof(struct _lua_obj)); + luaL_getmetatable(L, "obj"); + lua_setmetatable(L, -2); + + lo->state = 0; + lo->obj = otmp; + + return lo; +} + +/* local o = obj.new("large chest"); + local cobj = o:contents(); */ +static int +l_obj_getcontents(L) +lua_State *L; +{ + struct _lua_obj *lo = l_obj_check(L, 1); + struct obj *obj = lo->obj; + + if (!obj) + nhl_error(L, "l_obj_getcontents: no obj"); + if (!obj->cobj) + nhl_error(L, "l_obj_getcontents: no cobj"); + + (void) l_obj_push(L, obj->cobj); + return 1; +} + +/* local box = obj.new("large chest"); + box.addcontent(obj.new("rock")); +*/ +static int +l_obj_add_to_container(L) +lua_State *L; +{ + struct _lua_obj *lobox = l_obj_check(L, 1); + struct _lua_obj *lo = l_obj_check(L, 2); + struct obj *otmp; + + if (!lo->obj || !lobox->obj) + nhl_error(L, "l_obj_add_to_container: no obj"); + + if (!Is_container(lobox->obj)) + nhl_error(L, "l_obj_add_to_container: not a container"); + + otmp = add_to_container(lobox->obj, lo->obj); + + /* was lo->obj merged? FIXME: causes problems if both lo->obj and + the one it merged with are handled by lua. Use lo->state? */ + if (otmp != lo->obj) + lo->obj = otmp; + + return 0; +} + +/* Create a lua table representation of the object, unpacking all the object fields. + local o = obj.new("rock"); + local otbl = o:totable(); */ +static int +l_obj_to_table(L) +lua_State *L; +{ + struct _lua_obj *lo = l_obj_check(L, 1); + struct obj *obj = lo->obj; + char buf[BUFSZ]; + + lua_newtable(L); + + if (!obj) { + nhl_add_table_entry_int(L, "NO_OBJ", 1); + return 1; + } + + nhl_add_table_entry_int(L, "has_contents", Has_contents(obj)); + nhl_add_table_entry_int(L, "is_container", Is_container(obj)); + nhl_add_table_entry_int(L, "o_id", obj->o_id); + nhl_add_table_entry_int(L, "ox", obj->ox); + nhl_add_table_entry_int(L, "oy", obj->oy); + nhl_add_table_entry_int(L, "otyp", obj->otyp); + if (OBJ_NAME(objects[obj->otyp])) + nhl_add_table_entry_str(L, "otyp_name", OBJ_NAME(objects[obj->otyp])); + if (OBJ_DESCR(objects[obj->otyp])) + nhl_add_table_entry_str(L, "otyp_descr", OBJ_DESCR(objects[obj->otyp])); + nhl_add_table_entry_int(L, "owt", obj->owt); + nhl_add_table_entry_int(L, "quan", obj->quan); + nhl_add_table_entry_int(L, "spe", obj->spe); + nhl_add_table_entry_char(L, "oclass", def_oc_syms[obj->oclass].sym); + nhl_add_table_entry_char(L, "invlet", obj->invlet); + /* TODO: nhl_add_table_entry_char(L, "oartifact", obj->oartifact);*/ + nhl_add_table_entry_int(L, "where", obj->where); + /* TODO: nhl_add_table_entry_int(L, "timed", obj->timed); */ + nhl_add_table_entry_int(L, "cursed", obj->cursed); + nhl_add_table_entry_int(L, "blessed", obj->blessed); + nhl_add_table_entry_int(L, "unpaid", obj->unpaid); + nhl_add_table_entry_int(L, "no_charge", obj->no_charge); + nhl_add_table_entry_int(L, "known", obj->known); + nhl_add_table_entry_int(L, "dknown", obj->dknown); + nhl_add_table_entry_int(L, "bknown", obj->bknown); + nhl_add_table_entry_int(L, "rknown", obj->rknown); + if (obj->oclass == POTION_CLASS) + nhl_add_table_entry_int(L, "odiluted", obj->odiluted); + else + nhl_add_table_entry_int(L, "oeroded", obj->oeroded); + nhl_add_table_entry_int(L, "oeroded2", obj->oeroded2); + /* TODO: orotten, norevive */ + nhl_add_table_entry_int(L, "oerodeproof", obj->oerodeproof); + nhl_add_table_entry_int(L, "olocked", obj->olocked); + nhl_add_table_entry_int(L, "obroken", obj->obroken); + if (is_poisonable(obj)) + nhl_add_table_entry_int(L, "opoisoned", obj->opoisoned); + else + nhl_add_table_entry_int(L, "otrapped", obj->otrapped); + /* TODO: degraded_horn */ + nhl_add_table_entry_int(L, "recharged", obj->recharged); + /* TODO: on_ice */ + nhl_add_table_entry_int(L, "lamplit", obj->lamplit); + nhl_add_table_entry_int(L, "globby", obj->globby); + nhl_add_table_entry_int(L, "greased", obj->greased); + nhl_add_table_entry_int(L, "nomerge", obj->nomerge); + nhl_add_table_entry_int(L, "was_thrown", obj->was_thrown); + nhl_add_table_entry_int(L, "in_use", obj->in_use); + nhl_add_table_entry_int(L, "bypass", obj->bypass); + nhl_add_table_entry_int(L, "cknown", obj->cknown); + nhl_add_table_entry_int(L, "lknown", obj->lknown); + nhl_add_table_entry_int(L, "corpsenm", obj->corpsenm); + if (obj->corpsenm != NON_PM && (obj->otyp == TIN || obj->otyp == CORPSE || obj->otyp == EGG || obj->otyp == FIGURINE || obj->otyp == STATUE)) + nhl_add_table_entry_str(L, "corpsenm_name", mons[obj->corpsenm].mname); + /* TODO: leashmon, fromsink, novelidx, record_achieve_special */ + nhl_add_table_entry_int(L, "usecount", obj->usecount); + /* TODO: spestudied */ + nhl_add_table_entry_int(L, "oeaten", obj->oeaten); + nhl_add_table_entry_int(L, "age", obj->age); + nhl_add_table_entry_int(L, "owornmask", obj->owornmask); + /* TODO: more of oextra */ + nhl_add_table_entry_int(L, "has_oname", has_oname(obj)); + if (has_oname(obj)) + nhl_add_table_entry_str(L, "oname", ONAME(obj)); + + return 1; +} + +/* create a new object via wishing routine */ +/* local o = obj.new("rock"); */ +static int +l_obj_new_readobjnam(L) +lua_State *L; +{ + int argc = lua_gettop(L); + + if (argc == 1) { + char buf[BUFSZ]; + struct obj *otmp; + Sprintf(buf, "%s", luaL_checkstring(L, 1)); + lua_pop(L, 1); + otmp = readobjnam(buf, NULL); + (void) l_obj_push(L, otmp); + return 1; + } else + nhl_error(L, "l_obj_new_readobjname: Wrong args"); + return 0; +} + +/* Get the topmost object on the map at x,y */ +/* local o = obj.at(x, y); */ +static int +l_obj_at(L) +lua_State *L; +{ + int argc = lua_gettop(L); + + if (argc == 2) { + int x, y; + x = luaL_checkinteger(L, 1); + y = luaL_checkinteger(L, 2); + lua_pop(L, 2); + (void) l_obj_push(L, g.level.objects[x][y]); + return 1; + } else + nhl_error(L, "l_obj_at: Wrong args"); + return 0; +} + +/* Place an object on the map at (x,y). + local o = obj.new("rock"); + o:placeobj(u.ux, u.uy); */ +static int +l_obj_placeobj(L) +lua_State *L; +{ + int argc = lua_gettop(L); + struct _lua_obj *lo = l_obj_check(L, 1); + + if (argc != 3) + nhl_error(L, "l_obj_placeobj: Wrong args"); + + if (lo && lo->obj) { + int x, y; + x = luaL_checkinteger(L, 2); + y = luaL_checkinteger(L, 3); + lua_pop(L, 3); + + place_object(lo->obj, x, y); + } else + nhl_error(L, "l_obj_placeobj: Wrong args"); + + return 0; +} + +/* Get the next object in the object chain */ +/* local o = obj.at(x, y); + local o2 = o:next(); +*/ +static int +l_obj_nextobj(L) +lua_State *L; +{ + struct _lua_obj *lo = l_obj_check(L, 1); + + if (lo && lo->obj) + (void) l_obj_push(L, lo->obj->where == OBJ_FLOOR ? lo->obj->nexthere : lo->obj->nobj); + return 1; +} + +/* Get the container object is in */ +/* local box = o:container(); */ +static int +l_obj_container(L) +lua_State *L; +{ + struct _lua_obj *lo = l_obj_check(L, 1); + + if (lo && lo->obj && lo->obj->where == OBJ_CONTAINED) { + (void) l_obj_push(L, lo->obj->ocontainer); + return 1; + } + (void) l_obj_push(L, NULL); + return 1; +} + +/* Is the object a null? */ +/* local badobj = o:isnull(); */ +static int +l_obj_isnull(L) +lua_State *L; +{ + struct _lua_obj *lo = l_obj_check(L, 1); + + lua_pushboolean(L, lo && lo->obj); + return 1; +} + + +static const struct luaL_Reg l_obj_methods[] = { + { "new", l_obj_new_readobjnam }, + { "isnull", l_obj_isnull }, + { "at", l_obj_at }, + { "next", l_obj_nextobj }, + { "totable", l_obj_to_table }, + { "placeobj", l_obj_placeobj }, + { "container", l_obj_container }, + { "contents", l_obj_getcontents }, + { "addcontent", l_obj_add_to_container }, + { NULL, NULL } +}; + +static const luaL_Reg l_obj_meta[] = { + { "__gc", l_obj_gc }, + { NULL, NULL } +}; + +int +l_obj_register(L) +lua_State *L; +{ + int lib_id, meta_id; + + /* newclass = {} */ + lua_createtable(L, 0, 0); + lib_id = lua_gettop(L); + + /* metatable = {} */ + luaL_newmetatable(L, "obj"); + meta_id = lua_gettop(L); + luaL_setfuncs(L, l_obj_meta, 0); + + /* metatable.__index = _methods */ + luaL_newlib(L, l_obj_methods); + lua_setfield(L, meta_id, "__index"); + + /* metatable.__metatable = _meta */ + luaL_newlib(L, l_obj_meta); + lua_setfield(L, meta_id, "__metatable"); + + /* class.__metatable = metatable */ + lua_setmetatable(L, lib_id); + + /* _G["obj"] = newclass */ + lua_setglobal(L, "obj"); + + return 0; +} diff --git a/src/nhlua.c b/src/nhlua.c index eaa8e9758..d5dba8465 100644 --- a/src/nhlua.c +++ b/src/nhlua.c @@ -114,6 +114,19 @@ int value; lua_rawset(L, -3); } +void +nhl_add_table_entry_char(L, name, value) +lua_State *L; +const char *name; +char value; +{ + char buf[2]; + Sprintf(buf, "%c", value); + lua_pushstring(L, name); + lua_pushstring(L, buf); + lua_rawset(L, -3); +} + void nhl_add_table_entry_str(L, name, value) lua_State *L; @@ -874,6 +887,8 @@ nhl_init() l_selection_register(L); l_register_des(L); + l_obj_register(L); + if (!nhl_loadlua(L, "nhlib.lua")) { lua_close(L); return (lua_State *) 0; diff --git a/sys/msdos/Makefile.GCC b/sys/msdos/Makefile.GCC index 253f05490..fa40661c3 100644 --- a/sys/msdos/Makefile.GCC +++ b/sys/msdos/Makefile.GCC @@ -316,7 +316,7 @@ SOBJ = $(O)msdos.o $(O)pcsys.o $(O)tty.o $(O)unix.o \ VVOBJ = $(O)version.o ifeq "$(ADD_LUA)" "Y" -LUAOBJ = $(O)nhlua.o $(O)nhlsel.o +LUAOBJ = $(O)nhlua.o $(O)nhlsel.o $(O)nhlobj.o LUA_QTEXT_FILE = "quest.lua" endif @@ -1454,6 +1454,7 @@ $(O)muse.o: muse.c $(HACK_H) $(O)music.o: music.c $(HACK_H) #interp.c $(O)nhlua.o: nhlua.c $(HACK_H) $(O)nhlsel.o: nhlsel.c $(HACK_H) +$(O)nhlobj.o: nhlobj.c $(HACK_H) $(O)o_init.o: o_init.c $(HACK_H) $(INCL)/lev.h $(O)objects.o: objects.c $(CONFIG_H) $(INCL)/obj.h $(INCL)/objclass.h \ $(INCL)/prop.h $(INCL)/skills.h $(INCL)/color.h diff --git a/sys/msdos/Makefile2.cross b/sys/msdos/Makefile2.cross index 530cefe42..6b0b79632 100644 --- a/sys/msdos/Makefile2.cross +++ b/sys/msdos/Makefile2.cross @@ -320,7 +320,7 @@ VVOBJ = $(O)version.o MDLIB = $(O)mdlib.o ifeq "$(ADD_LUA)" "Y" -LUAOBJ = $(O)nhlua.o $(O)nhlsel.o +LUAOBJ = $(O)nhlua.o $(O)nhlsel.o $(O)nhlobj.o LUA_QTEXT_FILE = "quest.lua" endif diff --git a/sys/unix/Makefile.src b/sys/unix/Makefile.src index eddc63a86..ee4254ed9 100644 --- a/sys/unix/Makefile.src +++ b/sys/unix/Makefile.src @@ -449,7 +449,7 @@ HACKCSRC = allmain.c alloc.c apply.c artifact.c attrib.c ball.c bones.c \ lock.c mail.c makemon.c mapglyph.c mcastu.c mdlib.c mhitm.c \ mhitu.c minion.c mklev.c mkmap.c mkmaze.c mkobj.c mkroom.c mon.c \ mondata.c monmove.c monst.c mplayer.c mthrowu.c muse.c music.c \ - nhlua.c nhlsel.c o_init.c objects.c objnam.c \ + nhlua.c nhlsel.c nhlobj.c o_init.c objects.c objnam.c \ options.c pager.c pickup.c pline.c polyself.c potion.c pray.c \ priest.c quest.c questpgr.c read.c rect.c region.c restore.c \ rip.c rnd.c role.c rumors.c save.c sfstruct.c \ @@ -516,7 +516,7 @@ HOBJ = $(FIRSTOBJ) allmain.o alloc.o apply.o artifact.o attrib.o ball.o \ light.o lock.o mail.o makemon.o mapglyph.o mcastu.o mdlib.o mhitm.o \ mhitu.o minion.o mklev.o mkmap.o mkmaze.o mkobj.o mkroom.o mon.o \ mondata.o monmove.o mplayer.o mthrowu.o muse.o music.o \ - nhlua.o nhlsel.o o_init.o objnam.o options.o \ + nhlua.o nhlsel.o nhlobj.o o_init.o objnam.o options.o \ pager.o pickup.o pline.o polyself.o potion.o pray.o priest.o \ quest.o questpgr.o read.o rect.o region.o restore.o rip.o rnd.o \ role.o rumors.o save.o sfstruct.o \ diff --git a/sys/winnt/Makefile.gcc b/sys/winnt/Makefile.gcc index 9b292d737..5ba9ef68a 100644 --- a/sys/winnt/Makefile.gcc +++ b/sys/winnt/Makefile.gcc @@ -327,7 +327,7 @@ VOBJ28 = $(O)sfstruct.o #VOBJ31 = $(O)win10.o ifeq "$(ADD_LUA)" "Y" -LUAOBJ = $(O)nhlua.o $(O)nhlsel.o +LUAOBJ = $(O)nhlua.o $(O)nhlsel.o $(O)nhlobj.o LUA_QTEXT_FILE = "quest.lua" endif @@ -1593,6 +1593,7 @@ $(O)muse.o: muse.c $(HACK_H) $(O)music.o: music.c $(HACK_H) #interp.c $(O)nhlua.o: nhlua.c $(HACK_H) $(O)nhlsel.o: nhlsel.c $(HACK_H) +$(O)nhlobj.o: nhlobj.c $(HACK_H) $(O)o_init.o: o_init.c $(HACK_H) $(INCL)/lev.h $(O)objects.o: objects.c $(CONFIG_H) $(INCL)/obj.h $(INCL)/objclass.h \ $(INCL)/prop.h $(INCL)/skills.h $(INCL)/color.h diff --git a/sys/winnt/Makefile.msc b/sys/winnt/Makefile.msc index 08aa27ff5..aff743a53 100644 --- a/sys/winnt/Makefile.msc +++ b/sys/winnt/Makefile.msc @@ -327,7 +327,7 @@ VOBJ26 = $(O)were.o $(O)wield.o $(O)windows.o $(O)wizard.o VOBJ27 = $(O)worm.o $(O)worn.o $(O)write.o $(O)zap.o VOBJ28 = $(O)sfstruct.o -LUAOBJ = $(O)nhlua.o $(O)nhlsel.o +LUAOBJ = $(O)nhlua.o $(O)nhlsel.o $(O)nhlobj.o LUA_QTEXT_FILE = "quest.lua" !IFDEF CROSSCOMPILE_HOST @@ -1882,6 +1882,7 @@ $(O)muse.o: muse.c $(HACK_H) $(O)music.o: music.c $(HACK_H) #interp.c $(O)nhlua.o: nhlua.c $(HACK_H) $(O)nhlsel.o: nhlsel.c $(HACK_H) +$(O)nhlobj.o: nhlobj.c $(HACK_H) $(O)o_init.o: o_init.c $(HACK_H) $(INCL)\lev.h $(O)objnam.o: objnam.c $(HACK_H) $(O)options.o: options.c $(CONFIG_H) $(INCL)\objclass.h $(INCL)\flag.h \ diff --git a/test/test_obj.lua b/test/test_obj.lua new file mode 100644 index 000000000..7bda9bf0c --- /dev/null +++ b/test/test_obj.lua @@ -0,0 +1,41 @@ + +local o = obj.new("rock"); +o:placeobj(u.ux, u.uy); + +local o2 = obj.at(u.ux, u.uy); +local o2tbl = o2:totable(); +if (o2tbl.otyp_name ~= "rock") then + error("no rock under you"); +end + + +local box = obj.new("empty large box"); +local boxtbl = box:totable(); +if (boxtbl.has_contents ~= 0) then + error("empty box has contents"); +end + +box:addcontent(obj.new("diamond")); +boxtbl = box:totable(); +if (boxtbl.has_contents ~= 1) then + error("box has no contents"); +end + +local diamond = box:contents():totable(); +if (diamond.otyp_name ~= "diamond") then + error("box contents is not a diamond"); +end + +box:placeobj(u.ux, u.uy); + +local o3 = obj.at(u.ux, u.uy); +local o3tbl = o3:totable(); +if (o3tbl.otyp_name ~= "large box") then + error("no large box under you"); +end + +local o4 = o3:next(); +local o4tbl = o4:totable(); +if (o4tbl.otyp_name ~= "rock") then + error("no rock under the large box"); +end diff --git a/test/testwish.lua b/test/testwish.lua new file mode 100644 index 000000000..eb7d40b2a --- /dev/null +++ b/test/testwish.lua @@ -0,0 +1,47 @@ + +local wishtest_objects = { + ["a rock"] = { otyp_name = "rock", quan = 1, oclass = "*" }, + ["5 +3 blessed silver daggers"] = { otyp_name = "silver dagger", blessed = 1, cursed = 0, spe = 3, quan = 5 }, + ["an empty locked large box"] = { otyp_name = "large box", is_container = 1, has_contents = 0, olocked = 1 }, + ["potion of holy water"] = { otyp_name = "water", oclass = "!", blessed = 1, cursed = 0 }, + ["potion of unholy water"] = { otyp_name = "water", oclass = "!", blessed = 0, cursed = 1 }, + ["cursed greased +2 grey dragon scale mail"] = { otyp_name = "gray dragon scale mail", oclass = "[", blessed = 0, cursed = 1, spe = 2, greased = 1 }, + ["uncursed magic marker (11)"] = { otyp_name = "magic marker", blessed = 0, cursed = 0, spe = 11 }, + ["lit oil lamp"] = { otyp_name = "oil lamp", lamplit = 1 }, + ["unlit oil lamp"] = { otyp_name = "oil lamp", lamplit = 0 }, + ["2 blank scrolls"] = { otyp_name = "blank paper", quan = 2 }, + ["3 rusty poisoned darts"] = { otyp_name = "dart", quan = 3, opoisoned = 1, oeroded = 1 }, + ["broken empty chest"] = { otyp_name = "chest", obroken = 1 }, + ["4 diluted dark green potions named whisky"] = { otyp_descr = "dark green", oclass = "!", quan = 4, odiluted = 1, has_oname = 1, oname = "whisky" }, + ["poisoned food ration"] = { otyp_name = "food ration", oclass = "%", age = 1 }, + ["empty tin"] = { otyp_name = "tin", oclass = "%", corpsenm = -1, spe = 0 }, + ["blessed tin of spinach"] = { otyp_name = "tin", oclass = "%", corpsenm = -1, spe = 1, blessed = 1 }, + ["trapped tin of floating eye meat"] = { otyp_name = "tin", oclass = "%", otrapped = 1, corpsenm_name = "floating eye" }, + ["hill orc corpse"] = { otyp_name = "corpse", oclass = "%", corpsenm_name = "hill orc" }, + ["destroy armor"] = { otyp_name = "destroy armor", oclass = "?" }, + ["enchant weapon"] = { otyp_name = "enchant weapon", oclass = "?" }, + ["scroll of food detection"] = { otyp_name = "food detection", oclass = "?" }, + ["scroll of detect food"] = { otyp_name = "food detection", oclass = "?" }, + ["spellbook of food detection"] = { otyp_name = "detect food", oclass = "+" }, + ["spell"] = { NO_OBJ = 1 }, + ["-1 ring mail"] = { otyp_name = "ring mail", oclass = "[", spe = -1 }, + ["studded leather armor"] = { otyp_name = "studded leather armor", oclass = "[" }, + ["leather armor"] = { otyp_name = "leather armor", oclass = "[" }, + ["tooled horn"] = { otyp_name = "tooled horn", oclass = "(" }, + ["meat ring"] = { otyp_name = "meat ring", oclass = "%" }, + ["beartrap"] = { otyp_name = "beartrap", oclass = "(" }, + ["bear trap"] = { otyp_name = "beartrap", oclass = "(" }, + ["landmine"] = { otyp_name = "land mine", oclass = "(" }, + ["land mine"] = { otyp_name = "land mine", oclass = "(" }, + -- ["partly eaten orange"] = { otyp_descr = "orange", oclass = "%", oeaten = ... }, +}; + + +for str, tbl in pairs(wishtest_objects) do + local o = obj.new(str):totable(); + for field, value in pairs(tbl) do + if (o[field] ~= value) then + error("wished " .. str .. ", but " .. field .. " is " .. o[field] .. ", not " .. value); + end + end +end