From a bug report, the fallback selection criteria
(used when everything is extinct?) in rndmonnum() was excluding hell-only
monsters when outside of Gehennom, but failed to exclude never-in-hell ones
when inside. [Some of the never-in-hell monsters are Angels, but the rest
are all cold based creatures. That must date to when fire resistance was
required for the hero, which is no longer the case. Should those cold
monsters retain their never-in-hell setting?]
This also fixes a latent copy/paste bug in the unused mons[] definition
of Cerberus (it was the only unique monster which failed to specify G_NOGEN).
1987 lines
51 KiB
C
1987 lines
51 KiB
C
/* SCCS Id: @(#)mkobj.c 3.5 2006/08/23 */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
STATIC_DCL void FDECL(mkbox_cnts,(struct obj *));
|
|
STATIC_DCL void FDECL(obj_timer_checks,(struct obj *, XCHAR_P, XCHAR_P, int));
|
|
STATIC_DCL void FDECL(container_weight, (struct obj *));
|
|
STATIC_DCL struct obj *FDECL(save_mtraits, (struct obj *, struct monst *));
|
|
#ifdef WIZARD
|
|
STATIC_DCL const char *FDECL(where_name, (int));
|
|
STATIC_DCL void FDECL(check_contained, (struct obj *,const char *));
|
|
#endif
|
|
|
|
extern struct obj *thrownobj; /* defined in dothrow.c */
|
|
|
|
/*#define DEBUG_EFFECTS*/ /* show some messages for debugging */
|
|
|
|
struct icp {
|
|
int iprob; /* probability of an item type */
|
|
char iclass; /* item class */
|
|
};
|
|
|
|
const struct icp mkobjprobs[] = {
|
|
{10, WEAPON_CLASS},
|
|
{10, ARMOR_CLASS},
|
|
{20, FOOD_CLASS},
|
|
{ 8, TOOL_CLASS},
|
|
{ 8, GEM_CLASS},
|
|
{16, POTION_CLASS},
|
|
{16, SCROLL_CLASS},
|
|
{ 4, SPBOOK_CLASS},
|
|
{ 4, WAND_CLASS},
|
|
{ 3, RING_CLASS},
|
|
{ 1, AMULET_CLASS}
|
|
};
|
|
|
|
const struct icp boxiprobs[] = {
|
|
{18, GEM_CLASS},
|
|
{15, FOOD_CLASS},
|
|
{18, POTION_CLASS},
|
|
{18, SCROLL_CLASS},
|
|
{12, SPBOOK_CLASS},
|
|
{ 7, COIN_CLASS},
|
|
{ 6, WAND_CLASS},
|
|
{ 5, RING_CLASS},
|
|
{ 1, AMULET_CLASS}
|
|
};
|
|
|
|
#ifdef REINCARNATION
|
|
const struct icp rogueprobs[] = {
|
|
{12, WEAPON_CLASS},
|
|
{12, ARMOR_CLASS},
|
|
{22, FOOD_CLASS},
|
|
{22, POTION_CLASS},
|
|
{22, SCROLL_CLASS},
|
|
{ 5, WAND_CLASS},
|
|
{ 5, RING_CLASS}
|
|
};
|
|
#endif
|
|
|
|
const struct icp hellprobs[] = {
|
|
{20, WEAPON_CLASS},
|
|
{20, ARMOR_CLASS},
|
|
{16, FOOD_CLASS},
|
|
{12, TOOL_CLASS},
|
|
{10, GEM_CLASS},
|
|
{ 1, POTION_CLASS},
|
|
{ 1, SCROLL_CLASS},
|
|
{ 8, WAND_CLASS},
|
|
{ 8, RING_CLASS},
|
|
{ 4, AMULET_CLASS}
|
|
};
|
|
|
|
struct oextra *
|
|
newoextra()
|
|
{
|
|
struct oextra *oextra;
|
|
oextra = (struct oextra *)alloc(sizeof(struct oextra));
|
|
if (oextra)
|
|
(void) memset((genericptr_t)oextra, 0, sizeof(struct oextra));
|
|
return oextra;
|
|
}
|
|
|
|
void
|
|
dealloc_oextra(x)
|
|
struct oextra *x;
|
|
{
|
|
if (x) {
|
|
if (x->oname) free((genericptr_t)x->oname);
|
|
if (x->omonst) free((genericptr_t)x->omonst);
|
|
if (x->omid) free((genericptr_t)x->omid);
|
|
if (x->olong) free((genericptr_t)x->olong);
|
|
if (x->omailcmd) free((genericptr_t)x->omailcmd);
|
|
free((genericptr_t)x);
|
|
}
|
|
}
|
|
|
|
void
|
|
newomonst(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (!otmp->oextra) otmp->oextra = newoextra();
|
|
if (!OMONST(otmp)) {
|
|
OMONST(otmp) = (struct monst *)alloc(sizeof(struct monst));
|
|
(void) memset((genericptr_t) OMONST(otmp), 0, sizeof(struct monst));
|
|
}
|
|
}
|
|
|
|
void
|
|
free_omonst(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (otmp->oextra && OMONST(otmp)) {
|
|
free((genericptr_t) OMONST(otmp));
|
|
OMONST(otmp) = (struct monst *)0;
|
|
}
|
|
}
|
|
|
|
void
|
|
newomid(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (!otmp->oextra) otmp->oextra = newoextra();
|
|
if (!OMID(otmp)) {
|
|
OMID(otmp) = (unsigned *)alloc(sizeof(unsigned));
|
|
(void) memset((genericptr_t) OMID(otmp), 0, sizeof(unsigned));
|
|
}
|
|
}
|
|
|
|
void
|
|
free_omid(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (otmp->oextra && OMID(otmp)) {
|
|
free((genericptr_t) OMID(otmp));
|
|
OMID(otmp) = (unsigned *)0;
|
|
}
|
|
}
|
|
|
|
void
|
|
newolong(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (!otmp->oextra) otmp->oextra = newoextra();
|
|
if (!OLONG(otmp)) {
|
|
OLONG(otmp) = (long *)alloc(sizeof(long));
|
|
(void) memset((genericptr_t) OLONG(otmp), 0, sizeof(long));
|
|
}
|
|
}
|
|
|
|
void
|
|
free_olong(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (otmp->oextra && OLONG(otmp)) {
|
|
free((genericptr_t) OLONG(otmp));
|
|
OLONG(otmp) = (long *)0;
|
|
}
|
|
}
|
|
|
|
void
|
|
new_omailcmd(otmp, response_cmd)
|
|
struct obj *otmp;
|
|
const char *response_cmd;
|
|
{
|
|
unsigned lth = strlen(response_cmd) + 1;
|
|
|
|
if (!otmp->oextra) otmp->oextra = newoextra();
|
|
if (OMAILCMD(otmp)) free_omailcmd(otmp);
|
|
OMAILCMD(otmp) = (char *)alloc(lth);
|
|
Strcpy(OMAILCMD(otmp), response_cmd);
|
|
}
|
|
|
|
void
|
|
free_omailcmd(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
if (otmp->oextra && OMAILCMD(otmp)) {
|
|
free((genericptr_t) OMAILCMD(otmp));
|
|
OMAILCMD(otmp) = (char *)0;
|
|
}
|
|
}
|
|
|
|
struct obj *
|
|
mkobj_at(let, x, y, artif)
|
|
char let;
|
|
int x, y;
|
|
boolean artif;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
otmp = mkobj(let, artif);
|
|
place_object(otmp, x, y);
|
|
return(otmp);
|
|
}
|
|
|
|
struct obj *
|
|
mksobj_at(otyp, x, y, init, artif)
|
|
int otyp, x, y;
|
|
boolean init, artif;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
otmp = mksobj(otyp, init, artif);
|
|
place_object(otmp, x, y);
|
|
return(otmp);
|
|
}
|
|
|
|
struct obj *
|
|
mkobj(oclass, artif)
|
|
char oclass;
|
|
boolean artif;
|
|
{
|
|
int tprob, i, prob = rnd(1000);
|
|
|
|
if(oclass == RANDOM_CLASS) {
|
|
const struct icp *iprobs =
|
|
#ifdef REINCARNATION
|
|
(Is_rogue_level(&u.uz)) ?
|
|
(const struct icp *)rogueprobs :
|
|
#endif
|
|
Inhell ? (const struct icp *)hellprobs :
|
|
(const struct icp *)mkobjprobs;
|
|
|
|
for(tprob = rnd(100);
|
|
(tprob -= iprobs->iprob) > 0;
|
|
iprobs++);
|
|
oclass = iprobs->iclass;
|
|
}
|
|
|
|
i = bases[(int)oclass];
|
|
while((prob -= objects[i].oc_prob) > 0) i++;
|
|
|
|
if(objects[i].oc_class != oclass || !OBJ_NAME(objects[i]))
|
|
panic("probtype error, oclass=%d i=%d", (int) oclass, i);
|
|
|
|
return(mksobj(i, TRUE, artif));
|
|
}
|
|
|
|
STATIC_OVL void
|
|
mkbox_cnts(box)
|
|
struct obj *box;
|
|
{
|
|
register int n;
|
|
register struct obj *otmp;
|
|
|
|
box->cobj = (struct obj *) 0;
|
|
|
|
switch (box->otyp) {
|
|
case ICE_BOX: n = 20; break;
|
|
case CHEST: n = 5; break;
|
|
case LARGE_BOX: n = 3; break;
|
|
case SACK:
|
|
case OILSKIN_SACK:
|
|
/* initial inventory: sack starts out empty */
|
|
if (moves <= 1 && !in_mklev) { n = 0; break; }
|
|
/*else FALLTHRU*/
|
|
case BAG_OF_HOLDING: n = 1; break;
|
|
default: n = 0; break;
|
|
}
|
|
|
|
for (n = rn2(n+1); n > 0; n--) {
|
|
if (box->otyp == ICE_BOX) {
|
|
if (!(otmp = mksobj(CORPSE, TRUE, TRUE))) continue;
|
|
/* Note: setting age to 0 is correct. Age has a different
|
|
* from usual meaning for objects stored in ice boxes. -KAA
|
|
*/
|
|
otmp->age = 0L;
|
|
if (otmp->timed) {
|
|
(void) stop_timer(ROT_CORPSE, obj_to_any(otmp));
|
|
(void) stop_timer(REVIVE_MON, obj_to_any(otmp));
|
|
}
|
|
} else {
|
|
register int tprob;
|
|
const struct icp *iprobs = boxiprobs;
|
|
|
|
for (tprob = rnd(100); (tprob -= iprobs->iprob) > 0; iprobs++)
|
|
;
|
|
if (!(otmp = mkobj(iprobs->iclass, TRUE))) continue;
|
|
|
|
/* handle a couple of special cases */
|
|
if (otmp->oclass == COIN_CLASS) {
|
|
/* 2.5 x level's usual amount; weight adjusted below */
|
|
otmp->quan = (long)(rnd(level_difficulty()+2) * rnd(75));
|
|
otmp->owt = weight(otmp);
|
|
} else while (otmp->otyp == ROCK) {
|
|
otmp->otyp = rnd_class(DILITHIUM_CRYSTAL, LOADSTONE);
|
|
if (otmp->quan > 2L) otmp->quan = 1L;
|
|
otmp->owt = weight(otmp);
|
|
}
|
|
if (box->otyp == BAG_OF_HOLDING) {
|
|
if (Is_mbag(otmp)) {
|
|
otmp->otyp = SACK;
|
|
otmp->spe = 0;
|
|
otmp->owt = weight(otmp);
|
|
} else while (otmp->otyp == WAN_CANCELLATION)
|
|
otmp->otyp = rnd_class(WAN_LIGHT, WAN_LIGHTNING);
|
|
}
|
|
}
|
|
(void) add_to_container(box, otmp);
|
|
}
|
|
}
|
|
|
|
int
|
|
rndmonnum() /* select a random, common monster type */
|
|
{
|
|
register struct permonst *ptr;
|
|
register int i;
|
|
unsigned short excludeflags;
|
|
|
|
/* Plan A: get a level-appropriate common monster */
|
|
ptr = rndmonst();
|
|
if (ptr) return(monsndx(ptr));
|
|
|
|
/* Plan B: get any common monster */
|
|
excludeflags = G_UNIQ | G_NOGEN | (Inhell ? G_NOHELL : G_HELL);
|
|
do {
|
|
i = rn1(SPECIAL_PM - LOW_PM, LOW_PM);
|
|
ptr = &mons[i];
|
|
} while ((ptr->geno & excludeflags) != 0);
|
|
|
|
return(i);
|
|
}
|
|
|
|
void
|
|
copy_oextra(obj2, obj1)
|
|
struct obj *obj2, *obj1;
|
|
{
|
|
if(!obj2 || !obj1 || !obj1->oextra) return;
|
|
|
|
if (!obj2->oextra) obj2->oextra = newoextra();
|
|
if (has_oname(obj1)) oname(obj2, ONAME(obj1));
|
|
if (has_omonst(obj1)) {
|
|
if (!OMONST(obj2)) newomonst(obj2);
|
|
(void)memcpy((genericptr_t)OMONST(obj2),
|
|
(genericptr_t)OMONST(obj1),
|
|
sizeof(struct monst));
|
|
OMONST(obj2)->mextra = (struct mextra *)0;
|
|
OMONST(obj2)->nmon = (struct monst *)0;
|
|
#if 0
|
|
OMONST(obj2)->m_id = context.ident++;
|
|
if (OMONST(obj2)->m_id) /* ident overflowed */
|
|
OMONST(obj2)->m_id = context.ident++;
|
|
#endif
|
|
if (OMONST(obj1)->mextra)
|
|
copy_mextra(OMONST(obj2),OMONST(obj1));
|
|
}
|
|
if (has_omid(obj1)) {
|
|
if (!OMID(obj2)) newomid(obj2);
|
|
(void)memcpy((genericptr_t)OMID(obj2),
|
|
(genericptr_t)OMID(obj1),
|
|
sizeof(unsigned));
|
|
}
|
|
if (has_olong(obj1)) {
|
|
if (!OLONG(obj2)) newolong(obj2);
|
|
(void)memcpy((genericptr_t)OLONG(obj2),
|
|
(genericptr_t)OLONG(obj1),
|
|
sizeof(long));
|
|
}
|
|
if (has_omailcmd(obj1)) {
|
|
new_omailcmd(obj2, OMAILCMD(obj1));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Split obj so that it gets size gets reduced by num. The quantity num is
|
|
* put in the object structure delivered by this call. The returned object
|
|
* has its wornmask cleared and is positioned just following the original
|
|
* in the nobj chain (and nexthere chain when on the floor).
|
|
*/
|
|
struct obj *
|
|
splitobj(obj, num)
|
|
struct obj *obj;
|
|
long num;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
if (obj->cobj || num <= 0L || obj->quan <= num)
|
|
panic("splitobj"); /* can't split containers */
|
|
otmp = newobj();
|
|
*otmp = *obj; /* copies whole structure */
|
|
otmp->oextra = (struct oextra *)0;
|
|
otmp->o_id = context.ident++;
|
|
if (!otmp->o_id) otmp->o_id = context.ident++; /* ident overflowed */
|
|
otmp->timed = 0; /* not timed, yet */
|
|
otmp->lamplit = 0; /* ditto */
|
|
otmp->owornmask = 0L; /* new object isn't worn */
|
|
obj->quan -= num;
|
|
obj->owt = weight(obj);
|
|
otmp->quan = num;
|
|
otmp->owt = weight(otmp); /* -= obj->owt ? */
|
|
obj->nobj = otmp;
|
|
/* Only set nexthere when on the floor, nexthere is also used */
|
|
/* as a back pointer to the container object when contained. */
|
|
if (obj->where == OBJ_FLOOR)
|
|
obj->nexthere = otmp;
|
|
copy_oextra(otmp, obj);
|
|
if (has_omid(otmp)) free_omid(otmp); /* only one association with m_id*/
|
|
if (obj->unpaid) splitbill(obj,otmp);
|
|
if (obj->timed) obj_split_timers(obj, otmp);
|
|
if (obj_sheds_light(obj)) obj_split_light_source(obj, otmp);
|
|
return otmp;
|
|
}
|
|
|
|
/*
|
|
* Insert otmp right after obj in whatever chain(s) it is on. Then extract
|
|
* obj from the chain(s). This function does a literal swap. It is up to
|
|
* the caller to provide a valid context for the swap. When done, obj will
|
|
* still exist, but not on any chain.
|
|
*
|
|
* Note: Don't use use obj_extract_self() -- we are doing an in-place swap,
|
|
* not actually moving something.
|
|
*/
|
|
void
|
|
replace_object(obj, otmp)
|
|
struct obj *obj;
|
|
struct obj *otmp;
|
|
{
|
|
otmp->where = obj->where;
|
|
switch (obj->where) {
|
|
case OBJ_FREE:
|
|
/* do nothing */
|
|
break;
|
|
case OBJ_INVENT:
|
|
otmp->nobj = obj->nobj;
|
|
obj->nobj = otmp;
|
|
extract_nobj(obj, &invent);
|
|
break;
|
|
case OBJ_CONTAINED:
|
|
otmp->nobj = obj->nobj;
|
|
otmp->ocontainer = obj->ocontainer;
|
|
obj->nobj = otmp;
|
|
extract_nobj(obj, &obj->ocontainer->cobj);
|
|
break;
|
|
case OBJ_MINVENT:
|
|
otmp->nobj = obj->nobj;
|
|
otmp->ocarry = obj->ocarry;
|
|
obj->nobj = otmp;
|
|
extract_nobj(obj, &obj->ocarry->minvent);
|
|
break;
|
|
case OBJ_FLOOR:
|
|
otmp->nobj = obj->nobj;
|
|
otmp->nexthere = obj->nexthere;
|
|
otmp->ox = obj->ox;
|
|
otmp->oy = obj->oy;
|
|
obj->nobj = otmp;
|
|
obj->nexthere = otmp;
|
|
extract_nobj(obj, &fobj);
|
|
extract_nexthere(obj, &level.objects[obj->ox][obj->oy]);
|
|
break;
|
|
default:
|
|
panic("replace_object: obj position");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a dummy duplicate to put on shop bill. The duplicate exists
|
|
* only in the billobjs chain. This function is used when a shop object
|
|
* is being altered, and a copy of the original is needed for billing
|
|
* purposes. For example, when eating, where an interruption will yield
|
|
* an object which is different from what it started out as; the "I x"
|
|
* command needs to display the original object.
|
|
*
|
|
* [BUG: The cost might end up being different if item originally had a
|
|
* surcharge but doesn't get one now or vice versa. Having the dummy keep
|
|
* the same o_id value as the original would avoid this; is that viable?
|
|
* (Mustn't give the original itself a new o_id, so can't just swap them.)]
|
|
*
|
|
* The caller is responsible for checking otmp->unpaid and
|
|
* costly_spot(u.ux, u.uy). This function will make otmp no charge.
|
|
*
|
|
* Note that check_unpaid_usage() should be used instead for partial
|
|
* usage of an object.
|
|
*/
|
|
void
|
|
bill_dummy_object(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
register struct obj *dummy;
|
|
|
|
if (otmp->unpaid)
|
|
subfrombill(otmp, shop_keeper(*u.ushops));
|
|
dummy = newobj();
|
|
*dummy = *otmp;
|
|
dummy->oextra = (struct oextra *)0;
|
|
dummy->where = OBJ_FREE;
|
|
dummy->o_id = context.ident++;
|
|
if (!dummy->o_id) dummy->o_id = context.ident++; /* ident overflowed */
|
|
dummy->timed = 0;
|
|
copy_oextra(dummy, otmp);
|
|
if (has_omid(dummy)) free_omid(dummy); /* only one association with m_id*/
|
|
if (Is_candle(dummy)) dummy->lamplit = 0;
|
|
addtobill(dummy, FALSE, TRUE, TRUE);
|
|
/* no_charge is only valid for some locations */
|
|
otmp->no_charge = (otmp->where == OBJ_FLOOR ||
|
|
otmp->where == OBJ_CONTAINED) ? 1 : 0;
|
|
otmp->unpaid = 0;
|
|
return;
|
|
}
|
|
|
|
/* alteration types; must match COST_xxx macros in hack.h */
|
|
static const char * const alteration_verbs[] = {
|
|
"cancel", "drain", "uncharge", "unbless", "uncurse",
|
|
"disenchant", "degrade", "dilute", "erase", "burn",
|
|
"neutralize", "destroy", "splatter", "bite", "open",
|
|
};
|
|
|
|
/* possibly bill for an object which the player has just modified */
|
|
void
|
|
costly_alteration(obj, alter_type)
|
|
struct obj *obj;
|
|
int alter_type;
|
|
{
|
|
xchar ox, oy;
|
|
char objroom;
|
|
boolean set_bknown;
|
|
const char *those, *them, *what;
|
|
struct monst *shkp = 0;
|
|
|
|
if (alter_type < 0 || alter_type >= SIZE(alteration_verbs)) {
|
|
impossible("invalid alteration type (%d)", alter_type);
|
|
alter_type = 0;
|
|
}
|
|
|
|
ox = oy = 0; /* lint suppression */
|
|
objroom = '\0'; /* ditto */
|
|
if (carried(obj) || obj->where == OBJ_FREE) {
|
|
/* OBJ_FREE catches obj_no_longer_held()'s transformation
|
|
of crysknife back into worm tooth; the object has been
|
|
removed from inventory but not necessarily placed at
|
|
its new location yet--the unpaid flag will still be set
|
|
if this item is owned by a shop */
|
|
if (!obj->unpaid) return;
|
|
} else {
|
|
/* this get_obj_location shouldn't fail, but if it does,
|
|
use hero's location */
|
|
if (!get_obj_location(obj, &ox, &oy, CONTAINED_TOO))
|
|
ox = u.ux, oy = u.uy;
|
|
if (!costly_spot(ox, oy)) return;
|
|
objroom = *in_rooms(ox, oy, SHOPBASE);
|
|
/* if no shop cares about it, we're done */
|
|
if (!billable(&shkp, obj, objroom, FALSE)) return;
|
|
}
|
|
|
|
if (obj->quan == 1L)
|
|
those = "that", them = "it";
|
|
else
|
|
those = "those", them = "them";
|
|
|
|
/* when shopkeeper describes the object as being uncursed or unblessed
|
|
hero will know that it is now uncursed; will also make the feedback
|
|
from `I x' after bill_dummy_object() be more specific for this item */
|
|
set_bknown = (alter_type == COST_UNCURS || alter_type == COST_UNBLSS);
|
|
|
|
switch (obj->where) {
|
|
case OBJ_FREE: /* obj_no_longer_held() */
|
|
case OBJ_INVENT:
|
|
if (set_bknown) obj->bknown = 1;
|
|
what = simple_typename(obj->otyp);
|
|
if (obj->quan != 1L) what = makeplural(what);
|
|
verbalize("You %s %s %s, you pay for %s!",
|
|
alteration_verbs[alter_type], those, what, them);
|
|
bill_dummy_object(obj);
|
|
break;
|
|
case OBJ_FLOOR:
|
|
if (set_bknown) obj->bknown = 1;
|
|
if (costly_spot(u.ux, u.uy) && objroom == *u.ushops) {
|
|
verbalize("You %s %s, you pay for %s!",
|
|
alteration_verbs[alter_type], those, them);
|
|
bill_dummy_object(obj);
|
|
} else {
|
|
(void) stolen_value(obj, ox, oy, FALSE, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const char dknowns[] = {
|
|
WAND_CLASS, RING_CLASS, POTION_CLASS, SCROLL_CLASS,
|
|
GEM_CLASS, SPBOOK_CLASS, WEAPON_CLASS, TOOL_CLASS, 0
|
|
};
|
|
|
|
struct obj *
|
|
mksobj(otyp, init, artif)
|
|
int otyp;
|
|
boolean init;
|
|
boolean artif;
|
|
{
|
|
int mndx, tryct;
|
|
struct obj *otmp;
|
|
char let = objects[otyp].oc_class;
|
|
|
|
otmp = newobj();
|
|
*otmp = zeroobj;
|
|
otmp->age = monstermoves;
|
|
otmp->o_id = context.ident++;
|
|
if (!otmp->o_id) otmp->o_id = context.ident++; /* ident overflowed */
|
|
otmp->quan = 1L;
|
|
otmp->oclass = let;
|
|
otmp->otyp = otyp;
|
|
otmp->where = OBJ_FREE;
|
|
otmp->dknown = index(dknowns, let) ? 0 : 1;
|
|
if ((otmp->otyp >= ELVEN_SHIELD && otmp->otyp <= ORCISH_SHIELD) ||
|
|
otmp->otyp == SHIELD_OF_REFLECTION)
|
|
otmp->dknown = 0;
|
|
if (!objects[otmp->otyp].oc_uses_known)
|
|
otmp->known = 1;
|
|
otmp->lknown = 0;
|
|
otmp->cknown = 0;
|
|
#ifdef INVISIBLE_OBJECTS
|
|
otmp->oinvis = !rn2(1250);
|
|
#endif
|
|
if (init) switch (let) {
|
|
case WEAPON_CLASS:
|
|
otmp->quan = is_multigen(otmp) ? (long) rn1(6,6) : 1L;
|
|
if(!rn2(11)) {
|
|
otmp->spe = rne(3);
|
|
otmp->blessed = rn2(2);
|
|
} else if(!rn2(10)) {
|
|
curse(otmp);
|
|
otmp->spe = -rne(3);
|
|
} else blessorcurse(otmp, 10);
|
|
if (is_poisonable(otmp) && !rn2(100))
|
|
otmp->opoisoned = 1;
|
|
|
|
if (artif && !rn2(20))
|
|
otmp = mk_artifact(otmp, (aligntyp)A_NONE);
|
|
break;
|
|
case FOOD_CLASS:
|
|
otmp->oeaten = 0;
|
|
switch(otmp->otyp) {
|
|
case CORPSE:
|
|
/* possibly overridden by mkcorpstat() */
|
|
tryct = 50;
|
|
do otmp->corpsenm = undead_to_corpse(rndmonnum());
|
|
while ((mvitals[otmp->corpsenm].mvflags & G_NOCORPSE) && (--tryct > 0));
|
|
if (tryct == 0) {
|
|
/* perhaps rndmonnum() only wants to make G_NOCORPSE monsters on
|
|
this level; let's create an adventurer's corpse instead, then */
|
|
otmp->corpsenm = PM_HUMAN;
|
|
}
|
|
/* timer set below */
|
|
break;
|
|
case EGG:
|
|
otmp->corpsenm = NON_PM; /* generic egg */
|
|
if (!rn2(3)) for (tryct = 200; tryct > 0; --tryct) {
|
|
mndx = can_be_hatched(rndmonnum());
|
|
if (mndx != NON_PM && !dead_species(mndx, TRUE)) {
|
|
otmp->corpsenm = mndx; /* typed egg */
|
|
break;
|
|
}
|
|
}
|
|
/* timer set below */
|
|
break;
|
|
case TIN:
|
|
otmp->corpsenm = NON_PM; /* empty (so far) */
|
|
if (!rn2(6))
|
|
set_tin_variety(otmp, SPINACH_TIN);
|
|
else for (tryct = 200; tryct > 0; --tryct) {
|
|
mndx = undead_to_corpse(rndmonnum());
|
|
if (mons[mndx].cnutrit &&
|
|
!(mvitals[mndx].mvflags & G_NOCORPSE)) {
|
|
otmp->corpsenm = mndx;
|
|
set_tin_variety(otmp, RANDOM_TIN);
|
|
break;
|
|
}
|
|
}
|
|
blessorcurse(otmp, 10);
|
|
break;
|
|
case SLIME_MOLD:
|
|
otmp->spe = current_fruit;
|
|
break;
|
|
case KELP_FROND:
|
|
otmp->quan = (long) rnd(2);
|
|
break;
|
|
}
|
|
if (otmp->otyp == CORPSE || otmp->otyp == MEAT_RING ||
|
|
otmp->otyp == KELP_FROND) break;
|
|
/* fall into next case */
|
|
|
|
case GEM_CLASS:
|
|
if (otmp->otyp == LOADSTONE) curse(otmp);
|
|
else if (otmp->otyp == ROCK) otmp->quan = (long) rn1(6,6);
|
|
else if (otmp->otyp != LUCKSTONE && !rn2(6)) otmp->quan = 2L;
|
|
else otmp->quan = 1L;
|
|
break;
|
|
case TOOL_CLASS:
|
|
switch(otmp->otyp) {
|
|
case TALLOW_CANDLE:
|
|
case WAX_CANDLE: otmp->spe = 1;
|
|
otmp->age = 20L * /* 400 or 200 */
|
|
(long)objects[otmp->otyp].oc_cost;
|
|
otmp->lamplit = 0;
|
|
otmp->quan = 1L +
|
|
(long)(rn2(2) ? rn2(7) : 0);
|
|
blessorcurse(otmp, 5);
|
|
break;
|
|
case BRASS_LANTERN:
|
|
case OIL_LAMP: otmp->spe = 1;
|
|
otmp->age = (long) rn1(500,1000);
|
|
otmp->lamplit = 0;
|
|
blessorcurse(otmp, 5);
|
|
break;
|
|
case MAGIC_LAMP: otmp->spe = 1;
|
|
otmp->lamplit = 0;
|
|
blessorcurse(otmp, 2);
|
|
break;
|
|
case CHEST:
|
|
case LARGE_BOX: otmp->olocked = !!(rn2(5));
|
|
otmp->otrapped = !(rn2(10));
|
|
case ICE_BOX:
|
|
case SACK:
|
|
case OILSKIN_SACK:
|
|
case BAG_OF_HOLDING: mkbox_cnts(otmp);
|
|
break;
|
|
#ifdef TOURIST
|
|
case EXPENSIVE_CAMERA:
|
|
#endif
|
|
case TINNING_KIT:
|
|
case MAGIC_MARKER: otmp->spe = rn1(70,30);
|
|
break;
|
|
case CAN_OF_GREASE: otmp->spe = rnd(25);
|
|
blessorcurse(otmp, 10);
|
|
break;
|
|
case CRYSTAL_BALL: otmp->spe = rnd(5);
|
|
blessorcurse(otmp, 2);
|
|
break;
|
|
case HORN_OF_PLENTY:
|
|
case BAG_OF_TRICKS: otmp->spe = rnd(20);
|
|
break;
|
|
case FIGURINE: { int tryct2 = 0;
|
|
do
|
|
otmp->corpsenm = rndmonnum();
|
|
while(is_human(&mons[otmp->corpsenm])
|
|
&& tryct2++ < 30);
|
|
blessorcurse(otmp, 4);
|
|
break;
|
|
}
|
|
case BELL_OF_OPENING: otmp->spe = 3;
|
|
break;
|
|
case MAGIC_FLUTE:
|
|
case MAGIC_HARP:
|
|
case FROST_HORN:
|
|
case FIRE_HORN:
|
|
case DRUM_OF_EARTHQUAKE:
|
|
otmp->spe = rn1(5,4);
|
|
break;
|
|
}
|
|
break;
|
|
case AMULET_CLASS:
|
|
if (otmp->otyp == AMULET_OF_YENDOR) context.made_amulet = TRUE;
|
|
if(rn2(10) && (otmp->otyp == AMULET_OF_STRANGULATION ||
|
|
otmp->otyp == AMULET_OF_CHANGE ||
|
|
otmp->otyp == AMULET_OF_RESTFUL_SLEEP)) {
|
|
curse(otmp);
|
|
} else blessorcurse(otmp, 10);
|
|
case VENOM_CLASS:
|
|
case CHAIN_CLASS:
|
|
case BALL_CLASS:
|
|
break;
|
|
case POTION_CLASS:
|
|
if (otmp->otyp == POT_OIL)
|
|
otmp->age = MAX_OIL_IN_FLASK; /* amount of oil */
|
|
/* fall through */
|
|
case SCROLL_CLASS:
|
|
#ifdef MAIL
|
|
if (otmp->otyp != SCR_MAIL)
|
|
#endif
|
|
blessorcurse(otmp, 4);
|
|
break;
|
|
case SPBOOK_CLASS:
|
|
blessorcurse(otmp, 17);
|
|
break;
|
|
case ARMOR_CLASS:
|
|
if(rn2(10) && (otmp->otyp == FUMBLE_BOOTS ||
|
|
otmp->otyp == LEVITATION_BOOTS ||
|
|
otmp->otyp == HELM_OF_OPPOSITE_ALIGNMENT ||
|
|
otmp->otyp == GAUNTLETS_OF_FUMBLING ||
|
|
!rn2(11))) {
|
|
curse(otmp);
|
|
otmp->spe = -rne(3);
|
|
} else if(!rn2(10)) {
|
|
otmp->blessed = rn2(2);
|
|
otmp->spe = rne(3);
|
|
} else blessorcurse(otmp, 10);
|
|
if (artif && !rn2(40))
|
|
otmp = mk_artifact(otmp, (aligntyp)A_NONE);
|
|
/* simulate lacquered armor for samurai */
|
|
if (Role_if(PM_SAMURAI) && otmp->otyp == SPLINT_MAIL &&
|
|
(moves <= 1 || In_quest(&u.uz))) {
|
|
#ifdef UNIXPC
|
|
/* optimizer bitfield bug */
|
|
otmp->oerodeproof = 1;
|
|
otmp->rknown = 1;
|
|
#else
|
|
otmp->oerodeproof = otmp->rknown = 1;
|
|
#endif
|
|
}
|
|
break;
|
|
case WAND_CLASS:
|
|
if(otmp->otyp == WAN_WISHING) otmp->spe = rnd(3); else
|
|
otmp->spe = rn1(5,
|
|
(objects[otmp->otyp].oc_dir == NODIR) ? 11 : 4);
|
|
blessorcurse(otmp, 17);
|
|
otmp->recharged = 0; /* used to control recharging */
|
|
break;
|
|
case RING_CLASS:
|
|
if(objects[otmp->otyp].oc_charged) {
|
|
blessorcurse(otmp, 3);
|
|
if(rn2(10)) {
|
|
if(rn2(10) && bcsign(otmp))
|
|
otmp->spe = bcsign(otmp) * rne(3);
|
|
else otmp->spe = rn2(2) ? rne(3) : -rne(3);
|
|
}
|
|
/* make useless +0 rings much less common */
|
|
if (otmp->spe == 0) otmp->spe = rn2(4) - rn2(3);
|
|
/* negative rings are usually cursed */
|
|
if (otmp->spe < 0 && rn2(5)) curse(otmp);
|
|
} else if(rn2(10) && (otmp->otyp == RIN_TELEPORTATION ||
|
|
otmp->otyp == RIN_POLYMORPH ||
|
|
otmp->otyp == RIN_AGGRAVATE_MONSTER ||
|
|
otmp->otyp == RIN_HUNGER || !rn2(9))) {
|
|
curse(otmp);
|
|
}
|
|
break;
|
|
case ROCK_CLASS:
|
|
switch (otmp->otyp) {
|
|
case STATUE:
|
|
/* possibly overridden by mkcorpstat() */
|
|
otmp->corpsenm = rndmonnum();
|
|
if (!verysmall(&mons[otmp->corpsenm]) &&
|
|
rn2(level_difficulty()/2 + 10) > 10)
|
|
(void) add_to_container(otmp,
|
|
mkobj(SPBOOK_CLASS,FALSE));
|
|
}
|
|
break;
|
|
case COIN_CLASS:
|
|
break; /* do nothing */
|
|
default:
|
|
impossible("impossible mkobj %d, sym '%c'.", otmp->otyp,
|
|
objects[otmp->otyp].oc_class);
|
|
return (struct obj *)0;
|
|
}
|
|
|
|
/* Some things must get done (timers) even if init = 0 */
|
|
switch (otmp->otyp) {
|
|
case CORPSE:
|
|
case EGG:
|
|
case FIGURINE:
|
|
set_corpsenm(otmp, otmp->corpsenm);
|
|
break;
|
|
}
|
|
|
|
/* unique objects may have an associated artifact entry */
|
|
if (objects[otyp].oc_unique && !otmp->oartifact)
|
|
otmp = mk_artifact(otmp, (aligntyp)A_NONE);
|
|
otmp->owt = weight(otmp);
|
|
return(otmp);
|
|
}
|
|
|
|
/*
|
|
* Several areas of the code made direct reassignments
|
|
* to obj->corpsenm. Because some special handling is
|
|
* required in certain cases, place that handling here
|
|
* and call this routine in place of the direct assignment.
|
|
*
|
|
* If the object was a lizard or lichen corpse:
|
|
* - ensure that you don't end up with some
|
|
* other corpse type which has no rot-away timer.
|
|
*
|
|
* If the object was a troll corpse:
|
|
* - ensure that you don't end up with some other
|
|
* corpse type which resurrects from the dead.
|
|
*
|
|
* Re-calculates the weight of figurines and corpses to suit the
|
|
* new species.
|
|
*
|
|
* Existing timeout value for egg hatch is preserved.
|
|
*
|
|
*/
|
|
void
|
|
set_corpsenm(obj, id)
|
|
struct obj *obj;
|
|
int id;
|
|
{
|
|
long when = 0L;
|
|
|
|
if (obj->timed) {
|
|
if (obj->otyp == EGG)
|
|
when = stop_timer(HATCH_EGG, obj_to_any(obj));
|
|
else {
|
|
when = 0L;
|
|
obj_stop_timers(obj); /* corpse or figurine */
|
|
}
|
|
}
|
|
obj->corpsenm = id;
|
|
switch(obj->otyp) {
|
|
case CORPSE:
|
|
start_corpse_timeout(obj);
|
|
obj->owt = weight(obj);
|
|
break;
|
|
case FIGURINE:
|
|
if (obj->corpsenm != NON_PM
|
|
&& !dead_species(obj->corpsenm,TRUE)
|
|
&& (carried(obj) || mcarried(obj)))
|
|
attach_fig_transform_timeout(obj);
|
|
obj->owt = weight(obj);
|
|
break;
|
|
case EGG:
|
|
if (obj->corpsenm != NON_PM
|
|
&& !dead_species(obj->corpsenm,TRUE))
|
|
attach_egg_hatch_timeout(obj, when);
|
|
break;
|
|
default: /* tin, etc. */
|
|
obj->owt = weight(obj);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Start a corpse decay or revive timer.
|
|
* This takes the age of the corpse into consideration as of 3.4.0.
|
|
*/
|
|
void
|
|
start_corpse_timeout(body)
|
|
struct obj *body;
|
|
{
|
|
long when; /* rot away when this old */
|
|
long corpse_age; /* age of corpse */
|
|
int rot_adjust;
|
|
short action;
|
|
|
|
#define TAINT_AGE (50L) /* age when corpses go bad */
|
|
#define TROLL_REVIVE_CHANCE 37 /* 1/37 chance for 50 turns ~ 75% chance */
|
|
#define ROT_AGE (250L) /* age when corpses rot away */
|
|
|
|
/* lizards and lichen don't rot or revive */
|
|
if (body->corpsenm == PM_LIZARD || body->corpsenm == PM_LICHEN) return;
|
|
|
|
action = ROT_CORPSE; /* default action: rot away */
|
|
rot_adjust = in_mklev ? 25 : 10; /* give some variation */
|
|
corpse_age = monstermoves - body->age;
|
|
if (corpse_age > ROT_AGE)
|
|
when = rot_adjust;
|
|
else
|
|
when = ROT_AGE - corpse_age;
|
|
when += (long)(rnz(rot_adjust) - rot_adjust);
|
|
|
|
if (is_rider(&mons[body->corpsenm])) {
|
|
/*
|
|
* Riders always revive. They have a 1/3 chance per turn
|
|
* of reviving after 12 turns. Always revive by 500.
|
|
*/
|
|
action = REVIVE_MON;
|
|
for (when = 12L; when < 500L; when++)
|
|
if (!rn2(3)) break;
|
|
|
|
} else if (mons[body->corpsenm].mlet == S_TROLL && !body->norevive) {
|
|
long age;
|
|
for (age = 2; age <= TAINT_AGE; age++)
|
|
if (!rn2(TROLL_REVIVE_CHANCE)) { /* troll revives */
|
|
action = REVIVE_MON;
|
|
when = age;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (body->norevive) body->norevive = 0;
|
|
(void) start_timer(when, TIMER_OBJECT, action, obj_to_any(body));
|
|
}
|
|
|
|
void
|
|
bless(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
#ifdef GOLDOBJ
|
|
if (otmp->oclass == COIN_CLASS) return;
|
|
#endif
|
|
otmp->cursed = 0;
|
|
otmp->blessed = 1;
|
|
if (carried(otmp) && confers_luck(otmp))
|
|
set_moreluck();
|
|
else if (otmp->otyp == BAG_OF_HOLDING)
|
|
otmp->owt = weight(otmp);
|
|
else if (otmp->otyp == FIGURINE && otmp->timed)
|
|
(void) stop_timer(FIG_TRANSFORM, obj_to_any(otmp));
|
|
return;
|
|
}
|
|
|
|
void
|
|
unbless(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
otmp->blessed = 0;
|
|
if (carried(otmp) && confers_luck(otmp))
|
|
set_moreluck();
|
|
else if (otmp->otyp == BAG_OF_HOLDING)
|
|
otmp->owt = weight(otmp);
|
|
}
|
|
|
|
void
|
|
curse(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
#ifdef GOLDOBJ
|
|
if (otmp->oclass == COIN_CLASS) return;
|
|
#endif
|
|
otmp->blessed = 0;
|
|
otmp->cursed = 1;
|
|
/* welded two-handed weapon interferes with some armor removal */
|
|
if (otmp == uwep && bimanual(uwep)) reset_remarm();
|
|
/* rules at top of wield.c state that twoweapon cannot be done
|
|
with cursed alternate weapon */
|
|
if (otmp == uswapwep && u.twoweap)
|
|
drop_uswapwep();
|
|
/* some cursed items need immediate updating */
|
|
if (carried(otmp) && confers_luck(otmp))
|
|
set_moreluck();
|
|
else if (otmp->otyp == BAG_OF_HOLDING)
|
|
otmp->owt = weight(otmp);
|
|
else if (otmp->otyp == FIGURINE) {
|
|
if (otmp->corpsenm != NON_PM
|
|
&& !dead_species(otmp->corpsenm,TRUE)
|
|
&& (carried(otmp) || mcarried(otmp)))
|
|
attach_fig_transform_timeout(otmp);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void
|
|
uncurse(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
otmp->cursed = 0;
|
|
if (carried(otmp) && confers_luck(otmp))
|
|
set_moreluck();
|
|
else if (otmp->otyp == BAG_OF_HOLDING)
|
|
otmp->owt = weight(otmp);
|
|
else if (otmp->otyp == FIGURINE && otmp->timed)
|
|
(void) stop_timer(FIG_TRANSFORM, obj_to_any(otmp));
|
|
return;
|
|
}
|
|
|
|
void
|
|
blessorcurse(otmp, chance)
|
|
register struct obj *otmp;
|
|
register int chance;
|
|
{
|
|
if(otmp->blessed || otmp->cursed) return;
|
|
|
|
if(!rn2(chance)) {
|
|
if(!rn2(2)) {
|
|
curse(otmp);
|
|
} else {
|
|
bless(otmp);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
int
|
|
bcsign(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
return(!!otmp->blessed - !!otmp->cursed);
|
|
}
|
|
|
|
/*
|
|
* Calculate the weight of the given object. This will recursively follow
|
|
* and calculate the weight of any containers.
|
|
*
|
|
* Note: It is possible to end up with an incorrect weight if some part
|
|
* of the code messes with a contained object and doesn't update the
|
|
* container's weight.
|
|
*/
|
|
int
|
|
weight(obj)
|
|
register struct obj *obj;
|
|
{
|
|
int wt = objects[obj->otyp].oc_weight;
|
|
|
|
if (obj->otyp == LARGE_BOX && obj->spe == 1) /* Schroedinger's Cat */
|
|
wt += mons[PM_HOUSECAT].cwt;
|
|
if (Is_container(obj) || obj->otyp == STATUE) {
|
|
struct obj *contents;
|
|
register int cwt = 0;
|
|
|
|
if (obj->otyp == STATUE && obj->corpsenm >= LOW_PM)
|
|
wt = (int)obj->quan *
|
|
((int)mons[obj->corpsenm].cwt * 3 / 2);
|
|
|
|
for(contents=obj->cobj; contents; contents=contents->nobj)
|
|
cwt += weight(contents);
|
|
/*
|
|
* The weight of bags of holding is calculated as the weight
|
|
* of the bag plus the weight of the bag's contents modified
|
|
* as follows:
|
|
*
|
|
* Bag status Weight of contents
|
|
* ---------- ------------------
|
|
* cursed 2x
|
|
* blessed x/4 + 1
|
|
* otherwise x/2 + 1
|
|
*
|
|
* The macro DELTA_CWT in pickup.c also implements these
|
|
* weight equations.
|
|
*
|
|
* Note: The above checks are performed in the given order.
|
|
* this means that if an object is both blessed and
|
|
* cursed (not supposed to happen), it will be treated
|
|
* as cursed.
|
|
*/
|
|
if (obj->otyp == BAG_OF_HOLDING)
|
|
cwt = obj->cursed ? (cwt * 2) :
|
|
(1 + (cwt / (obj->blessed ? 4 : 2)));
|
|
|
|
return wt + cwt;
|
|
}
|
|
if (obj->otyp == CORPSE && obj->corpsenm >= LOW_PM) {
|
|
long long_wt = obj->quan * (long) mons[obj->corpsenm].cwt;
|
|
|
|
wt = (long_wt > LARGEST_INT) ? LARGEST_INT : (int)long_wt;
|
|
if (obj->oeaten) wt = eaten_stat(wt, obj);
|
|
return wt;
|
|
} else if (obj->oclass == FOOD_CLASS && obj->oeaten) {
|
|
return eaten_stat((int)obj->quan * wt, obj);
|
|
} else if (obj->oclass == COIN_CLASS)
|
|
return (int)((obj->quan + 50L) / 100L);
|
|
else if (obj->otyp == HEAVY_IRON_BALL && obj->owt != 0)
|
|
return((int)(obj->owt)); /* kludge for "very" heavy iron ball */
|
|
return(wt ? wt*(int)obj->quan : ((int)obj->quan + 1)>>1);
|
|
}
|
|
|
|
static int treefruits[] = {APPLE,ORANGE,PEAR,BANANA,EUCALYPTUS_LEAF};
|
|
|
|
struct obj *
|
|
rnd_treefruit_at(x,y)
|
|
int x, y;
|
|
{
|
|
return mksobj_at(treefruits[rn2(SIZE(treefruits))], x, y, TRUE, FALSE);
|
|
}
|
|
|
|
struct obj *
|
|
mkgold(amount, x, y)
|
|
long amount;
|
|
int x, y;
|
|
{
|
|
register struct obj *gold = g_at(x,y);
|
|
|
|
if (amount <= 0L)
|
|
amount = (long)(1 + rnd(level_difficulty()+2) * rnd(30));
|
|
if (gold) {
|
|
gold->quan += amount;
|
|
} else {
|
|
gold = mksobj_at(GOLD_PIECE, x, y, TRUE, FALSE);
|
|
gold->quan = amount;
|
|
}
|
|
gold->owt = weight(gold);
|
|
return (gold);
|
|
}
|
|
|
|
/* return TRUE if the corpse has special timing */
|
|
#define special_corpse(num) (((num) == PM_LIZARD) \
|
|
|| ((num) == PM_LICHEN) \
|
|
|| (is_rider(&mons[num])) \
|
|
|| (mons[num].mlet == S_TROLL))
|
|
|
|
/*
|
|
* OEXTRA note: Passing mtmp causes mtraits to be saved
|
|
* even if ptr passed as well, but ptr is always used for
|
|
* the corpse type (corpsenm). That allows the corpse type
|
|
* to be different from the original monster,
|
|
* i.e. vampire -> human corpse
|
|
* yet still allow restoration of the original monster upon
|
|
* resurrection.
|
|
*/
|
|
struct obj *
|
|
mkcorpstat(objtype, mtmp, ptr, x, y, corpstatflags)
|
|
int objtype; /* CORPSE or STATUE */
|
|
struct monst *mtmp;
|
|
struct permonst *ptr;
|
|
int x, y;
|
|
unsigned corpstatflags;
|
|
{
|
|
register struct obj *otmp;
|
|
boolean init = ((corpstatflags & CORPSTAT_INIT) != 0);
|
|
|
|
if (objtype != CORPSE && objtype != STATUE)
|
|
impossible("making corpstat type %d", objtype);
|
|
if (x == 0 && y == 0) { /* special case - random placement */
|
|
otmp = mksobj(objtype, init, FALSE);
|
|
if (otmp) (void)rloco(otmp);
|
|
} else
|
|
otmp = mksobj_at(objtype, x, y, init, FALSE);
|
|
if (otmp) {
|
|
if (mtmp) {
|
|
struct obj *otmp2;
|
|
|
|
if (!ptr) ptr = mtmp->data;
|
|
/* save_mtraits frees original data pointed to by otmp */
|
|
otmp2 = save_mtraits(otmp, mtmp);
|
|
if (otmp2) otmp = otmp2;
|
|
}
|
|
/* use the corpse or statue produced by mksobj() as-is
|
|
unless `ptr' is non-null */
|
|
if (ptr) {
|
|
int old_corpsenm = otmp->corpsenm;
|
|
|
|
otmp->corpsenm = monsndx(ptr);
|
|
otmp->owt = weight(otmp);
|
|
if (otmp->otyp == CORPSE &&
|
|
(special_corpse(old_corpsenm) ||
|
|
special_corpse(otmp->corpsenm))) {
|
|
obj_stop_timers(otmp);
|
|
start_corpse_timeout(otmp);
|
|
}
|
|
}
|
|
}
|
|
return(otmp);
|
|
}
|
|
|
|
/*
|
|
* Return the type of monster that this corpse will
|
|
* revive as, even if it has a monster structure
|
|
* attached to it. In that case, you can't just
|
|
* use obj->corpsenm, because the stored monster
|
|
* type can, and often is, different.
|
|
* The return value is an index into mons[].
|
|
*/
|
|
int
|
|
corpse_revive_type(obj)
|
|
struct obj *obj;
|
|
{
|
|
int revivetype;
|
|
struct monst *mtmp;
|
|
if (has_omonst(obj) &&
|
|
((mtmp = get_mtraits(obj, FALSE)) != (struct monst *)0)) {
|
|
/* mtmp is a temporary pointer to a monster's stored
|
|
attributes, not a real monster */
|
|
revivetype = mtmp->mnum;
|
|
} else
|
|
revivetype = obj->corpsenm;
|
|
return revivetype;
|
|
}
|
|
|
|
/*
|
|
* Attach a monster id to an object, to provide
|
|
* a lasting association between the two.
|
|
*/
|
|
struct obj *
|
|
obj_attach_mid(obj, mid)
|
|
struct obj *obj;
|
|
unsigned mid;
|
|
{
|
|
if (!mid || !obj) return (struct obj *)0;
|
|
newomid(obj);
|
|
*OMID(obj) = mid;
|
|
return obj;
|
|
}
|
|
|
|
static struct obj *
|
|
save_mtraits(obj, mtmp)
|
|
struct obj *obj;
|
|
struct monst *mtmp;
|
|
{
|
|
if (mtmp->ispriest) forget_temple_entry(mtmp); /* EPRI() */
|
|
if (!has_omonst(obj)) newomonst(obj);
|
|
if (has_omonst(obj)) {
|
|
struct monst *mtmp2 = OMONST(obj);
|
|
*mtmp2 = *mtmp;
|
|
mtmp2->mextra = (struct mextra *)0;
|
|
if (mtmp->data) mtmp2->mnum = monsndx(mtmp->data);
|
|
/* invalidate pointers */
|
|
/* m_id is needed to know if this is a revived quest leader */
|
|
/* but m_id must be cleared when loading bones */
|
|
mtmp2->nmon = (struct monst *)0;
|
|
mtmp2->data = (struct permonst *)0;
|
|
mtmp2->minvent = (struct obj *)0;
|
|
if (mtmp->mextra) copy_mextra(mtmp2, mtmp);
|
|
#ifndef GOLDOBJ
|
|
/* not a pointer but is discarded along with minvent */
|
|
mtmp2->mgold = 0L;
|
|
#endif
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/* returns a pointer to a new monst structure based on
|
|
* the one contained within the obj.
|
|
*/
|
|
struct monst *
|
|
get_mtraits(obj, copyof)
|
|
struct obj *obj;
|
|
boolean copyof;
|
|
{
|
|
struct monst *mtmp = (struct monst *)0;
|
|
struct monst *mnew = (struct monst *)0;
|
|
|
|
if (has_omonst(obj)) mtmp = OMONST(obj);
|
|
if (mtmp) {
|
|
if (copyof) {
|
|
mnew = newmonst();
|
|
*mnew = *mtmp;
|
|
mnew->mextra = (struct mextra *)0;
|
|
if (mtmp->mextra) copy_mextra(mnew, mtmp);
|
|
} else {
|
|
/* Never insert this returned pointer into mon chains! */
|
|
mnew = mtmp;
|
|
}
|
|
}
|
|
return mnew;
|
|
}
|
|
|
|
/* make an object named after someone listed in the scoreboard file */
|
|
struct obj *
|
|
mk_tt_object(objtype, x, y)
|
|
int objtype; /* CORPSE or STATUE */
|
|
register int x, y;
|
|
{
|
|
register struct obj *otmp, *otmp2;
|
|
boolean initialize_it;
|
|
|
|
/* player statues never contain books */
|
|
initialize_it = (objtype != STATUE);
|
|
if ((otmp = mksobj_at(objtype, x, y, initialize_it, FALSE)) != 0) {
|
|
/* tt_oname will return null if the scoreboard is empty */
|
|
if ((otmp2 = tt_oname(otmp)) != 0) otmp = otmp2;
|
|
}
|
|
return(otmp);
|
|
}
|
|
|
|
/* make a new corpse or statue, uninitialized if a statue (i.e. no books) */
|
|
struct obj *
|
|
mk_named_object(objtype, ptr, x, y, nm)
|
|
int objtype; /* CORPSE or STATUE */
|
|
struct permonst *ptr;
|
|
int x, y;
|
|
const char *nm;
|
|
{
|
|
struct obj *otmp;
|
|
unsigned corpstatflags = (objtype != STATUE) ?
|
|
CORPSTAT_INIT : CORPSTAT_NONE;
|
|
|
|
otmp = mkcorpstat(objtype, (struct monst *)0, ptr,
|
|
x, y, corpstatflags);
|
|
if (nm)
|
|
otmp = oname(otmp, nm);
|
|
return(otmp);
|
|
}
|
|
|
|
boolean
|
|
is_flammable(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
int otyp = otmp->otyp;
|
|
int omat = objects[otyp].oc_material;
|
|
|
|
/* Candles can be burned, but they're not flammable in the sense that
|
|
* they can't get fire damage and it makes no sense for them to be
|
|
* fireproofed.
|
|
*/
|
|
if (Is_candle(otmp))
|
|
return FALSE;
|
|
|
|
if (objects[otyp].oc_oprop == FIRE_RES || otyp == WAN_FIRE)
|
|
return FALSE;
|
|
|
|
return((boolean)((omat <= WOOD && omat != LIQUID) || omat == PLASTIC));
|
|
}
|
|
|
|
boolean
|
|
is_rottable(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
int otyp = otmp->otyp;
|
|
|
|
return((boolean)(objects[otyp].oc_material <= WOOD &&
|
|
objects[otyp].oc_material != LIQUID));
|
|
}
|
|
|
|
/*
|
|
* These routines maintain the single-linked lists headed in level.objects[][]
|
|
* and threaded through the nexthere fields in the object-instance structure.
|
|
*/
|
|
|
|
/* put the object at the given location */
|
|
void
|
|
place_object(otmp, x, y)
|
|
register struct obj *otmp;
|
|
int x, y;
|
|
{
|
|
register struct obj *otmp2 = level.objects[x][y];
|
|
|
|
if (otmp->where != OBJ_FREE)
|
|
panic("place_object: obj not free");
|
|
|
|
obj_no_longer_held(otmp);
|
|
/* (could bypass this vision update if there is already a boulder here) */
|
|
if (otmp->otyp == BOULDER) block_point(x,y); /* vision */
|
|
|
|
/* obj goes under boulders */
|
|
if (otmp2 && (otmp2->otyp == BOULDER)) {
|
|
otmp->nexthere = otmp2->nexthere;
|
|
otmp2->nexthere = otmp;
|
|
} else {
|
|
otmp->nexthere = otmp2;
|
|
level.objects[x][y] = otmp;
|
|
}
|
|
|
|
/* set the new object's location */
|
|
otmp->ox = x;
|
|
otmp->oy = y;
|
|
|
|
otmp->where = OBJ_FLOOR;
|
|
|
|
/* add to floor chain */
|
|
otmp->nobj = fobj;
|
|
fobj = otmp;
|
|
if (otmp->timed) obj_timer_checks(otmp, x, y, 0);
|
|
}
|
|
|
|
#define ROT_ICE_ADJUSTMENT 2 /* rotting on ice takes 2 times as long */
|
|
|
|
/* If ice was affecting any objects correct that now
|
|
* Also used for starting ice effects too. [zap.c]
|
|
*/
|
|
void
|
|
obj_ice_effects(x, y, do_buried)
|
|
int x, y;
|
|
boolean do_buried;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere) {
|
|
if (otmp->timed) obj_timer_checks(otmp, x, y, 0);
|
|
}
|
|
if (do_buried) {
|
|
for (otmp = level.buriedobjlist; otmp; otmp = otmp->nobj) {
|
|
if (otmp->ox == x && otmp->oy == y) {
|
|
if (otmp->timed) obj_timer_checks(otmp, x, y, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns an obj->age for a corpse object on ice, that would be the
|
|
* actual obj->age if the corpse had just been lifted from the ice.
|
|
* This is useful when just using obj->age in a check or calculation because
|
|
* rot timers pertaining to the object don't have to be stopped and
|
|
* restarted etc.
|
|
*/
|
|
long
|
|
peek_at_iced_corpse_age(otmp)
|
|
struct obj *otmp;
|
|
{
|
|
long age, retval = otmp->age;
|
|
|
|
if (otmp->otyp == CORPSE && otmp->on_ice) {
|
|
/* Adjust the age; must be same as obj_timer_checks() for off ice*/
|
|
age = monstermoves - otmp->age;
|
|
retval = otmp->age + (age / ROT_ICE_ADJUSTMENT);
|
|
#ifdef DEBUG_EFFECTS
|
|
pline_The("%s age has ice modifications:otmp->age = %ld, returning %ld.",
|
|
s_suffix(doname(otmp)),otmp->age, retval);
|
|
pline("Effective age of corpse: %ld.",
|
|
monstermoves - retval);
|
|
#endif
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
STATIC_OVL void
|
|
obj_timer_checks(otmp, x, y, force)
|
|
struct obj *otmp;
|
|
xchar x, y;
|
|
int force; /* 0 = no force so do checks, <0 = force off, >0 force on */
|
|
{
|
|
long tleft = 0L;
|
|
short action = ROT_CORPSE;
|
|
boolean restart_timer = FALSE;
|
|
boolean on_floor = (otmp->where == OBJ_FLOOR);
|
|
boolean buried = (otmp->where == OBJ_BURIED);
|
|
|
|
/* Check for corpses just placed on or in ice */
|
|
if (otmp->otyp == CORPSE && (on_floor || buried) && is_ice(x,y)) {
|
|
tleft = stop_timer(action, obj_to_any(otmp));
|
|
if (tleft == 0L) {
|
|
action = REVIVE_MON;
|
|
tleft = stop_timer(action, obj_to_any(otmp));
|
|
}
|
|
if (tleft != 0L) {
|
|
long age;
|
|
|
|
/* mark the corpse as being on ice */
|
|
otmp->on_ice = 1;
|
|
#ifdef DEBUG_EFFECTS
|
|
pline("%s is now on ice at %d,%d.", The(xname(otmp)),x,y);
|
|
#endif
|
|
/* Adjust the time remaining */
|
|
tleft *= ROT_ICE_ADJUSTMENT;
|
|
restart_timer = TRUE;
|
|
/* Adjust the age; must be same as in obj_ice_age() */
|
|
age = monstermoves - otmp->age;
|
|
otmp->age = monstermoves - (age * ROT_ICE_ADJUSTMENT);
|
|
}
|
|
}
|
|
/* Check for corpses coming off ice */
|
|
else if ((force < 0) ||
|
|
(otmp->otyp == CORPSE && otmp->on_ice &&
|
|
((on_floor && !is_ice(x,y)) || !on_floor))) {
|
|
tleft = stop_timer(action, obj_to_any(otmp));
|
|
if (tleft == 0L) {
|
|
action = REVIVE_MON;
|
|
tleft = stop_timer(action, obj_to_any(otmp));
|
|
}
|
|
if (tleft != 0L) {
|
|
long age;
|
|
|
|
otmp->on_ice = 0;
|
|
#ifdef DEBUG_EFFECTS
|
|
pline("%s is no longer on ice at %d,%d.", The(xname(otmp)),x,y);
|
|
#endif
|
|
/* Adjust the remaining time */
|
|
tleft /= ROT_ICE_ADJUSTMENT;
|
|
restart_timer = TRUE;
|
|
/* Adjust the age */
|
|
age = monstermoves - otmp->age;
|
|
otmp->age = otmp->age + (age / ROT_ICE_ADJUSTMENT);
|
|
}
|
|
}
|
|
/* now re-start the timer with the appropriate modifications */
|
|
if (restart_timer)
|
|
(void) start_timer(tleft, TIMER_OBJECT, action, obj_to_any(otmp));
|
|
}
|
|
|
|
#undef ROT_ICE_ADJUSTMENT
|
|
|
|
void
|
|
remove_object(otmp)
|
|
register struct obj *otmp;
|
|
{
|
|
xchar x = otmp->ox;
|
|
xchar y = otmp->oy;
|
|
|
|
if (otmp->where != OBJ_FLOOR)
|
|
panic("remove_object: obj not on floor");
|
|
extract_nexthere(otmp, &level.objects[x][y]);
|
|
extract_nobj(otmp, &fobj);
|
|
/* update vision iff this was the only boulder at its spot */
|
|
if (otmp->otyp == BOULDER && !sobj_at(BOULDER, x, y))
|
|
unblock_point(x,y); /* vision */
|
|
if (otmp->timed) obj_timer_checks(otmp,x,y,0);
|
|
}
|
|
|
|
/* throw away all of a monster's inventory */
|
|
void
|
|
discard_minvent(mtmp)
|
|
struct monst *mtmp;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
while ((otmp = mtmp->minvent) != 0) {
|
|
obj_extract_self(otmp);
|
|
obfree(otmp, (struct obj *)0); /* dealloc_obj() isn't sufficient */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Free obj from whatever list it is on in preperation of deleting it or
|
|
* moving it elsewhere. This will perform all high-level consequences
|
|
* involved with removing the item. E.g. if the object is in the hero's
|
|
* inventory and confers heat resistance, the hero will lose it.
|
|
*
|
|
* Object positions:
|
|
* OBJ_FREE not on any list
|
|
* OBJ_FLOOR fobj, level.locations[][] chains (use remove_object)
|
|
* OBJ_CONTAINED cobj chain of container object
|
|
* OBJ_INVENT hero's invent chain (use freeinv)
|
|
* OBJ_MINVENT monster's invent chain
|
|
* OBJ_MIGRATING migrating chain
|
|
* OBJ_BURIED level.buriedobjs chain
|
|
* OBJ_ONBILL on billobjs chain
|
|
*/
|
|
void
|
|
obj_extract_self(obj)
|
|
struct obj *obj;
|
|
{
|
|
switch (obj->where) {
|
|
case OBJ_FREE:
|
|
break;
|
|
case OBJ_FLOOR:
|
|
remove_object(obj);
|
|
break;
|
|
case OBJ_CONTAINED:
|
|
extract_nobj(obj, &obj->ocontainer->cobj);
|
|
container_weight(obj->ocontainer);
|
|
break;
|
|
case OBJ_INVENT:
|
|
freeinv(obj);
|
|
break;
|
|
case OBJ_MINVENT:
|
|
extract_nobj(obj, &obj->ocarry->minvent);
|
|
break;
|
|
case OBJ_MIGRATING:
|
|
extract_nobj(obj, &migrating_objs);
|
|
break;
|
|
case OBJ_BURIED:
|
|
extract_nobj(obj, &level.buriedobjlist);
|
|
break;
|
|
case OBJ_ONBILL:
|
|
extract_nobj(obj, &billobjs);
|
|
break;
|
|
default:
|
|
panic("obj_extract_self");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Extract the given object from the chain, following nobj chain. */
|
|
void
|
|
extract_nobj(obj, head_ptr)
|
|
struct obj *obj, **head_ptr;
|
|
{
|
|
struct obj *curr, *prev;
|
|
|
|
curr = *head_ptr;
|
|
for (prev = (struct obj *) 0; curr; prev = curr, curr = curr->nobj) {
|
|
if (curr == obj) {
|
|
if (prev)
|
|
prev->nobj = curr->nobj;
|
|
else
|
|
*head_ptr = curr->nobj;
|
|
break;
|
|
}
|
|
}
|
|
if (!curr) panic("extract_nobj: object lost");
|
|
obj->where = OBJ_FREE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Extract the given object from the chain, following nexthere chain.
|
|
*
|
|
* This does not set obj->where, this function is expected to be called
|
|
* in tandem with extract_nobj, which does set it.
|
|
*/
|
|
void
|
|
extract_nexthere(obj, head_ptr)
|
|
struct obj *obj, **head_ptr;
|
|
{
|
|
struct obj *curr, *prev;
|
|
|
|
curr = *head_ptr;
|
|
for (prev = (struct obj *) 0; curr; prev = curr, curr = curr->nexthere) {
|
|
if (curr == obj) {
|
|
if (prev)
|
|
prev->nexthere = curr->nexthere;
|
|
else
|
|
*head_ptr = curr->nexthere;
|
|
break;
|
|
}
|
|
}
|
|
if (!curr) panic("extract_nexthere: object lost");
|
|
}
|
|
|
|
|
|
/*
|
|
* Add obj to mon's inventory. If obj is able to merge with something already
|
|
* in the inventory, then the passed obj is deleted and 1 is returned.
|
|
* Otherwise 0 is returned.
|
|
*/
|
|
int
|
|
add_to_minv(mon, obj)
|
|
struct monst *mon;
|
|
struct obj *obj;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
if (obj->where != OBJ_FREE)
|
|
panic("add_to_minv: obj not free");
|
|
|
|
/* merge if possible */
|
|
for (otmp = mon->minvent; otmp; otmp = otmp->nobj)
|
|
if (merged(&otmp, &obj))
|
|
return 1; /* obj merged and then free'd */
|
|
/* else insert; don't bother forcing it to end of chain */
|
|
obj->where = OBJ_MINVENT;
|
|
obj->ocarry = mon;
|
|
obj->nobj = mon->minvent;
|
|
mon->minvent = obj;
|
|
return 0; /* obj on mon's inventory chain */
|
|
}
|
|
|
|
/*
|
|
* Add obj to container, make sure obj is "free". Returns (merged) obj.
|
|
* The input obj may be deleted in the process.
|
|
*/
|
|
struct obj *
|
|
add_to_container(container, obj)
|
|
struct obj *container, *obj;
|
|
{
|
|
struct obj *otmp;
|
|
|
|
if (obj->where != OBJ_FREE)
|
|
panic("add_to_container: obj not free");
|
|
if (container->where != OBJ_INVENT && container->where != OBJ_MINVENT)
|
|
obj_no_longer_held(obj);
|
|
|
|
/* merge if possible */
|
|
for (otmp = container->cobj; otmp; otmp = otmp->nobj)
|
|
if (merged(&otmp, &obj)) return (otmp);
|
|
|
|
obj->where = OBJ_CONTAINED;
|
|
obj->ocontainer = container;
|
|
obj->nobj = container->cobj;
|
|
container->cobj = obj;
|
|
return (obj);
|
|
}
|
|
|
|
void
|
|
add_to_migration(obj)
|
|
struct obj *obj;
|
|
{
|
|
if (obj->where != OBJ_FREE)
|
|
panic("add_to_migration: obj not free");
|
|
|
|
obj->where = OBJ_MIGRATING;
|
|
obj->nobj = migrating_objs;
|
|
migrating_objs = obj;
|
|
}
|
|
|
|
void
|
|
add_to_buried(obj)
|
|
struct obj *obj;
|
|
{
|
|
if (obj->where != OBJ_FREE)
|
|
panic("add_to_buried: obj not free");
|
|
|
|
obj->where = OBJ_BURIED;
|
|
obj->nobj = level.buriedobjlist;
|
|
level.buriedobjlist = obj;
|
|
}
|
|
|
|
/* Recalculate the weight of this container and all of _its_ containers. */
|
|
STATIC_OVL void
|
|
container_weight(container)
|
|
struct obj *container;
|
|
{
|
|
container->owt = weight(container);
|
|
if (container->where == OBJ_CONTAINED)
|
|
container_weight(container->ocontainer);
|
|
/*
|
|
else if (container->where == OBJ_INVENT)
|
|
recalculate load delay here ???
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Deallocate the object. _All_ objects should be run through here for
|
|
* them to be deallocated.
|
|
*/
|
|
void
|
|
dealloc_obj(obj)
|
|
struct obj *obj;
|
|
{
|
|
if (obj->where != OBJ_FREE)
|
|
panic("dealloc_obj: obj not free");
|
|
|
|
/* free up any timers attached to the object */
|
|
if (obj->timed)
|
|
obj_stop_timers(obj);
|
|
|
|
/*
|
|
* Free up any light sources attached to the object.
|
|
*
|
|
* We may want to just call del_light_source() without any
|
|
* checks (requires a code change there). Otherwise this
|
|
* list must track all objects that can have a light source
|
|
* attached to it (and also requires lamplit to be set).
|
|
*/
|
|
if (obj_sheds_light(obj))
|
|
del_light_source(LS_OBJECT, obj_to_any(obj));
|
|
|
|
if (obj == thrownobj) thrownobj = (struct obj*)0;
|
|
|
|
if (obj->oextra) dealloc_oextra(obj->oextra);
|
|
free((genericptr_t) obj);
|
|
}
|
|
|
|
/* create an object from a horn of plenty; mirrors bagotricks(makemon.c) */
|
|
int
|
|
hornoplenty(horn, tipping)
|
|
struct obj *horn;
|
|
boolean tipping; /* caller emptying entire contents; affects shop handling */
|
|
{
|
|
int objcount = 0;
|
|
|
|
if (!horn || horn->otyp != HORN_OF_PLENTY) {
|
|
impossible("bad horn o' plenty");
|
|
} else if (horn->spe < 1) {
|
|
pline(nothing_happens);
|
|
} else {
|
|
struct obj *obj;
|
|
const char *what;
|
|
|
|
consume_obj_charge(horn, !tipping);
|
|
if (!rn2(13)) {
|
|
obj = mkobj(POTION_CLASS, FALSE);
|
|
if (objects[obj->otyp].oc_magic) do {
|
|
obj->otyp = rnd_class(POT_BOOZE, POT_WATER);
|
|
} while (obj->otyp == POT_SICKNESS);
|
|
what = (obj->quan > 1L) ? "Some potions" : "A potion";
|
|
} else {
|
|
obj = mkobj(FOOD_CLASS, FALSE);
|
|
if (obj->otyp == FOOD_RATION && !rn2(7))
|
|
obj->otyp = LUMP_OF_ROYAL_JELLY;
|
|
what = "Some food";
|
|
}
|
|
++objcount;
|
|
pline("%s %s out.", what, vtense(what, "spill"));
|
|
obj->blessed = horn->blessed;
|
|
obj->cursed = horn->cursed;
|
|
obj->owt = weight(obj);
|
|
if (!tipping) {
|
|
obj = hold_another_object(obj, u.uswallow ?
|
|
"Oops! %s out of your reach!" :
|
|
(Is_airlevel(&u.uz) ||
|
|
Is_waterlevel(&u.uz) ||
|
|
levl[u.ux][u.uy].typ < IRONBARS ||
|
|
levl[u.ux][u.uy].typ >= ICE) ?
|
|
"Oops! %s away from you!" :
|
|
"Oops! %s to the floor!",
|
|
The(aobjnam(obj, "slip")),
|
|
(const char *)0);
|
|
} else {
|
|
/* assumes this is taking place at hero's location */
|
|
if (!can_reach_floor(TRUE)) {
|
|
hitfloor(obj); /* does altar check, message, drop */
|
|
} else {
|
|
if (IS_ALTAR(levl[u.ux][u.uy].typ))
|
|
doaltarobj(obj); /* does its own drop message */
|
|
else
|
|
pline("%s %s to the %s.", Doname2(obj),
|
|
otense(obj, "drop"), surface(u.ux, u.uy));
|
|
dropy(obj);
|
|
}
|
|
}
|
|
if (horn->dknown) makeknown(HORN_OF_PLENTY);
|
|
}
|
|
return objcount;
|
|
}
|
|
|
|
#ifdef WIZARD
|
|
/* Check all object lists for consistency. */
|
|
void
|
|
obj_sanity_check()
|
|
{
|
|
int x, y;
|
|
struct obj *obj;
|
|
struct monst *mon;
|
|
const char *mesg;
|
|
|
|
mesg = "fobj sanity";
|
|
for (obj = fobj; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_FLOOR) {
|
|
pline("%s obj %s %s@(%d,%d): %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where),
|
|
obj->ox, obj->oy, doname(obj));
|
|
}
|
|
check_contained(obj, mesg);
|
|
}
|
|
|
|
mesg = "location sanity";
|
|
for (x = 0; x < COLNO; x++)
|
|
for (y = 0; y < ROWNO; y++)
|
|
for (obj = level.objects[x][y]; obj; obj = obj->nexthere)
|
|
if (obj->where != OBJ_FLOOR) {
|
|
pline("%s obj %s %s@(%d,%d): %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where),
|
|
obj->ox, obj->oy, doname(obj));
|
|
}
|
|
|
|
mesg = "invent sanity";
|
|
for (obj = invent; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_INVENT) {
|
|
pline("%s obj %s %s: %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where), doname(obj));
|
|
}
|
|
check_contained(obj, mesg);
|
|
}
|
|
|
|
mesg = "migrating sanity";
|
|
for (obj = migrating_objs; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_MIGRATING) {
|
|
pline("%s obj %s %s: %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where), doname(obj));
|
|
}
|
|
check_contained(obj, mesg);
|
|
}
|
|
|
|
mesg = "buried sanity";
|
|
for (obj = level.buriedobjlist; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_BURIED) {
|
|
pline("%s obj %s %s: %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where), doname(obj));
|
|
}
|
|
check_contained(obj, mesg);
|
|
}
|
|
|
|
mesg = "bill sanity";
|
|
for (obj = billobjs; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_ONBILL) {
|
|
pline("%s obj %s %s: %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where), doname(obj));
|
|
}
|
|
/* shouldn't be a full container on the bill */
|
|
if (obj->cobj) {
|
|
pline("%s obj %s contains %s! %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
something, doname(obj));
|
|
}
|
|
}
|
|
|
|
mesg = "minvent sanity";
|
|
for (mon = fmon; mon; mon = mon->nmon)
|
|
for (obj = mon->minvent; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_MINVENT) {
|
|
pline("%s obj %s %s: %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where), doname(obj));
|
|
}
|
|
if (obj->ocarry != mon) {
|
|
pline("%s obj %s (%s) not held by mon %s (%s)\n", mesg,
|
|
fmt_ptr((genericptr_t)obj), doname(obj),
|
|
fmt_ptr((genericptr_t)mon), mon_nam(mon));
|
|
}
|
|
check_contained(obj, mesg);
|
|
}
|
|
}
|
|
|
|
/* This must stay consistent with the defines in obj.h. */
|
|
static const char *obj_state_names[NOBJ_STATES] = {
|
|
"free", "floor", "contained", "invent",
|
|
"minvent", "migrating", "buried", "onbill"
|
|
};
|
|
|
|
STATIC_OVL const char *
|
|
where_name(where)
|
|
int where;
|
|
{
|
|
return (where<0 || where>=NOBJ_STATES) ? "unknown" : obj_state_names[where];
|
|
}
|
|
|
|
/* obj sanity check: check objs contained by container */
|
|
STATIC_OVL void
|
|
check_contained(container, mesg)
|
|
struct obj *container;
|
|
const char *mesg;
|
|
{
|
|
struct obj *obj;
|
|
|
|
for (obj = container->cobj; obj; obj = obj->nobj) {
|
|
if (obj->where != OBJ_CONTAINED)
|
|
pline("contained %s obj %s: %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
where_name(obj->where));
|
|
else if (obj->ocontainer != container)
|
|
pline("%s obj %s not in container %s\n", mesg,
|
|
fmt_ptr((genericptr_t)obj),
|
|
fmt_ptr((genericptr_t)container));
|
|
}
|
|
}
|
|
#endif /* WIZARD */
|
|
|
|
/*mkobj.c*/
|