/* NetHack 3.7 nhlobj.c $NHDT-Date: 1576097301 2019/12/11 20:48:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.0 $ */ /* 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; }; staticfn struct _lua_obj *l_obj_check(lua_State *, int); staticfn int l_obj_add_to_container(lua_State *); staticfn int l_obj_gc(lua_State *); staticfn int l_obj_getcontents(lua_State *); staticfn int l_obj_isnull(lua_State *); staticfn int l_obj_new_readobjnam(lua_State *); staticfn int l_obj_nextobj(lua_State *); staticfn int l_obj_objects_to_table(lua_State *); staticfn int l_obj_placeobj(lua_State *); staticfn int l_obj_to_table(lua_State *); staticfn int l_obj_at(lua_State *); staticfn int l_obj_container(lua_State *); staticfn int l_obj_timer_has(lua_State *); staticfn int l_obj_timer_peek(lua_State *); staticfn int l_obj_timer_stop(lua_State *); staticfn int l_obj_timer_start(lua_State *); staticfn int l_obj_bury(lua_State *); staticfn struct _lua_obj *l_obj_push(lua_State *, struct obj *); #define lobj_is_ok(lo) ((lo) && (lo)->obj && (lo)->obj->where != OBJ_LUAFREE) staticfn struct _lua_obj * l_obj_check(lua_State *L, int indx) { struct _lua_obj *lo; luaL_checktype(L, indx, LUA_TUSERDATA); lo = (struct _lua_obj *) luaL_checkudata(L, indx, "obj"); if (!lo) nhl_error(L, "Obj error"); return lo; } staticfn int l_obj_gc(lua_State *L) { struct obj *obj, *otmp; struct _lua_obj *lo = l_obj_check(L, 1); if (lo && (obj = lo->obj) != 0) { if (obj->lua_ref_cnt > 0) obj->lua_ref_cnt--; /* free-floating objects with no other refs are deallocated. */ if (!obj->lua_ref_cnt && (obj->where == OBJ_FREE || obj->where == OBJ_LUAFREE)) { if (Has_contents(obj)) { while ((otmp = obj->cobj) != 0) { obj_extract_self(otmp); dealloc_obj(otmp); } } obj->where = OBJ_FREE; dealloc_obj(obj), obj = 0; } lo->obj = NULL; } return 0; } staticfn struct _lua_obj * l_obj_push(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; if (otmp) otmp->lua_ref_cnt++; return lo; } void nhl_push_obj(lua_State *L, struct obj *otmp) { (void) l_obj_push(L, otmp); } /* local o = obj.new("large chest"); local cobj = o:contents(); */ staticfn int l_obj_getcontents(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"); (void) l_obj_push(L, obj->cobj); return 1; } /* Puts object inside another object. */ /* local box = obj.new("large chest"); box:addcontent(obj.new("rock")); */ staticfn int l_obj_add_to_container(lua_State *L) { struct _lua_obj *lobox = l_obj_check(L, 1); struct _lua_obj *lo = l_obj_check(L, 2); struct obj *otmp; int refs; if (!lobj_is_ok(lo) || !lobj_is_ok(lobox)) return 0; refs = lo->obj->lua_ref_cnt; obj_extract_self(lo->obj); otmp = add_to_container(lobox->obj, lo->obj); /* was lo->obj merged? */ if (otmp != lo->obj) { lo->obj = otmp; lo->obj->lua_ref_cnt += refs; } lobox->obj->owt = weight(lobox->obj); return 0; } /* Put object into player's inventory */ /* u.giveobj(obj.new("rock")); */ int nhl_obj_u_giveobj(lua_State *L) { struct _lua_obj *lo = l_obj_check(L, 1); struct obj *otmp; int refs; if (!lobj_is_ok(lo) || lo->obj->where == OBJ_INVENT) return 0; refs = lo->obj->lua_ref_cnt; obj_extract_self(lo->obj); otmp = addinv(lo->obj); if (otmp != lo->obj) { lo->obj->lua_ref_cnt += refs; lo->obj = otmp; } return 0; } DISABLE_WARNING_UNREACHABLE_CODE /* Get a table of object class data. */ /* local odata = obj.class(otbl.otyp); */ /* local odata = obj.class(obj.new("rock")); */ /* local odata = o:class(); */ staticfn int l_obj_objects_to_table(lua_State *L) { int argc = lua_gettop(L); int otyp = -1; struct objclass *o; if (argc != 1) { nhl_error(L, "l_obj_objects_to_table: Wrong args"); /*NOTREACHED*/ return 0; } if (lua_type(L, 1) == LUA_TNUMBER) { otyp = (int) luaL_checkinteger(L, 1); } else if (lua_type(L, 1) == LUA_TUSERDATA) { struct _lua_obj *lo = l_obj_check(L, 1); if (lo && lo->obj) otyp = lo->obj->otyp; } lua_pop(L, 1); if (otyp == -1) { nhl_error(L, "l_obj_objects_to_table: Wrong args"); /*NOTREACHED*/ return 0; } o = &objects[otyp]; lua_newtable(L); if (OBJ_NAME(objects[otyp])) nhl_add_table_entry_str(L, "name", OBJ_NAME(objects[otyp])); if (OBJ_DESCR(objects[otyp])) nhl_add_table_entry_str(L, "descr", OBJ_DESCR(objects[otyp])); if (o->oc_uname) nhl_add_table_entry_str(L, "uname", o->oc_uname); nhl_add_table_entry_int(L, "name_known", o->oc_name_known); nhl_add_table_entry_int(L, "merge", o->oc_merge); nhl_add_table_entry_int(L, "uses_known", o->oc_uses_known); nhl_add_table_entry_int(L, "pre_discovered", o->oc_pre_discovered); nhl_add_table_entry_int(L, "magic", o->oc_magic); nhl_add_table_entry_int(L, "charged", o->oc_charged); nhl_add_table_entry_int(L, "unique", o->oc_unique); nhl_add_table_entry_int(L, "nowish", o->oc_nowish); nhl_add_table_entry_int(L, "big", o->oc_big); /* TODO: oc_bimanual, oc_bulky */ nhl_add_table_entry_int(L, "tough", o->oc_tough); nhl_add_table_entry_int(L, "dir", o->oc_dir); /* TODO: convert to text */ nhl_add_table_entry_str(L, "material", materialnm[o->oc_material]); /* TODO: oc_subtyp, oc_skill, oc_armcat */ nhl_add_table_entry_int(L, "oprop", o->oc_oprop); nhl_add_table_entry_char(L, "class", def_oc_syms[(uchar) o->oc_class].sym); nhl_add_table_entry_int(L, "delay", o->oc_delay); nhl_add_table_entry_int(L, "color", o->oc_color); /* TODO: text? */ nhl_add_table_entry_int(L, "prob", o->oc_prob); nhl_add_table_entry_int(L, "weight", o->oc_weight); nhl_add_table_entry_int(L, "cost", o->oc_cost); nhl_add_table_entry_int(L, "damage_small", o->oc_wsdam); nhl_add_table_entry_int(L, "damage_large", o->oc_wldam); /* TODO: oc_oc1, oc_oc2, oc_hitbon, a_ac, a_can, oc_level */ nhl_add_table_entry_int(L, "nutrition", o->oc_nutrition); return 1; } RESTORE_WARNING_UNREACHABLE_CODE /* Create a lua table representation of the object, unpacking all the object fields. local o = obj.new("rock"); local otbl = o:totable(); */ staticfn int l_obj_to_table(lua_State *L) { struct _lua_obj *lo = l_obj_check(L, 1); struct obj *obj = lo->obj; lua_newtable(L); if (!obj || obj->where == OBJ_LUAFREE) { 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); if (obj->otyp == STATUE) nhl_add_table_entry_int(L, "historic", (obj->spe & CORPSTAT_HISTORIC) != 0); if (obj->otyp == CORPSE || obj->otyp == STATUE) { nhl_add_table_entry_int(L, "male", (obj->spe & CORPSTAT_MALE) != 0); nhl_add_table_entry_int(L, "female", (obj->spe & CORPSTAT_FEMALE) != 0); } nhl_add_table_entry_char(L, "oclass", def_oc_syms[(uchar) 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); nhl_add_table_entry_int(L, "tknown", obj->tknown); 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, "how_lost", obj->how_lost); 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].pmnames[NEUTRAL]); /* 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; } DISABLE_WARNING_UNREACHABLE_CODE /* create a new object via wishing routine */ /* local o = obj.new("rock"); */ staticfn int l_obj_new_readobjnam(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); if ((otmp = readobjnam(buf, NULL)) == &hands_obj) otmp = NULL; (void) l_obj_push(L, otmp); return 1; } else nhl_error(L, "l_obj_new_readobjname: Wrong args"); /*NOTREACHED*/ return 0; } /* Get the topmost object on the map at x,y */ /* local o = obj.at(x, y); */ staticfn int l_obj_at(lua_State *L) { int argc = lua_gettop(L); if (argc == 2) { coordxy x, y; x = (coordxy) luaL_checkinteger(L, 1); y = (coordxy) luaL_checkinteger(L, 2); cvt_to_abscoord(&x, &y); lua_pop(L, 2); (void) l_obj_push(L, svl.level.objects[x][y]); return 1; } else nhl_error(L, "l_obj_at: Wrong args"); /*NOTREACHED*/ return 0; } /* Place an object on the map at (x,y). local o = obj.new("rock"); o:placeobj(u.ux, u.uy); */ staticfn int l_obj_placeobj(lua_State *L) { int argc = lua_gettop(L); struct _lua_obj *lo = l_obj_check(L, 1); coordxy x, y; if (argc != 3) nhl_error(L, "l_obj_placeobj: Wrong args"); x = (coordxy) luaL_checkinteger(L, 2); y = (coordxy) luaL_checkinteger(L, 3); cvt_to_abscoord(&x, &y); lua_pop(L, 3); if (lobj_is_ok(lo)) { obj_extract_self(lo->obj); place_object(lo->obj, x, y); newsym(x, y); } return 0; } RESTORE_WARNING_UNREACHABLE_CODE /* Get the next object in the object chain */ /* local o = obj.at(x, y); local o2 = o:next(true); local firstobj = obj.next(); */ staticfn int l_obj_nextobj(lua_State *L) { int argc = lua_gettop(L); if (argc == 0) { (void) l_obj_push(L, fobj); } else { struct _lua_obj *lo = l_obj_check(L, 1); boolean use_nexthere = FALSE; if (argc == 2) use_nexthere = lua_toboolean(L, 2); if (lo && lo->obj) (void) l_obj_push(L, (use_nexthere && lo->obj->where == OBJ_FLOOR) ? lo->obj->nexthere : lo->obj->nobj); } return 1; } /* Get the container object is in */ /* local box = o:container(); */ staticfn int l_obj_container(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); else (void) l_obj_push(L, NULL); return 1; } /* Is the object a null? */ /* local badobj = o:isnull(); */ staticfn int l_obj_isnull(lua_State *L) { struct _lua_obj *lo = l_obj_check(L, 1); lua_pushboolean(L, !lobj_is_ok(lo)); return 1; } DISABLE_WARNING_UNREACHABLE_CODE /* does object have a timer of certain type? */ /* local hastimer = o:has_timer("rot-organic"); */ staticfn int l_obj_timer_has(lua_State *L) { int argc = lua_gettop(L); if (argc == 2) { struct _lua_obj *lo = l_obj_check(L, 1); short timertype = nhl_get_timertype(L, 2); if (timer_is_obj(timertype) && lo && lo->obj) { lua_pushboolean(L, obj_has_timer(lo->obj, timertype)); return 1; } else { lua_pushboolean(L, FALSE); return 1; } } else nhl_error(L, "l_obj_timer_has: Wrong args"); return 0; } /* peek at an object timer. return the turn when timer triggers. returns 0 if no such timer attached to the object. */ /* local timeout = o:peek_timer("hatch-egg"); */ staticfn int l_obj_timer_peek(lua_State *L) { int argc = lua_gettop(L); if (argc == 2) { struct _lua_obj *lo = l_obj_check(L, 1); short timertype = nhl_get_timertype(L, 2); if (timer_is_obj(timertype) && lo && lo->obj) { lua_pushinteger(L, peek_timer(timertype, obj_to_any(lo->obj))); return 1; } else { lua_pushinteger(L, 0); return 1; } } else nhl_error(L, "l_obj_timer_peek: Wrong args"); /*NOTREACHED*/ return 0; } /* stop object timer(s). return the turn when timer triggers. returns 0 if no such timer attached to the object. without a timer type parameter, stops all timers for the object. */ /* local timeout = o:stop_timer("rot-organic"); */ /* o:stop_timer(); */ staticfn int l_obj_timer_stop(lua_State *L) { int argc = lua_gettop(L); if (argc == 1) { struct _lua_obj *lo = l_obj_check(L, 1); if (lo && lo->obj) obj_stop_timers(lo->obj); return 0; } else if (argc == 2) { struct _lua_obj *lo = l_obj_check(L, 1); short timertype = nhl_get_timertype(L, 2); if (timer_is_obj(timertype) && lo && lo->obj) { lua_pushinteger(L, stop_timer(timertype, obj_to_any(lo->obj))); return 1; } else { lua_pushinteger(L, 0); return 1; } } else nhl_error(L, "l_obj_timer_stop: Wrong args"); return 0; } RESTORE_WARNING_UNREACHABLE_CODE /* start an object timer. */ /* o:start_timer("hatch-egg", 10); */ staticfn int l_obj_timer_start(lua_State *L) { int argc = lua_gettop(L); if (argc == 3) { struct _lua_obj *lo = l_obj_check(L, 1); short timertype = nhl_get_timertype(L, 2); long when = luaL_checkinteger(L, 3); if (timer_is_obj(timertype) && lo && lo->obj && when > 0) { if (obj_has_timer(lo->obj, timertype)) stop_timer(timertype, obj_to_any(lo->obj)); start_timer(when, TIMER_OBJECT, timertype, obj_to_any(lo->obj)); } } else nhl_error(L, "l_obj_timer_start: Wrong args"); return 0; } /* bury an obj. returns true if object is gone (merged with ground), false otherwise. */ /* local ogone = o:bury(); */ /* local ogone = o:bury(5,5); */ staticfn int l_obj_bury(lua_State *L) { int argc = lua_gettop(L); boolean dealloced = FALSE; struct _lua_obj *lo = l_obj_check(L, 1); coordxy x = 0, y = 0; if (argc == 1) { x = lo->obj->ox; y = lo->obj->oy; } else if (argc == 3) { x = (coordxy) lua_tointeger(L, 2); y = (coordxy) lua_tointeger(L, 3); cvt_to_abscoord(&x, &y); } else nhl_error(L, "l_obj_bury: Wrong args"); if (lobj_is_ok(lo) && isok(x, y)) { lo->obj->ox = x; lo->obj->oy = y; (void) bury_an_obj(lo->obj, &dealloced); } lua_pushboolean(L, dealloced); 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 }, { "class", l_obj_objects_to_table }, { "placeobj", l_obj_placeobj }, { "container", l_obj_container }, { "contents", l_obj_getcontents }, { "addcontent", l_obj_add_to_container }, { "has_timer", l_obj_timer_has }, { "peek_timer", l_obj_timer_peek }, { "stop_timer", l_obj_timer_stop }, { "start_timer", l_obj_timer_start }, { "bury", l_obj_bury }, { NULL, NULL } }; static const luaL_Reg l_obj_meta[] = { { "__gc", l_obj_gc }, { NULL, NULL } }; int l_obj_register(lua_State *L) { /* Table of instance methods (e.g. an_object:isnull()) and static methods (e.g. obj.new("dagger")). */ luaL_newlib(L, l_obj_methods); /* metatable = { __name = "obj", __gc = l_obj_gc } */ luaL_newmetatable(L, "obj"); luaL_setfuncs(L, l_obj_meta, 0); /* metatable.__index points at the object method table. */ lua_pushvalue(L, -2); lua_setfield(L, -2, "__index"); /* Don't let lua code mess with the real metatable. Instead offer a fake one that only contains __gc. */ luaL_newlib(L, l_obj_meta); lua_setfield(L, -2, "__metatable"); /* We don't need the metatable anymore. It's safe in the Lua registry for use by luaL_setmetatable. */ lua_pop(L, 1); /* global obj = the method table we created at the start */ lua_setglobal(L, "obj"); return 0; } /* for 'onefile' processing where end of this file isn't necessarily the end of the source code seen by the compiler */ #undef lobj_is_ok /*nhlobj.c*/