Issue reported by Umbire: if a mind flayer got turned to stone by hitting a hero who is polymorphed into a cockatrice and the first tentacle drain missed but a subsequent one hit, any remaining ones would keep being applied even though the mind flayer was dead. This works but doesn't feel right to me. A more substantial change to mhitm_ad_drin() didn't work as expected so I've settled for this. Fixes #1378
3960 lines
138 KiB
C
3960 lines
138 KiB
C
/* NetHack 3.7 eat.c $NHDT-Date: 1740534854 2025/02/25 17:54:14 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.344 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/*-Copyright (c) Robert Patrick Rankin, 2012. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
staticfn int eatmdone(void);
|
|
staticfn int eatfood(void);
|
|
staticfn struct obj *costly_tin(int);
|
|
staticfn int opentin(void);
|
|
staticfn int unfaint(void);
|
|
|
|
staticfn const char *food_xname(struct obj *, boolean);
|
|
staticfn void choke(struct obj *);
|
|
staticfn void recalc_wt(void);
|
|
staticfn int adj_victual_nutrition(void);
|
|
staticfn struct obj *touchfood(struct obj *);
|
|
staticfn void do_reset_eat(void);
|
|
staticfn void done_eating(boolean);
|
|
staticfn void cprefx(int);
|
|
staticfn boolean temp_givit(int, struct permonst *);
|
|
staticfn void givit(int, struct permonst *);
|
|
staticfn void eye_of_newt_buzz(void);
|
|
staticfn void cpostfx(int);
|
|
staticfn void use_up_tin(struct obj *) NONNULLARG1;
|
|
staticfn void consume_tin(const char *);
|
|
staticfn void start_tin(struct obj *);
|
|
staticfn int eatcorpse(struct obj *);
|
|
staticfn void start_eating(struct obj *, boolean);
|
|
staticfn void garlic_breath(struct monst *);
|
|
staticfn boolean fprefx(struct obj *);
|
|
staticfn void fpostfx(struct obj *);
|
|
staticfn int bite(void);
|
|
staticfn int edibility_prompts(struct obj *);
|
|
staticfn int doeat_nonfood(struct obj *);
|
|
staticfn int tinopen_ok(struct obj *);
|
|
staticfn int rottenfood(struct obj *);
|
|
staticfn void eatspecial(void);
|
|
staticfn int bounded_increase(int, int, int);
|
|
staticfn void accessory_has_effect(struct obj *);
|
|
staticfn void eataccessory(struct obj *);
|
|
staticfn const char *foodword(struct obj *);
|
|
staticfn int tin_variety(struct obj *, boolean);
|
|
staticfn boolean maybe_cannibal(int, boolean);
|
|
staticfn int eat_ok(struct obj *);
|
|
staticfn int offer_ok(struct obj *);
|
|
staticfn int tin_ok(struct obj *);
|
|
|
|
/* also used to see if you're allowed to eat cats and dogs */
|
|
#define CANNIBAL_ALLOWED() (Role_if(PM_CAVE_DWELLER) || Race_if(PM_ORC))
|
|
|
|
/* Rider corpses are treated as non-rotting so that attempting to eat one
|
|
will be sure to reach the stage of eating where that meal is fatal;
|
|
acid blob corpses eventually rot away to nothing but before that happens
|
|
they can be sacrificed regardless of age which implies that they never
|
|
become rotten */
|
|
#define nonrotting_corpse(mnum) \
|
|
((mnum) == PM_LIZARD || (mnum) == PM_LICHEN \
|
|
|| is_rider(&mons[mnum]) \
|
|
|| (mnum) == PM_ACID_BLOB)
|
|
|
|
/* non-rotting non-corpses; unlike lizard corpses, these items will behave
|
|
as if rotten if they are cursed (fortune cookies handled elsewhere) */
|
|
#define nonrotting_food(otyp) \
|
|
((otyp) == LEMBAS_WAFER || (otyp) == CRAM_RATION)
|
|
|
|
/* see hunger states in hack.h - texts used on bottom line
|
|
Also used in botl.c and insight.c */
|
|
const char *const hu_stat[] = {
|
|
"Satiated", " ", "Hungry ", "Weak ",
|
|
"Fainting", "Fainted ", "Starved "
|
|
};
|
|
|
|
static const struct victual_info zero_victual = { 0 };
|
|
|
|
/* used by getobj() callback routines eat_ok()/offer_ok()/tin_ok() to
|
|
indicate whether player was given an opportunity to eat or offer or
|
|
tin an item on the floor and declined, in order to insert "else"
|
|
into the "you don't have anything [else] to {eat | offer | tin}"
|
|
feedback if hero lacks any suitable items in inventory
|
|
[reinitialized every time it's used so does not need to be placed
|
|
in struct instance_globals g for potential bulk reinitialization] */
|
|
static int getobj_else = 0;
|
|
|
|
/*
|
|
* Decide whether a particular object can be eaten by the possibly
|
|
* polymorphed character. Not used for monster checks.
|
|
*/
|
|
boolean
|
|
is_edible(struct obj *obj)
|
|
{
|
|
/* protect invocation tools but not Rider corpses (handled elsewhere)*/
|
|
/* if (obj->oclass != FOOD_CLASS && obj_resists(obj, 0, 0)) */
|
|
if (objects[obj->otyp].oc_unique)
|
|
return FALSE;
|
|
/* above also prevents the Amulet from being eaten, so we must never
|
|
allow fake amulets to be eaten either [which is already the case] */
|
|
|
|
if (gy.youmonst.data == &mons[PM_FIRE_ELEMENTAL]
|
|
&& is_flammable(obj))
|
|
return TRUE;
|
|
|
|
if (metallivorous(gy.youmonst.data) && is_metallic(obj)
|
|
&& (gy.youmonst.data != &mons[PM_RUST_MONSTER] || is_rustprone(obj)))
|
|
return TRUE;
|
|
|
|
/* Ghouls only eat non-veggy corpses or eggs (see dogfood()) */
|
|
if (u.umonnum == PM_GHOUL)
|
|
return (boolean)((obj->otyp == CORPSE
|
|
&& !vegan(&mons[obj->corpsenm]))
|
|
|| (obj->otyp == EGG));
|
|
|
|
if (u.umonnum == PM_GELATINOUS_CUBE && is_organic(obj)
|
|
/* [g-cubes can eat containers and retain all contents
|
|
as engulfed items, but poly'd player can't do that] */
|
|
&& !Has_contents(obj))
|
|
return TRUE;
|
|
|
|
return (boolean) (obj->oclass == FOOD_CLASS);
|
|
}
|
|
|
|
/* used for hero init, life saving (if choking), and prayer results of fix
|
|
starving, fix weak from hunger, or golden glow boon (if u.uhunger < 900) */
|
|
void
|
|
init_uhunger(void)
|
|
{
|
|
disp.botl = (u.uhs != NOT_HUNGRY || ATEMP(A_STR) < 0);
|
|
u.uhunger = 900;
|
|
u.uhs = NOT_HUNGRY;
|
|
if (ATEMP(A_STR) < 0) {
|
|
ATEMP(A_STR) = 0;
|
|
(void) encumber_msg();
|
|
}
|
|
}
|
|
|
|
/* tin types [SPINACH_TIN = -1, overrides corpsenm, nut==600] */
|
|
static const struct {
|
|
const char *txt; /* description */
|
|
int nut; /* nutrition */
|
|
Bitfield(fodder, 1); /* stocked by health food shops */
|
|
Bitfield(greasy, 1); /* causes slippery fingers */
|
|
} tintxts[] = { { "rotten", -50, 0, 0 }, /* ROTTEN_TIN = 0 */
|
|
{ "homemade", 50, 1, 0 }, /* HOMEMADE_TIN = 1 */
|
|
{ "soup made from", 20, 1, 0 },
|
|
{ "french fried", 40, 0, 1 },
|
|
{ "pickled", 40, 1, 0 },
|
|
{ "boiled", 50, 1, 0 },
|
|
{ "smoked", 50, 1, 0 },
|
|
{ "dried", 55, 1, 0 },
|
|
{ "deep fried", 60, 0, 1 },
|
|
{ "szechuan", 70, 1, 0 },
|
|
{ "broiled", 80, 0, 0 },
|
|
{ "stir fried", 80, 0, 1 },
|
|
{ "sauteed", 95, 0, 0 },
|
|
{ "candied", 100, 1, 0 },
|
|
{ "pureed", 500, 1, 0 },
|
|
{ "", 0, 0, 0 } };
|
|
#define TTSZ SIZE(tintxts)
|
|
|
|
/* called after mimicking is over */
|
|
staticfn int
|
|
eatmdone(void)
|
|
{
|
|
/* release `eatmbuf' */
|
|
if (ge.eatmbuf) {
|
|
if (gn.nomovemsg == ge.eatmbuf)
|
|
gn.nomovemsg = 0;
|
|
free((genericptr_t) ge.eatmbuf), ge.eatmbuf = 0;
|
|
}
|
|
/* update display */
|
|
if (U_AP_TYPE) {
|
|
gy.youmonst.m_ap_type = M_AP_NOTHING;
|
|
newsym(u.ux, u.uy);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* called when hallucination is toggled */
|
|
void
|
|
eatmupdate(void)
|
|
{
|
|
const char *altmsg = 0;
|
|
int altapp = 0; /* lint suppression */
|
|
|
|
if (!ge.eatmbuf || gn.nomovemsg != ge.eatmbuf)
|
|
return;
|
|
|
|
if (is_obj_mappear(&gy.youmonst,ORANGE) && !Hallucination) {
|
|
/* revert from hallucinatory to "normal" mimicking */
|
|
altmsg = "You now prefer mimicking yourself.";
|
|
altapp = GOLD_PIECE;
|
|
} else if (is_obj_mappear(&gy.youmonst,GOLD_PIECE) && Hallucination) {
|
|
/* won't happen; anything which might make immobilized
|
|
hero begin hallucinating (black light attack, theft
|
|
of Grayswandir) will terminate the mimicry first */
|
|
altmsg = "Your rind escaped intact.";
|
|
altapp = ORANGE;
|
|
}
|
|
|
|
if (altmsg) {
|
|
/* replace end-of-mimicking message */
|
|
unsigned amlen = Strlen(altmsg);
|
|
if (amlen > Strlen(ge.eatmbuf)) {
|
|
free((genericptr_t) ge.eatmbuf);
|
|
ge.eatmbuf = (char *) alloc(amlen + 1);
|
|
}
|
|
gn.nomovemsg = strcpy(ge.eatmbuf, altmsg);
|
|
/* update current image */
|
|
gy.youmonst.mappearance = altapp;
|
|
newsym(u.ux, u.uy);
|
|
}
|
|
}
|
|
|
|
/* ``[the(] singular(food, xname) [)]'' */
|
|
staticfn const char *
|
|
food_xname(struct obj *food, boolean the_pfx)
|
|
{
|
|
const char *result;
|
|
|
|
if (food->otyp == CORPSE) {
|
|
result = corpse_xname(food, (const char *) 0,
|
|
CXN_SINGULAR | (the_pfx ? CXN_PFX_THE : 0));
|
|
/* not strictly needed since pname values are capitalized
|
|
and the() is a no-op for them */
|
|
if (type_is_pname(&mons[food->corpsenm]))
|
|
the_pfx = FALSE;
|
|
} else {
|
|
/* the ordinary case */
|
|
result = singular(food, xname);
|
|
}
|
|
if (the_pfx)
|
|
result = the(result);
|
|
return result;
|
|
}
|
|
|
|
/* Created by GAN 01/28/87
|
|
* Amended by AKP 09/22/87: if not hard, don't choke, just vomit.
|
|
* Amended by 3. 06/12/89: if not hard, sometimes choke anyway, to keep risk.
|
|
* 11/10/89: if hard, rarely vomit anyway, for slim chance.
|
|
*
|
|
* To a full belly all food is bad. (It.)
|
|
*/
|
|
staticfn void
|
|
choke(struct obj *food)
|
|
{
|
|
/* only happens if you were satiated */
|
|
if (u.uhs != SATIATED) {
|
|
if (!food || food->otyp != AMULET_OF_STRANGULATION)
|
|
return;
|
|
} else if (Role_if(PM_KNIGHT) && u.ualign.type == A_LAWFUL) {
|
|
adjalign(-1); /* gluttony is unchivalrous */
|
|
You_feel("like a glutton!");
|
|
}
|
|
|
|
exercise(A_CON, FALSE);
|
|
|
|
if (Breathless || Hunger || (!Strangled && !rn2(20))) {
|
|
/* choking by eating AoS doesn't involve stuffing yourself */
|
|
if (food && food->otyp == AMULET_OF_STRANGULATION) {
|
|
You("choke, but recover your composure.");
|
|
return;
|
|
}
|
|
You("stuff yourself and then vomit voluminously.");
|
|
morehungry(Hunger ? (u.uhunger - 60) : 1000); /* just got very sick! */
|
|
vomit();
|
|
} else {
|
|
svk.killer.format = KILLED_BY_AN;
|
|
/*
|
|
* Note all "killer"s below read "Choked on %s" on the
|
|
* high score list & tombstone. So plan accordingly.
|
|
*/
|
|
if (food) {
|
|
You("choke over your %s.", foodword(food));
|
|
if (food->oclass == COIN_CLASS) {
|
|
Strcpy(svk.killer.name, "very rich meal");
|
|
} else {
|
|
svk.killer.format = KILLED_BY;
|
|
Strcpy(svk.killer.name, killer_xname(food));
|
|
}
|
|
} else {
|
|
You("choke over it.");
|
|
Strcpy(svk.killer.name, "quick snack");
|
|
}
|
|
You("die...");
|
|
done(CHOKING);
|
|
}
|
|
}
|
|
|
|
/* modify victual.piece->owt depending on time spent consuming it */
|
|
staticfn void
|
|
recalc_wt(void)
|
|
{
|
|
struct obj *piece = svc.context.victual.piece;
|
|
|
|
if (!piece) {
|
|
impossible("recalc_wt without piece");
|
|
return;
|
|
}
|
|
debugpline1("Old weight = %d", piece->owt);
|
|
debugpline2("Used time = %d, Req'd time = %d",
|
|
svc.context.victual.usedtime, svc.context.victual.reqtime);
|
|
piece->owt = weight(piece);
|
|
debugpline1("New weight = %d", piece->owt);
|
|
}
|
|
|
|
/* called when eating interrupted by an event */
|
|
void
|
|
reset_eat(void)
|
|
{
|
|
/* we only set a flag here - the actual reset process is done after
|
|
* the round is spent eating.
|
|
*/
|
|
if (svc.context.victual.eating && !svc.context.victual.doreset) {
|
|
debugpline0("reset_eat...");
|
|
svc.context.victual.doreset = 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* base nutrition of a food-class object; this used to include a variation
|
|
of the code that is now in adj_victual_nutrition() and was moved due to
|
|
its affect on weight() */
|
|
unsigned
|
|
obj_nutrition(struct obj *otmp)
|
|
{
|
|
unsigned nut = (otmp->otyp == CORPSE) ? mons[otmp->corpsenm].cnutrit
|
|
: otmp->globby ? otmp->owt
|
|
: (unsigned) objects[otmp->otyp].oc_nutrition;
|
|
|
|
return nut;
|
|
}
|
|
|
|
/* nutrition increment for next byte; this used to be factored into
|
|
victual.piece->oeaten but that produced weight change if hero
|
|
polymorphed to or from one of the races which has nutrition adjusted */
|
|
staticfn int
|
|
adj_victual_nutrition(void)
|
|
{
|
|
int otyp = svc.context.victual.piece->otyp;
|
|
/* note: adj_victual_nutrition() is only called when 'nmod' is negative */
|
|
int nut = -svc.context.victual.nmod; /* convert 'nmod' to positive */
|
|
|
|
assert(nut > 0);
|
|
if (otyp == LEMBAS_WAFER) {
|
|
if (maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF)))
|
|
nut += (nut + 2) / 4; /* 800 -> 1000 */
|
|
else if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC)))
|
|
nut -= (nut + 2) / 4; /* 800 -> 600 */
|
|
} else if (otyp == CRAM_RATION) {
|
|
if (maybe_polyd(is_dwarf(gy.youmonst.data), Race_if(PM_DWARF)))
|
|
nut += (nut + 3) / 6; /* 600 -> 700 */
|
|
}
|
|
nut = max(nut, 1);
|
|
return nut;
|
|
}
|
|
|
|
/* might destroy otmp if hero drops it */
|
|
staticfn struct obj *
|
|
touchfood(struct obj *otmp)
|
|
{
|
|
if (otmp->quan > 1L) {
|
|
if (!carried(otmp))
|
|
(void) splitobj(otmp, otmp->quan - 1L);
|
|
else
|
|
otmp = splitobj(otmp, 1L);
|
|
debugpline0("split food,");
|
|
}
|
|
|
|
if (!otmp->oeaten) {
|
|
costly_alteration(otmp, COST_BITE);
|
|
otmp->oeaten = obj_nutrition(otmp);
|
|
}
|
|
|
|
if (carried(otmp)) {
|
|
freeinv(otmp);
|
|
if (inv_cnt(FALSE) >= invlet_basic) {
|
|
sellobj_state(SELL_DONTSELL);
|
|
dropy(otmp);
|
|
sellobj_state(SELL_NORMAL);
|
|
if (otmp->where == OBJ_DELETED)
|
|
otmp = (struct obj *) NULL;
|
|
} else {
|
|
otmp = addinv_nomerge(otmp);
|
|
}
|
|
}
|
|
return otmp;
|
|
}
|
|
|
|
/* When food decays, in the middle of your meal, we don't want to dereference
|
|
* any dangling pointers, so set it to null (which should still trigger
|
|
* do_reset_eat() at the beginning of eatfood()) and check for null pointers
|
|
* in do_reset_eat().
|
|
*/
|
|
void
|
|
food_disappears(struct obj *obj)
|
|
{
|
|
if (obj == svc.context.victual.piece)
|
|
svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */
|
|
|
|
if (obj->timed)
|
|
obj_stop_timers(obj);
|
|
}
|
|
|
|
/* renaming an object used to result in it having a different address,
|
|
so the sequence start eating/opening, get interrupted, name the food,
|
|
resume eating/opening would restart from scratch */
|
|
void
|
|
food_substitution(struct obj *old_obj, struct obj *new_obj)
|
|
{
|
|
if (old_obj == svc.context.victual.piece) {
|
|
svc.context.victual.piece = new_obj;
|
|
svc.context.victual.o_id = new_obj->o_id;
|
|
}
|
|
if (old_obj == svc.context.tin.tin) {
|
|
svc.context.tin.tin = new_obj;
|
|
svc.context.tin.o_id = new_obj->o_id;
|
|
}
|
|
}
|
|
|
|
staticfn void
|
|
do_reset_eat(void)
|
|
{
|
|
debugpline0("do_reset_eat...");
|
|
if (svc.context.victual.piece) {
|
|
struct obj *otmp;
|
|
|
|
svc.context.victual.o_id = 0;
|
|
otmp = touchfood(svc.context.victual.piece);
|
|
svc.context.victual.piece = otmp;
|
|
if (otmp) {
|
|
svc.context.victual.o_id = otmp->o_id;
|
|
recalc_wt();
|
|
}
|
|
}
|
|
svc.context.victual.fullwarn
|
|
= svc.context.victual.eating
|
|
= svc.context.victual.doreset
|
|
= 0;
|
|
/* Do not set canchoke to FALSE; if we continue eating the same object
|
|
* we need to know if canchoke was set when they started eating it the
|
|
* previous time. And if we don't continue eating the same object
|
|
* canchoke always gets recalculated anyway.
|
|
*/
|
|
stop_occupation();
|
|
newuhs(FALSE);
|
|
}
|
|
|
|
/* if 'prop' is only set because of a timed value (so not an intrinsic
|
|
attribute or because of polymorph shape or worn or carried gear), return
|
|
its timeout, otherwise return 0; used by enlightenment */
|
|
long
|
|
temp_resist(int prop)
|
|
{
|
|
struct prop *p = &u.uprops[prop];
|
|
long timeout = p->intrinsic & TIMEOUT;
|
|
|
|
if (timeout
|
|
/* and if not also protected by polymorph form */
|
|
&& (p->intrinsic & ~TIMEOUT) == 0L
|
|
/* and not by worn gear (dragon armor) */
|
|
&& !p->extrinsic
|
|
/* and property is not blocked; we don't expect this, but if it
|
|
is then the timeout doesn't matter so we won't extend that */
|
|
&& !p->blocked) {
|
|
return timeout;
|
|
}
|
|
return 0L;
|
|
}
|
|
|
|
/* if temporary acid or stoning resistance is timing out while eating
|
|
something which that resistance is protecting against, caller will
|
|
extend resistance's duration so that it times out after meal finishes */
|
|
boolean
|
|
eating_dangerous_corpse(int res)
|
|
{
|
|
struct obj *food;
|
|
int mnum;
|
|
|
|
if (go.occupation == eatfood
|
|
&& (food = svc.context.victual.piece) != 0
|
|
&& food->otyp == CORPSE
|
|
&& (mnum = food->corpsenm) >= LOW_PM
|
|
&& (carried(food) || obj_here(food, u.ux, u.uy))) {
|
|
|
|
if (res == ACID_RES && acidic(&mons[mnum]))
|
|
return TRUE;
|
|
/* flesh_petrifies() includes Medusa as well as touch_petrifies() */
|
|
if (res == STONE_RES && flesh_petrifies(&mons[mnum]))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0 /* no longer used */
|
|
staticfn void maybe_extend_timed_resist(int);
|
|
|
|
/* if temp resist against 'prop' is about to timeout, extend it slightly */
|
|
staticfn void
|
|
maybe_extend_timed_resist(int prop)
|
|
{
|
|
long timeout = temp_resist(prop);
|
|
|
|
/* if hero is being protected from nasty effects of current meal by
|
|
temporary resistance (timed acid resist or timed stoning resist),
|
|
prevent expiration from occurring while the meal is in progress
|
|
so that player doesn't get feedback about becoming more vulnerable
|
|
and then have the hero stay unharmed; has a minor side-effect of
|
|
also extending the protection against other attacks of the sort
|
|
being resisted */
|
|
if (timeout == 1L) {
|
|
set_itimeout(&u.uprops[prop].intrinsic, 2L);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* called each move during eating process */
|
|
staticfn int
|
|
eatfood(void)
|
|
{
|
|
struct obj *food = svc.context.victual.piece;
|
|
|
|
if (food && !carried(food) && !obj_here(food, u.ux, u.uy))
|
|
food = 0;
|
|
if (!food) {
|
|
/* maybe it was stolen? */
|
|
do_reset_eat();
|
|
return 0;
|
|
}
|
|
if (!svc.context.victual.eating)
|
|
return 0;
|
|
|
|
if (++svc.context.victual.usedtime <= svc.context.victual.reqtime) {
|
|
if (bite())
|
|
return 0;
|
|
return 1; /* still busy */
|
|
} else { /* done */
|
|
done_eating(TRUE);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
staticfn void
|
|
done_eating(boolean message)
|
|
{
|
|
struct obj *piece = svc.context.victual.piece;
|
|
|
|
piece->in_use = TRUE;
|
|
go.occupation = 0; /* do this early, so newuhs() knows we're done */
|
|
newuhs(FALSE);
|
|
if (gn.nomovemsg) {
|
|
if (message)
|
|
pline1(gn.nomovemsg);
|
|
gn.nomovemsg = 0;
|
|
} else if (message) {
|
|
You("finish %s %s.",
|
|
(gy.youmonst.data == &mons[PM_FIRE_ELEMENTAL]) ? "consuming"
|
|
: "eating",
|
|
food_xname(piece, TRUE));
|
|
}
|
|
|
|
if (piece->otyp == CORPSE || piece->globby)
|
|
cpostfx(piece->corpsenm);
|
|
else
|
|
fpostfx(piece);
|
|
|
|
if (carried(piece))
|
|
useup(piece);
|
|
else
|
|
useupf(piece, 1L);
|
|
|
|
svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */
|
|
}
|
|
|
|
void
|
|
eating_conducts(struct permonst *pd)
|
|
{
|
|
int ll_conduct = 0;
|
|
|
|
if (!u.uconduct.food++) {
|
|
livelog_printf(LL_CONDUCT, "ate for the first time - %s",
|
|
pd->pmnames[NEUTRAL]);
|
|
ll_conduct++;
|
|
}
|
|
if (!vegan(pd)) {
|
|
if (!u.uconduct.unvegan++ && !ll_conduct) {
|
|
livelog_printf(LL_CONDUCT,
|
|
"consumed animal products (%s) for the first time",
|
|
pd->pmnames[NEUTRAL]);
|
|
ll_conduct++;
|
|
}
|
|
}
|
|
if (!vegetarian(pd)) {
|
|
if (!u.uconduct.unvegetarian && !ll_conduct)
|
|
livelog_printf(LL_CONDUCT, "tasted meat (%s) for the first time",
|
|
pd->pmnames[NEUTRAL]);
|
|
violated_vegetarian();
|
|
}
|
|
}
|
|
|
|
/* handle side-effects of mind flayer's tentacle attack */
|
|
int
|
|
eat_brains(
|
|
struct monst *magr,
|
|
struct monst *mdef,
|
|
boolean visflag,
|
|
int *dmg_p) /* for dishing out extra damage in lieu of Int loss */
|
|
{
|
|
struct permonst *pd = mdef->data;
|
|
boolean give_nutrit = FALSE;
|
|
int result = M_ATTK_HIT, xtra_dmg = rnd(10);
|
|
|
|
/* previous tentacle attack might have triggered fatal passive
|
|
counterattack [callers ought to be updated to avoid this situation] */
|
|
if (magr != &gy.youmonst && DEADMONSTER(magr)) {
|
|
return M_ATTK_AGR_DIED;
|
|
}
|
|
|
|
if (noncorporeal(pd)) {
|
|
if (visflag)
|
|
pline("%s brain is unharmed.",
|
|
(mdef == &gy.youmonst) ? "Your" : s_suffix(Monnam(mdef)));
|
|
return M_ATTK_MISS; /* side-effects can't occur */
|
|
} else if (magr == &gy.youmonst) {
|
|
You("eat %s brain!", s_suffix(mon_nam(mdef)));
|
|
} else if (mdef == &gy.youmonst) {
|
|
Your("brain is eaten!");
|
|
} else { /* monster against monster */
|
|
if (visflag && canspotmon(mdef))
|
|
pline("%s brain is eaten!", s_suffix(Monnam(mdef)));
|
|
}
|
|
|
|
if (flesh_petrifies(pd)) {
|
|
/* mind flayer has attempted to eat the brains of a petrification
|
|
inducing critter (most likely Medusa; attacking a cockatrice via
|
|
tentacle-touch should have been caught before reaching this far) */
|
|
if (magr == &gy.youmonst) {
|
|
if (!Stone_resistance && !Stoned)
|
|
make_stoned(5L, (char *) 0, KILLED_BY_AN,
|
|
pmname(pd, Mgender(mdef)));
|
|
} else {
|
|
/* no need to check for poly_when_stoned or Stone_resistance;
|
|
mind flayers don't have those capabilities */
|
|
if (visflag && canseemon(magr))
|
|
pline("%s turns to stone!", Monnam(magr));
|
|
monstone(magr);
|
|
if (!DEADMONSTER(magr)) {
|
|
/* life-saved; don't continue eating the brains */
|
|
return M_ATTK_MISS;
|
|
} else {
|
|
if (magr->mtame && !visflag)
|
|
/* parallels mhitm.c's brief_feeling */
|
|
You("have a sad thought for a moment, then it passes.");
|
|
return M_ATTK_AGR_DIED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (magr == &gy.youmonst) {
|
|
/*
|
|
* player mind flayer is eating something's brain
|
|
*/
|
|
eating_conducts(pd);
|
|
if (mindless(pd)) { /* (cannibalism not possible here) */
|
|
pline("%s doesn't notice.", Monnam(mdef));
|
|
/* all done; no extra harm inflicted upon target */
|
|
return M_ATTK_MISS;
|
|
} else if (is_rider(pd)) {
|
|
pline("Ingesting that is fatal.");
|
|
Sprintf(svk.killer.name, "unwisely ate the brain of %s",
|
|
pmname(pd, Mgender(mdef)));
|
|
svk.killer.format = NO_KILLER_PREFIX;
|
|
done(DIED);
|
|
/* life-saving needed to reach here */
|
|
exercise(A_WIS, FALSE);
|
|
*dmg_p += xtra_dmg; /* Rider takes extra damage */
|
|
} else {
|
|
morehungry(-rnd(30)); /* cannot choke */
|
|
if (ABASE(A_INT) < AMAX(A_INT)) {
|
|
/* recover lost Int; won't increase current max */
|
|
ABASE(A_INT) += rnd(4);
|
|
if (ABASE(A_INT) > AMAX(A_INT))
|
|
ABASE(A_INT) = AMAX(A_INT);
|
|
disp.botl = TRUE;
|
|
}
|
|
exercise(A_WIS, TRUE);
|
|
*dmg_p += xtra_dmg;
|
|
}
|
|
/* targeting another mind flayer or your own underlying species
|
|
is cannibalism */
|
|
(void) maybe_cannibal(monsndx(pd), TRUE);
|
|
|
|
} else if (mdef == &gy.youmonst) {
|
|
/*
|
|
* monster mind flayer is eating hero's brain
|
|
*/
|
|
/* no such thing as mindless players */
|
|
if (ABASE(A_INT) <= ATTRMIN(A_INT)) {
|
|
static NEARDATA const char brainlessness[] = "brainlessness";
|
|
|
|
if (Lifesaved) {
|
|
Strcpy(svk.killer.name, brainlessness);
|
|
svk.killer.format = KILLED_BY;
|
|
done(DIED);
|
|
/* amulet of life saving has now been used up */
|
|
pline("Unfortunately your brain is still gone.");
|
|
/* sanity check against adding other forms of life-saving */
|
|
u.uprops[LIFESAVED].extrinsic =
|
|
u.uprops[LIFESAVED].intrinsic = 0L;
|
|
} else {
|
|
Your("last thought fades away.");
|
|
}
|
|
Strcpy(svk.killer.name, brainlessness);
|
|
svk.killer.format = KILLED_BY;
|
|
done(DIED);
|
|
/* can only get here when in wizard or explore mode and user has
|
|
explicitly chosen not to die; arbitrarily boost intelligence */
|
|
ABASE(A_INT) = ATTRMIN(A_INT) + 2;
|
|
You_feel("like a scarecrow.");
|
|
}
|
|
give_nutrit = TRUE; /* in case a conflicted pet is doing this */
|
|
exercise(A_WIS, FALSE);
|
|
/* caller handles Int and memory loss */
|
|
|
|
} else { /* mhitm */
|
|
/*
|
|
* monster mind flayer is eating another monster's brain
|
|
*/
|
|
if (mindless(pd)) {
|
|
if (visflag && canspotmon(mdef))
|
|
pline("%s doesn't notice.", Monnam(mdef));
|
|
return M_ATTK_MISS;
|
|
} else if (is_rider(pd)) {
|
|
mondied(magr);
|
|
if (DEADMONSTER(magr))
|
|
result = M_ATTK_AGR_DIED;
|
|
/* Rider takes extra damage regardless of whether attacker dies */
|
|
*dmg_p += xtra_dmg;
|
|
} else {
|
|
*dmg_p += xtra_dmg;
|
|
give_nutrit = TRUE;
|
|
if (*dmg_p >= mdef->mhp && visflag && canspotmon(mdef))
|
|
pline("%s last thought fades away...",
|
|
s_suffix(Monnam(mdef)));
|
|
}
|
|
}
|
|
|
|
if (give_nutrit && magr->mtame && !magr->isminion) {
|
|
EDOG(magr)->hungrytime += rnd(60);
|
|
magr->mconf = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* eating a corpse or egg of one's own species is usually naughty */
|
|
staticfn boolean
|
|
maybe_cannibal(int pm, boolean allowmsg)
|
|
{
|
|
static NEARDATA long ate_brains = 0L;
|
|
struct permonst *fptr = &mons[pm]; /* food type */
|
|
|
|
/* when poly'd into a mind flayer, multiple tentacle hits in one
|
|
turn cause multiple digestion checks to occur; avoid giving
|
|
multiple luck penalties for the same attack */
|
|
if (svm.moves == ate_brains)
|
|
return FALSE;
|
|
ate_brains = svm.moves; /* ate_anything, not just brains... */
|
|
|
|
if (!CANNIBAL_ALLOWED()
|
|
/* non-cannibalistic heroes shouldn't eat own species ever
|
|
and also shouldn't eat current species when polymorphed
|
|
(even if having the form of something which doesn't care
|
|
about cannibalism--hero's innate traits aren't altered) */
|
|
&& (your_race(fptr)
|
|
|| (Upolyd && same_race(gy.youmonst.data, fptr))
|
|
|| (ismnum(u.ulycn) && were_beastie(pm) == u.ulycn))) {
|
|
if (allowmsg) {
|
|
if (Upolyd && your_race(fptr))
|
|
You("have a bad feeling deep inside.");
|
|
You("cannibal! You will regret this!");
|
|
}
|
|
HAggravate_monster |= FROMOUTSIDE;
|
|
change_luck(-rn1(4, 2)); /* -5..-2 */
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
staticfn void
|
|
cprefx(int pm)
|
|
{
|
|
(void) maybe_cannibal(pm, TRUE);
|
|
if (flesh_petrifies(&mons[pm])) {
|
|
if (!Stone_resistance
|
|
&& !(poly_when_stoned(gy.youmonst.data)
|
|
&& polymon(PM_STONE_GOLEM))) {
|
|
/* if food was a tin, use it up early to keep it out of bones */
|
|
if (svc.context.tin.tin)
|
|
use_up_tin(svc.context.tin.tin);
|
|
|
|
Sprintf(svk.killer.name, "tasting %s meat",
|
|
mons[pm].pmnames[NEUTRAL]);
|
|
svk.killer.format = KILLED_BY;
|
|
You("turn to stone.");
|
|
done(STONING);
|
|
if (svc.context.victual.piece)
|
|
svc.context.victual.eating = 0;
|
|
return; /* lifesaved */
|
|
}
|
|
}
|
|
|
|
switch (pm) {
|
|
case PM_LITTLE_DOG:
|
|
case PM_DOG:
|
|
case PM_LARGE_DOG:
|
|
case PM_KITTEN:
|
|
case PM_HOUSECAT:
|
|
case PM_LARGE_CAT:
|
|
/* cannibals are allowed to eat domestic animals without penalty */
|
|
if (!CANNIBAL_ALLOWED()) {
|
|
You_feel("that eating the %s was a bad idea.",
|
|
mons[pm].pmnames[NEUTRAL]);
|
|
HAggravate_monster |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case PM_LIZARD:
|
|
if (Stoned)
|
|
fix_petrification();
|
|
break;
|
|
case PM_DEATH:
|
|
case PM_PESTILENCE:
|
|
case PM_FAMINE: {
|
|
pline("Eating that is instantly fatal.");
|
|
Sprintf(svk.killer.name, "unwisely ate the body of %s",
|
|
mons[pm].pmnames[NEUTRAL]);
|
|
svk.killer.format = NO_KILLER_PREFIX;
|
|
done(DIED);
|
|
/* life-saving needed to reach here */
|
|
exercise(A_WIS, FALSE);
|
|
/* revive an actual corpse; can't do that if it was a tin;
|
|
3.7: this used to assume that such tins were impossible but
|
|
they can be wished for in wizard mode; they can't make it
|
|
to normal play though because bones creation empties them */
|
|
if (svc.context.victual.piece /* Null for tins */
|
|
&& svc.context.victual.piece->otyp == CORPSE /* paranoia */
|
|
&& revive_corpse(svc.context.victual.piece))
|
|
svc.context.victual = zero_victual; /* victual.piece=0, .o_id=0 */
|
|
return;
|
|
}
|
|
case PM_GREEN_SLIME:
|
|
if (!Slimed && !Unchanging && !slimeproof(gy.youmonst.data)) {
|
|
You("don't feel very well.");
|
|
make_slimed(10L, (char *) 0);
|
|
delayed_killer(SLIMED, KILLED_BY_AN, "");
|
|
}
|
|
FALLTHROUGH;
|
|
/* Fall through */
|
|
default:
|
|
if (acidic(&mons[pm]) && Stoned)
|
|
fix_petrification();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
fix_petrification(void)
|
|
{
|
|
char buf[BUFSZ];
|
|
|
|
if (Hallucination)
|
|
Sprintf(buf, "What a pity--you just ruined a future piece of %sart!",
|
|
ACURR(A_CHA) > 15 ? "fine " : "");
|
|
else
|
|
Strcpy(buf, "You feel limber!");
|
|
make_stoned(0L, buf, 0, (char *) 0);
|
|
}
|
|
|
|
/*
|
|
* If you add an intrinsic that can be gotten by eating a monster, add it
|
|
* to intrinsic_possible() and givit(). (It must already be in prop.h to
|
|
* be an intrinsic property.)
|
|
* It would be very easy to make the intrinsics not try to give you one
|
|
* that you already had by checking to see if you have it in
|
|
* intrinsic_possible() instead of givit(), but we're not that nice.
|
|
*/
|
|
|
|
/* intrinsic_possible() returns TRUE iff a monster can give an intrinsic. */
|
|
int
|
|
intrinsic_possible(int type, struct permonst *ptr)
|
|
{
|
|
int res = 0;
|
|
|
|
#ifdef DEBUG
|
|
#define ifdebugresist(Msg) \
|
|
do { \
|
|
if (res) \
|
|
debugpline0(Msg); \
|
|
} while (0)
|
|
#else
|
|
#define ifdebugresist(Msg) /*empty*/
|
|
#endif
|
|
switch (type) {
|
|
case FIRE_RES:
|
|
res = (ptr->mconveys & MR_FIRE) != 0;
|
|
ifdebugresist("can get fire resistance");
|
|
break;
|
|
case SLEEP_RES:
|
|
res = (ptr->mconveys & MR_SLEEP) != 0;
|
|
ifdebugresist("can get sleep resistance");
|
|
break;
|
|
case COLD_RES:
|
|
res = (ptr->mconveys & MR_COLD) != 0;
|
|
ifdebugresist("can get cold resistance");
|
|
break;
|
|
case DISINT_RES:
|
|
res = (ptr->mconveys & MR_DISINT) != 0;
|
|
ifdebugresist("can get disintegration resistance");
|
|
break;
|
|
case SHOCK_RES: /* shock (electricity) resistance */
|
|
res = (ptr->mconveys & MR_ELEC) != 0;
|
|
ifdebugresist("can get shock resistance");
|
|
break;
|
|
case POISON_RES:
|
|
res = (ptr->mconveys & MR_POISON) != 0;
|
|
ifdebugresist("can get poison resistance");
|
|
break;
|
|
case ACID_RES:
|
|
res = (ptr->mconveys & MR_ACID) != 0;
|
|
ifdebugresist("can get acid resistance temporarily");
|
|
break;
|
|
case STONE_RES:
|
|
res = (ptr->mconveys & MR_STONE) != 0;
|
|
ifdebugresist("can get stoning resistance temporarily");
|
|
break;
|
|
case TELEPORT:
|
|
res = can_teleport(ptr);
|
|
ifdebugresist("can get teleport");
|
|
break;
|
|
case TELEPORT_CONTROL:
|
|
res = control_teleport(ptr);
|
|
ifdebugresist("can get teleport control");
|
|
break;
|
|
case TELEPAT:
|
|
res = telepathic(ptr);
|
|
ifdebugresist("can get telepathy");
|
|
break;
|
|
default:
|
|
/* res stays 0 */
|
|
break;
|
|
}
|
|
#undef ifdebugresist
|
|
return res;
|
|
}
|
|
|
|
/* The "do we or do we not give the intrinsic" logic from givit(), extracted
|
|
* into its own function. Depends on the monster's level and the type of
|
|
* intrinsic it is trying to give you.
|
|
*/
|
|
boolean
|
|
should_givit(int type, struct permonst *ptr)
|
|
{
|
|
int chance;
|
|
|
|
/* some intrinsics are easier to get than others */
|
|
switch (type) {
|
|
case POISON_RES:
|
|
if ((ptr == &mons[PM_KILLER_BEE] || ptr == &mons[PM_SCORPION])
|
|
&& !rn2(4))
|
|
chance = 1;
|
|
else
|
|
chance = 15;
|
|
break;
|
|
case TELEPORT:
|
|
chance = 10;
|
|
break;
|
|
case TELEPORT_CONTROL:
|
|
chance = 12;
|
|
break;
|
|
case TELEPAT:
|
|
chance = 1;
|
|
break;
|
|
default:
|
|
chance = 15;
|
|
break;
|
|
}
|
|
|
|
return (ptr->mlevel > rn2(chance));
|
|
}
|
|
|
|
staticfn boolean
|
|
temp_givit(int type, struct permonst *ptr)
|
|
{
|
|
int chance = (type == STONE_RES) ? 6 : (type == ACID_RES) ? 3 : 0;
|
|
|
|
return chance ? (ptr->mlevel > rn2(chance)) : FALSE;
|
|
}
|
|
|
|
/* givit() tries to give you an intrinsic based on the monster's level
|
|
* and what type of intrinsic it is trying to give you.
|
|
*/
|
|
staticfn void
|
|
givit(int type, struct permonst *ptr)
|
|
{
|
|
debugpline1("Attempting to give intrinsic %d", type);
|
|
|
|
if (!should_givit(type, ptr) && !temp_givit(type, ptr))
|
|
return;
|
|
|
|
switch (type) {
|
|
case FIRE_RES:
|
|
debugpline0("Trying to give fire resistance");
|
|
if (!(HFire_resistance & FROMOUTSIDE)) {
|
|
You(Hallucination ? "be chillin'." : "feel a momentary chill.");
|
|
HFire_resistance |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case SLEEP_RES:
|
|
debugpline0("Trying to give sleep resistance");
|
|
if (!(HSleep_resistance & FROMOUTSIDE)) {
|
|
You_feel("wide awake.");
|
|
HSleep_resistance |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case COLD_RES:
|
|
debugpline0("Trying to give cold resistance");
|
|
if (!(HCold_resistance & FROMOUTSIDE)) {
|
|
You_feel("full of hot air.");
|
|
HCold_resistance |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case DISINT_RES:
|
|
debugpline0("Trying to give disintegration resistance");
|
|
if (!(HDisint_resistance & FROMOUTSIDE)) {
|
|
You_feel(Hallucination ? "totally together, man." : "very firm.");
|
|
HDisint_resistance |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case SHOCK_RES: /* shock (electricity) resistance */
|
|
debugpline0("Trying to give shock resistance");
|
|
if (!(HShock_resistance & FROMOUTSIDE)) {
|
|
if (Hallucination)
|
|
You_feel("grounded in reality.");
|
|
else
|
|
Your("health currently feels amplified!");
|
|
HShock_resistance |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case POISON_RES:
|
|
debugpline0("Trying to give poison resistance");
|
|
if (!(HPoison_resistance & FROMOUTSIDE)) {
|
|
You_feel(Poison_resistance ? "especially healthy." : "healthy.");
|
|
HPoison_resistance |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case TELEPORT:
|
|
debugpline0("Trying to give teleport");
|
|
if (!(HTeleportation & FROMOUTSIDE)) {
|
|
You_feel(Hallucination ? "diffuse." : "very jumpy.");
|
|
HTeleportation |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case TELEPORT_CONTROL:
|
|
debugpline0("Trying to give teleport control");
|
|
if (!(HTeleport_control & FROMOUTSIDE)) {
|
|
You_feel(Hallucination ? "centered in your personal space."
|
|
: "in control of yourself.");
|
|
HTeleport_control |= FROMOUTSIDE;
|
|
}
|
|
break;
|
|
case TELEPAT:
|
|
debugpline0("Trying to give telepathy");
|
|
if (!(HTelepat & FROMOUTSIDE)) {
|
|
You_feel(Hallucination ? "in touch with the cosmos."
|
|
: "a strange mental acuity.");
|
|
HTelepat |= FROMOUTSIDE;
|
|
/* If blind, make sure monsters show up. */
|
|
if (Blind)
|
|
see_monsters();
|
|
}
|
|
break;
|
|
case ACID_RES:
|
|
debugpline0("Giving timed acid resistance");
|
|
if (!Acid_resistance)
|
|
You_feel("%s.", Hallucination ? "secure from flashbacks"
|
|
: "less concerned about being harmed by acid");
|
|
incr_itimeout(&HAcid_resistance, d(3, 6));
|
|
break;
|
|
case STONE_RES:
|
|
debugpline0("Giving timed stoning resistance");
|
|
if (!Stone_resistance)
|
|
You_feel("%s.", Hallucination ? "unusually limber"
|
|
: "less concerned about becoming petrified");
|
|
incr_itimeout(&HStone_resistance, d(3, 6));
|
|
break;
|
|
default:
|
|
debugpline0("Tried to give an impossible intrinsic");
|
|
break;
|
|
}
|
|
}
|
|
|
|
staticfn void
|
|
eye_of_newt_buzz(void)
|
|
{
|
|
/* MRKR: "eye of newt" may give small magical energy boost */
|
|
if (rn2(3) || 3 * u.uen <= 2 * u.uenmax) {
|
|
int old_uen = u.uen;
|
|
|
|
u.uen += rnd(3);
|
|
if (u.uen > u.uenmax) {
|
|
if (!rn2(3)) {
|
|
u.uenmax++;
|
|
if (u.uenmax > u.uenpeak)
|
|
u.uenpeak = u.uenmax;
|
|
}
|
|
u.uen = u.uenmax;
|
|
}
|
|
if (old_uen != u.uen) {
|
|
You_feel("a mild buzz.");
|
|
disp.botl = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
DISABLE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* called after completely consuming a corpse */
|
|
staticfn void
|
|
cpostfx(int pm)
|
|
{
|
|
int tmp = 0;
|
|
int catch_lycanthropy = NON_PM;
|
|
boolean check_intrinsics = FALSE;
|
|
|
|
/* in case `afternmv' didn't get called for previously mimicking
|
|
gold, clean up now to avoid `eatmbuf' memory leak */
|
|
if (ge.eatmbuf)
|
|
(void) eatmdone();
|
|
|
|
switch (pm) {
|
|
case PM_WRAITH:
|
|
pluslvl(FALSE);
|
|
break;
|
|
case PM_HUMAN_WERERAT:
|
|
catch_lycanthropy = PM_WERERAT;
|
|
break;
|
|
case PM_HUMAN_WEREJACKAL:
|
|
catch_lycanthropy = PM_WEREJACKAL;
|
|
break;
|
|
case PM_HUMAN_WEREWOLF:
|
|
catch_lycanthropy = PM_WEREWOLF;
|
|
break;
|
|
case PM_NURSE:
|
|
if (Upolyd)
|
|
u.mh = u.mhmax;
|
|
else
|
|
u.uhp = u.uhpmax;
|
|
make_blinded(0L, !u.ucreamed);
|
|
disp.botl = TRUE;
|
|
check_intrinsics = TRUE; /* might also convey poison resistance */
|
|
break;
|
|
case PM_STALKER:
|
|
if (!Invis) {
|
|
set_itimeout(&HInvis, (long) rn1(100, 50));
|
|
if (!Blind && !BInvis)
|
|
self_invis_message();
|
|
} else {
|
|
if (!(HInvis & INTRINSIC))
|
|
You_feel("hidden!");
|
|
HInvis |= FROMOUTSIDE;
|
|
HSee_invisible |= FROMOUTSIDE;
|
|
}
|
|
newsym(u.ux, u.uy);
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case PM_YELLOW_LIGHT:
|
|
case PM_GIANT_BAT:
|
|
make_stunned((HStun & TIMEOUT) + 30L, FALSE);
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case PM_BAT:
|
|
make_stunned((HStun & TIMEOUT) + 30L, FALSE);
|
|
break;
|
|
case PM_GIANT_MIMIC:
|
|
tmp += 10;
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case PM_LARGE_MIMIC:
|
|
tmp += 20;
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case PM_SMALL_MIMIC:
|
|
tmp += 20;
|
|
if (gy.youmonst.data->mlet != S_MIMIC && !Unchanging) {
|
|
char buf[BUFSZ];
|
|
const char *tempshape = !Hallucination ? "a pile of gold"
|
|
: "an orange";
|
|
|
|
if (!u.uconduct.polyselfs++) /* you're changing form */
|
|
livelog_printf(LL_CONDUCT,
|
|
"changed form for the first time by mimicking %s",
|
|
tempshape);
|
|
You_cant("resist the temptation to mimic %s.", tempshape);
|
|
/* A pile of gold can't ride. */
|
|
if (u.usteed)
|
|
dismount_steed(DISMOUNT_FELL);
|
|
nomul(-tmp);
|
|
gm.multi_reason = "pretending to be a pile of gold";
|
|
Sprintf(buf,
|
|
Hallucination
|
|
? "You suddenly dread being peeled and mimic %s again!"
|
|
: "You now prefer mimicking %s again.",
|
|
an(Upolyd ? pmname(gy.youmonst.data, Ugender)
|
|
: gu.urace.noun));
|
|
ge.eatmbuf = dupstr(buf);
|
|
gn.nomovemsg = ge.eatmbuf;
|
|
ga.afternmv = eatmdone;
|
|
/* ??? what if this was set before? */
|
|
gy.youmonst.m_ap_type = M_AP_OBJECT;
|
|
gy.youmonst.mappearance = Hallucination ? ORANGE : GOLD_PIECE;
|
|
newsym(u.ux, u.uy);
|
|
curs_on_u();
|
|
/* make gold symbol show up now */
|
|
display_nhwindow(WIN_MAP, TRUE);
|
|
}
|
|
break;
|
|
case PM_QUANTUM_MECHANIC:
|
|
Your("velocity suddenly seems very uncertain!");
|
|
if (HFast & INTRINSIC) {
|
|
HFast &= ~INTRINSIC;
|
|
You("seem slower.");
|
|
} else {
|
|
HFast |= FROMOUTSIDE;
|
|
You("seem faster.");
|
|
}
|
|
break;
|
|
case PM_LIZARD:
|
|
if ((HStun & TIMEOUT) > 2)
|
|
make_stunned(2L, FALSE);
|
|
if ((HConfusion & TIMEOUT) > 2)
|
|
make_confused(2L, FALSE);
|
|
check_intrinsics = TRUE; /* might convey temporary stoning resist */
|
|
break;
|
|
case PM_CHAMELEON:
|
|
case PM_DOPPELGANGER:
|
|
case PM_SANDESTIN: /* moot--they don't leave corpses */
|
|
case PM_GENETIC_ENGINEER:
|
|
if (Unchanging) {
|
|
You_feel("momentarily different."); /* same as poly trap */
|
|
} else {
|
|
/* polyself() is potentially fatal; if food is a tin, use it up
|
|
early to keep it out of bones */
|
|
if (svc.context.tin.tin) {
|
|
use_up_tin(svc.context.tin.tin);
|
|
/* most tin effects end up being skipped */
|
|
lesshungry(200 + (metallivorous(gy.youmonst.data) ? 5 : 0));
|
|
}
|
|
|
|
You("%s.", (pm == PM_GENETIC_ENGINEER)
|
|
? "undergo a freakish metamorphosis"
|
|
: "feel a change coming over you");
|
|
polyself(POLY_NOFLAGS);
|
|
}
|
|
break;
|
|
case PM_DISPLACER_BEAST:
|
|
if (!Displaced) /* give a message (before setting the timeout) */
|
|
toggle_displacement((struct obj *) 0, 0L, TRUE);
|
|
incr_itimeout(&HDisplaced, d(6, 6));
|
|
break;
|
|
case PM_DISENCHANTER:
|
|
/* picks an intrinsic at random and removes it; there's
|
|
no feedback if hero already lacks the chosen ability */
|
|
debugpline0("using attrcurse to strip an intrinsic");
|
|
(void) attrcurse();
|
|
break;
|
|
case PM_DEATH:
|
|
case PM_PESTILENCE:
|
|
case PM_FAMINE:
|
|
/* life-saved; don't attempt to confer any intrinsics */
|
|
break;
|
|
case PM_MIND_FLAYER:
|
|
case PM_MASTER_MIND_FLAYER:
|
|
if (ABASE(A_INT) < ATTRMAX(A_INT)) {
|
|
if (!rn2(2)) {
|
|
pline("Yum! That was real brain food!");
|
|
(void) adjattrib(A_INT, 1, FALSE);
|
|
break; /* don't give them telepathy, too */
|
|
}
|
|
} else {
|
|
pline("For some reason, that tasted bland.");
|
|
}
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
default:
|
|
check_intrinsics = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* possibly convey an intrinsic */
|
|
if (check_intrinsics) {
|
|
struct permonst *ptr = &mons[pm];
|
|
|
|
if (dmgtype(ptr, AD_STUN) || dmgtype(ptr, AD_HALU)
|
|
|| pm == PM_VIOLET_FUNGUS) {
|
|
pline("Oh wow! Great stuff!");
|
|
(void) make_hallucinated((HHallucination & TIMEOUT) + 200L, FALSE,
|
|
0L);
|
|
}
|
|
|
|
/* Eating magical monsters can give you some magical energy. */
|
|
if (attacktype(ptr, AT_MAGC) || pm == PM_NEWT)
|
|
eye_of_newt_buzz();
|
|
|
|
tmp = corpse_intrinsic(ptr);
|
|
|
|
/* if something was chosen, give it now (givit() might fail) */
|
|
if (tmp == -1)
|
|
gainstr((struct obj *) 0, 0, TRUE);
|
|
else if (tmp > 0)
|
|
givit(tmp, ptr);
|
|
} /* check_intrinsics */
|
|
|
|
if (ismnum(catch_lycanthropy)) {
|
|
set_ulycn(catch_lycanthropy);
|
|
retouch_equipment(2);
|
|
}
|
|
return;
|
|
}
|
|
|
|
RESTORE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* Choose (one of) the intrinsics granted by a corpse, and return it.
|
|
* If this corpse gives no intrinsics, return 0.
|
|
* For the special not-real-prop cases of strength gain from giants
|
|
* return fake prop value of -1.
|
|
* Non-deterministic; should only be called once per corpse.
|
|
*/
|
|
int
|
|
corpse_intrinsic(struct permonst *ptr)
|
|
{
|
|
/* Check the monster for all of the intrinsics. If this
|
|
* monster can give more than one, pick one to try to give
|
|
* from among all it can give.
|
|
*/
|
|
boolean conveys_STR = is_giant(ptr);
|
|
int i;
|
|
int count = 0; /* number of possible intrinsics */
|
|
int prop = 0; /* which one we will try to give */
|
|
|
|
if (conveys_STR) {
|
|
count = 1;
|
|
prop = -1; /* use -1 as fake prop index for STR */
|
|
debugpline1("\"Intrinsic\" strength, %d", prop);
|
|
}
|
|
for (i = 1; i <= LAST_PROP; i++) {
|
|
if (!intrinsic_possible(i, ptr))
|
|
continue;
|
|
++count;
|
|
/* a 1 in count chance of replacing the old choice
|
|
with this one, and a count-1 in count chance
|
|
of keeping the old choice (note that 1 in 1 and
|
|
0 in 1 are what we want for the first candidate) */
|
|
if (!rn2(count)) {
|
|
debugpline2("Intrinsic %d replacing %d", i, prop);
|
|
prop = i;
|
|
}
|
|
}
|
|
/* if strength is the only candidate, give it 50% chance */
|
|
if (conveys_STR && count == 1 && !rn2(2))
|
|
prop = 0;
|
|
|
|
return prop;
|
|
}
|
|
|
|
void
|
|
violated_vegetarian(void)
|
|
{
|
|
u.uconduct.unvegetarian++;
|
|
if (Role_if(PM_MONK)) {
|
|
You_feel("guilty.");
|
|
adjalign(-1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* common code to check and possibly charge for 1 svc.context.tin.tin,
|
|
* will split() svc.context.tin.tin if necessary */
|
|
staticfn struct obj *
|
|
costly_tin(int alter_type) /* COST_xxx */
|
|
{
|
|
struct obj *tin = svc.context.tin.tin;
|
|
|
|
if (carried(tin) ? tin->unpaid
|
|
: (costly_spot(tin->ox, tin->oy) && !tin->no_charge)) {
|
|
if (tin->quan > 1L) {
|
|
tin = svc.context.tin.tin = splitobj(tin, 1L);
|
|
svc.context.tin.o_id = tin->o_id;
|
|
}
|
|
costly_alteration(tin, alter_type);
|
|
}
|
|
return tin;
|
|
}
|
|
|
|
int
|
|
tin_variety_txt(char *s, int *tinvariety)
|
|
{
|
|
int k, l;
|
|
|
|
if (s && tinvariety) {
|
|
*tinvariety = -1;
|
|
for (k = 0; k < TTSZ - 1; ++k) {
|
|
l = (int) strlen(tintxts[k].txt);
|
|
if (!strncmpi(s, tintxts[k].txt, l) && ((int) strlen(s) > l)
|
|
&& s[l] == ' ') {
|
|
*tinvariety = k;
|
|
return (l + 1);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This assumes that buf already contains the word "tin",
|
|
* as is the case with caller xname().
|
|
*/
|
|
void
|
|
tin_details(struct obj *obj, int mnum, char *buf)
|
|
{
|
|
char buf2[BUFSZ];
|
|
|
|
if (!obj || !buf)
|
|
return;
|
|
|
|
int r = tin_variety(obj, TRUE);
|
|
|
|
if (r == SPINACH_TIN)
|
|
Strcat(buf, " of spinach");
|
|
else if (mnum == NON_PM)
|
|
Strcpy(buf, "empty tin");
|
|
else {
|
|
if ((obj->cknown || iflags.override_ID) && obj->spe < 0) {
|
|
if (r == ROTTEN_TIN || r == HOMEMADE_TIN) {
|
|
/* put these before the word tin */
|
|
Sprintf(buf2, "%s %s of ", tintxts[r].txt, buf);
|
|
Strcpy(buf, buf2);
|
|
} else {
|
|
Sprintf(eos(buf), " of %s ", tintxts[r].txt);
|
|
}
|
|
} else {
|
|
Strcpy(eos(buf), " of ");
|
|
}
|
|
if (vegetarian(&mons[mnum]))
|
|
Sprintf(eos(buf), "%s", mons[mnum].pmnames[NEUTRAL]);
|
|
else
|
|
Sprintf(eos(buf), "%s meat", mons[mnum].pmnames[NEUTRAL]);
|
|
}
|
|
}
|
|
|
|
void
|
|
set_tin_variety(struct obj *obj, int forcetype)
|
|
{
|
|
int r, mnum = obj->corpsenm;
|
|
|
|
if (forcetype == SPINACH_TIN
|
|
|| (forcetype == HEALTHY_TIN
|
|
&& (mnum == NON_PM /* empty or already spinach */
|
|
|| !vegetarian(&mons[mnum])))) { /* replace meat */
|
|
obj->corpsenm = NON_PM; /* not based on any monster */
|
|
obj->spe = 1; /* spinach */
|
|
return;
|
|
} else if (forcetype == HEALTHY_TIN) {
|
|
r = tin_variety(obj, FALSE);
|
|
if (r < 0 || r >= TTSZ)
|
|
r = ROTTEN_TIN; /* shouldn't happen */
|
|
while ((r == ROTTEN_TIN && !obj->cursed) || !tintxts[r].fodder)
|
|
r = rn2(TTSZ - 1);
|
|
} else if (forcetype >= 0 && forcetype < TTSZ - 1) {
|
|
r = forcetype;
|
|
} else { /* RANDOM_TIN */
|
|
r = rn2(TTSZ - 1); /* take your pick */
|
|
if (r == ROTTEN_TIN && (ismnum(mnum) && nonrotting_corpse(mnum)))
|
|
r = HOMEMADE_TIN; /* lizards don't rot */
|
|
}
|
|
obj->spe = -(r + 1); /* offset by 1 to allow index 0 */
|
|
}
|
|
|
|
staticfn int
|
|
tin_variety(
|
|
struct obj *obj,
|
|
boolean displ) /* we're just displaying so leave things alone */
|
|
{
|
|
int r, mnum = obj->corpsenm;
|
|
|
|
if (obj->spe == 1) {
|
|
r = SPINACH_TIN;
|
|
} else if (obj->cursed) {
|
|
r = ROTTEN_TIN; /* always rotten if cursed */
|
|
} else if (obj->spe < 0) {
|
|
r = -(obj->spe);
|
|
--r; /* get rid of the offset */
|
|
} else {
|
|
r = rn2(TTSZ - 1);
|
|
}
|
|
|
|
if (!displ && r == HOMEMADE_TIN && !obj->blessed && !rn2(7))
|
|
r = ROTTEN_TIN; /* some homemade tins go bad */
|
|
|
|
if (r == ROTTEN_TIN && (ismnum(mnum) && nonrotting_corpse(mnum)))
|
|
r = HOMEMADE_TIN; /* lizards don't rot */
|
|
return r;
|
|
}
|
|
|
|
/* finish consume_tin(); also potentially used by cprefx() and cpostfx() */
|
|
staticfn void
|
|
use_up_tin(struct obj *tin)
|
|
{
|
|
if (carried(tin))
|
|
useup(tin);
|
|
else
|
|
useupf(tin, 1L);
|
|
/* reset tin context */
|
|
svc.context.tin.tin = (struct obj *) NULL;
|
|
svc.context.tin.o_id = 0;
|
|
}
|
|
|
|
staticfn void
|
|
consume_tin(const char *mesg)
|
|
{
|
|
const char *what;
|
|
int which, mnum, r, nutamt;
|
|
/* if you've eaten tin itself, chance to not eat contents gets bypassed */
|
|
boolean always_eat = metallivorous(gy.youmonst.data);
|
|
struct obj *tin = svc.context.tin.tin;
|
|
|
|
r = tin_variety(tin, FALSE);
|
|
if (tin->otrapped || (tin->cursed && r != HOMEMADE_TIN && !rn2(8))) {
|
|
b_trapped("tin", NO_PART);
|
|
tin = costly_tin(COST_DSTROY);
|
|
use_up_tin(tin);
|
|
return;
|
|
}
|
|
|
|
pline1(mesg); /* "You succeed in opening the tin." */
|
|
|
|
if (r != SPINACH_TIN) {
|
|
mnum = tin->corpsenm;
|
|
if (mnum == NON_PM) {
|
|
pline("It turns out to be empty.");
|
|
tin->dknown = tin->known = 1;
|
|
tin = costly_tin(COST_OPEN);
|
|
use_up_tin(tin);
|
|
if (always_eat)
|
|
lesshungry(5); /* metallivorous hero ate the tin itself */
|
|
return;
|
|
}
|
|
|
|
which = 0; /* 0=>plural, 1=>as-is, 2=>"the" prefix */
|
|
if ((mnum == PM_COCKATRICE || mnum == PM_CHICKATRICE)
|
|
&& (Stone_resistance || Hallucination)) {
|
|
what = "chicken";
|
|
which = 1; /* suppress pluralization */
|
|
} else if (Hallucination) {
|
|
what = rndmonnam(NULL);
|
|
} else {
|
|
what = mons[mnum].pmnames[NEUTRAL];
|
|
if (the_unique_pm(&mons[mnum]))
|
|
which = 2;
|
|
else if (type_is_pname(&mons[mnum]))
|
|
which = 1;
|
|
}
|
|
if (which == 0)
|
|
what = makeplural(what);
|
|
else if (which == 2)
|
|
what = the(what);
|
|
|
|
if (!always_eat) {
|
|
pline("It smells like %s.", what);
|
|
if (y_n("Eat it?") == 'n') {
|
|
if (flags.verbose)
|
|
You("discard the open tin.");
|
|
if (!Hallucination)
|
|
tin->dknown = tin->known = 1;
|
|
tin = costly_tin(COST_OPEN);
|
|
use_up_tin(tin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* in case stop_occupation() was called on previous meal */
|
|
svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */
|
|
|
|
You("consume %s %s.", tintxts[r].txt, mons[mnum].pmnames[NEUTRAL]);
|
|
|
|
eating_conducts(&mons[mnum]);
|
|
|
|
tin->dknown = tin->known = 1;
|
|
/* charge for one at pre-eating cost */
|
|
tin = svc.context.tin.tin = costly_tin(COST_OPEN);
|
|
|
|
/* cprefx() or cpostfx() might use up tin to keep it out of bones */
|
|
cprefx(mnum);
|
|
if (svc.context.tin.tin)
|
|
cpostfx(mnum);
|
|
if (!svc.context.tin.tin)
|
|
return;
|
|
|
|
if (tintxts[r].nut < 0) { /* rotten */
|
|
make_vomiting((long) rn1(15, 10), FALSE);
|
|
} else {
|
|
nutamt = tintxts[r].nut;
|
|
/* nutrition from a homemade tin (made from a single corpse)
|
|
shouldn't be more than nutrition from the corresponding
|
|
corpse; other tinning modes might use more than one corpse
|
|
or add extra ingredients so aren't similarly restricted */
|
|
if (r == HOMEMADE_TIN && nutamt > mons[mnum].cnutrit)
|
|
nutamt = mons[mnum].cnutrit;
|
|
if (always_eat)
|
|
nutamt += 5; /* metallivorous hero ate the tin itself */
|
|
/* use up tin now; lesshungry() could be fatal and produce bones */
|
|
use_up_tin(tin), tin = NULL;
|
|
lesshungry(nutamt);
|
|
}
|
|
|
|
if (tintxts[r].greasy) {
|
|
/* normal hero is !Glib, because you can't open tins when Glib,
|
|
but one poly'd into metallivorous form might already be Glib;
|
|
it's debatable whether a rock mole should have its paws made
|
|
slippery when eating a greasy tin, but we'll go with that... */
|
|
int alreadyglib = (int) (Glib & TIMEOUT);
|
|
|
|
make_glib(alreadyglib + rn1(11, 5)); /* 5..15 */
|
|
pline("Eating %s food made your %s %s slippery.",
|
|
tintxts[r].txt, fingers_or_gloves(TRUE),
|
|
alreadyglib ? "even more" : "very");
|
|
}
|
|
|
|
} else { /* spinach... */
|
|
if (tin->cursed) {
|
|
pline("It contains some decaying%s%s substance.",
|
|
Blind ? "" : " ", Blind ? "" : hcolor(NH_GREEN));
|
|
} else {
|
|
pline("It contains spinach.");
|
|
tin->dknown = tin->known = 1;
|
|
}
|
|
|
|
if (!always_eat && y_n("Eat it?") == 'n') {
|
|
if (flags.verbose)
|
|
You("discard the open tin.");
|
|
tin = costly_tin(COST_OPEN);
|
|
use_up_tin(tin);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Same order as with non-spinach above:
|
|
* conduct update, side-effects, shop handling, and nutrition.
|
|
*/
|
|
/* don't need vegetarian checks for spinach */
|
|
if (!u.uconduct.food++)
|
|
livelog_printf(LL_CONDUCT, "ate for the first time (spinach)");
|
|
if (!tin->cursed)
|
|
pline("This makes you feel like %s!",
|
|
/* "Swee'pea" is a character from the Popeye cartoons */
|
|
Hallucination ? "Swee'pea"
|
|
/* "feel like Popeye" unless sustain ability suppresses
|
|
any attribute change; this slightly oversimplifies
|
|
things: we want "Popeye" if no strength increase
|
|
occurs due to already being at maximum, but we won't
|
|
get it if at-maximum and fixed-abil both apply */
|
|
: !Fixed_abil ? "Popeye"
|
|
/* no gain, feel like another character from Popeye */
|
|
: (flags.female ? "Olive Oyl" : "Bluto"));
|
|
gainstr(tin, 0, FALSE);
|
|
|
|
tin = svc.context.tin.tin = costly_tin(COST_OPEN);
|
|
nutamt = (tin->blessed ? 600 /* blessed */
|
|
: !tin->cursed ? (400 + rnd(200)) /* uncursed */
|
|
: (200 + rnd(400))); /* cursed */
|
|
if (always_eat)
|
|
nutamt += 5; /* metallivorous hero also eats the tin itself */
|
|
/* use up tin first; lesshungry() could be fatal and produce bones */
|
|
use_up_tin(tin), tin = NULL;
|
|
lesshungry(nutamt);
|
|
}
|
|
if (tin)
|
|
use_up_tin(tin);
|
|
return;
|
|
}
|
|
|
|
/* called during each move whilst opening a tin */
|
|
staticfn int
|
|
opentin(void)
|
|
{
|
|
/* perhaps it was stolen (although that should cause interruption) */
|
|
if (!carried(svc.context.tin.tin)
|
|
&& (!obj_here(svc.context.tin.tin, u.ux, u.uy)
|
|
|| !can_reach_floor(TRUE)))
|
|
return 0; /* %% probably we should use tinoid */
|
|
if (svc.context.tin.usedtime++ >= 50) {
|
|
You("give up your attempt to open the tin.");
|
|
return 0;
|
|
}
|
|
if (svc.context.tin.usedtime < svc.context.tin.reqtime)
|
|
return 1; /* still busy */
|
|
|
|
consume_tin("You succeed in opening the tin.");
|
|
return 0;
|
|
}
|
|
|
|
/* called when starting to open a tin */
|
|
staticfn void
|
|
start_tin(struct obj *otmp)
|
|
{
|
|
const char *mesg = 0;
|
|
int tmp;
|
|
|
|
if (metallivorous(gy.youmonst.data)) {
|
|
mesg = "You bite right into the metal tin...";
|
|
tmp = 0;
|
|
} else if (cantwield(gy.youmonst.data)) { /* nohands || verysmall */
|
|
You("cannot handle the tin properly to open it.");
|
|
return;
|
|
} else if (otmp->blessed) {
|
|
/* 50/50 chance for immediate access vs 1 turn delay (unless
|
|
wielding blessed tin opener which always yields immediate
|
|
access); 1 turn delay case is non-deterministic: getting
|
|
interrupted and retrying might yield another 1 turn delay
|
|
or might open immediately on 2nd (or 3rd, 4th, ...) try */
|
|
tmp = (uwep && uwep->blessed && uwep->otyp == TIN_OPENER) ? 0
|
|
: rn2(2);
|
|
if (!tmp)
|
|
mesg = "The tin opens like magic!";
|
|
else
|
|
pline_The("tin seems easy to open.");
|
|
} else if (uwep) {
|
|
switch (uwep->otyp) {
|
|
case TIN_OPENER:
|
|
mesg = "You easily open the tin."; /* iff tmp==0 */
|
|
tmp = rn2(uwep->cursed ? 3 : !uwep->blessed ? 2 : 1);
|
|
break;
|
|
case DAGGER:
|
|
case SILVER_DAGGER:
|
|
case ELVEN_DAGGER:
|
|
case ORCISH_DAGGER:
|
|
case ATHAME:
|
|
case KNIFE:
|
|
case STILETTO:
|
|
case CRYSKNIFE:
|
|
tmp = 3;
|
|
break;
|
|
case PICK_AXE:
|
|
case AXE:
|
|
tmp = 6;
|
|
break;
|
|
default:
|
|
goto no_opener;
|
|
}
|
|
pline("Using %s you try to open the tin.", yobjnam(uwep, (char *) 0));
|
|
} else {
|
|
no_opener:
|
|
pline("It is not so easy to open this tin.");
|
|
if (Glib) {
|
|
pline_The("tin slips from your %s.", fingers_or_gloves(FALSE));
|
|
if (otmp->quan > 1L) {
|
|
otmp = splitobj(otmp, 1L);
|
|
}
|
|
if (carried(otmp))
|
|
dropx(otmp);
|
|
else
|
|
stackobj(otmp);
|
|
return;
|
|
}
|
|
tmp = rn1(1 + 500 / ((int) (ACURR(A_DEX) + ACURRSTR)), 10);
|
|
}
|
|
|
|
svc.context.tin.tin = otmp;
|
|
svc.context.tin.o_id = otmp->o_id;
|
|
if (!tmp) {
|
|
consume_tin(mesg); /* begin immediately */
|
|
} else {
|
|
svc.context.tin.reqtime = tmp;
|
|
svc.context.tin.usedtime = 0;
|
|
set_occupation(opentin, "opening the tin", 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* called when waking up after fainting */
|
|
int
|
|
Hear_again(void)
|
|
{
|
|
/* Chance of deafness going away while fainted/sleeping/etc. */
|
|
if (!rn2(2)) {
|
|
make_deaf(0L, FALSE);
|
|
disp.botl = TRUE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* called on the "first bite" of rotten food */
|
|
staticfn int
|
|
rottenfood(struct obj *obj)
|
|
{
|
|
pline("Blecch! %s %s!",
|
|
is_rottable(obj) ? "Rotten" : "Awful", foodword(obj));
|
|
if (!rn2(4)) {
|
|
if (Hallucination)
|
|
You_feel("rather trippy.");
|
|
else
|
|
You_feel("rather %s.", body_part(LIGHT_HEADED));
|
|
make_confused(HConfusion + d(2, 4), FALSE);
|
|
} else if (!rn2(4) && !Blind) {
|
|
pline("Everything suddenly goes dark.");
|
|
/* hero is not Blind, but Blinded timer might be nonzero if
|
|
blindness is being overridden by the Eyes of the Overworld */
|
|
make_blinded(BlindedTimeout + (long) d(2, 10), FALSE);
|
|
if (!Blind)
|
|
Your1(vision_clears);
|
|
} else if (!rn2(3)) {
|
|
const char *what, *where;
|
|
int duration = rnd(10);
|
|
|
|
if (!Blind)
|
|
what = "goes", where = "dark";
|
|
else if (Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz))
|
|
what = "you lose control of", where = "yourself";
|
|
else
|
|
what = "you slap against the",
|
|
where = (u.usteed) ? "saddle" : surface(u.ux, u.uy);
|
|
pline_The("world spins and %s %s.", what, where);
|
|
incr_itimeout(&HDeaf, duration);
|
|
disp.botl = TRUE;
|
|
nomul(-duration);
|
|
gm.multi_reason = "unconscious from rotten food";
|
|
gn.nomovemsg = "You are conscious again.";
|
|
ga.afternmv = Hear_again;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* called when a corpse is selected as food */
|
|
staticfn int
|
|
eatcorpse(struct obj *otmp)
|
|
{
|
|
int retcode = 0, tp = 0, mnum = otmp->corpsenm;
|
|
long rotted = 0L;
|
|
int ll_conduct = 0;
|
|
boolean stoneable,
|
|
slimeable = (mnum == PM_GREEN_SLIME && !Slimed && !Unchanging
|
|
&& !slimeproof(gy.youmonst.data)),
|
|
glob = otmp->globby ? TRUE : FALSE;
|
|
|
|
assert(ismnum(mnum));
|
|
stoneable = (flesh_petrifies(&mons[mnum]) && !Stone_resistance
|
|
&& !poly_when_stoned(gy.youmonst.data));
|
|
|
|
/* KMH, conduct */
|
|
if (!vegan(&mons[mnum]))
|
|
if (!u.uconduct.unvegan++) {
|
|
livelog_printf(LL_CONDUCT,
|
|
"consumed animal products for the first time, by eating %s",
|
|
an(food_xname(otmp, FALSE)));
|
|
ll_conduct++;
|
|
}
|
|
if (!vegetarian(&mons[mnum])) {
|
|
if (!u.uconduct.unvegetarian && !ll_conduct)
|
|
livelog_printf(LL_CONDUCT,
|
|
"tasted meat for the first time, by eating %s",
|
|
an(food_xname(otmp, FALSE)));
|
|
violated_vegetarian();
|
|
}
|
|
if (!nonrotting_corpse(mnum)) {
|
|
long age = peek_at_iced_corpse_age(otmp);
|
|
|
|
rotted = (svm.moves - age) / (10L + rn2(20));
|
|
if (otmp->cursed)
|
|
rotted += 2L;
|
|
else if (otmp->blessed)
|
|
rotted -= 2L;
|
|
}
|
|
|
|
/* 3.7: globs don't become tainted, they shrink away */
|
|
if (!glob && !stoneable && !slimeable && rotted > 5L) {
|
|
boolean cannibal = maybe_cannibal(mnum, FALSE);
|
|
|
|
/* tp++; -- early return makes this unnecessary */
|
|
pline("Ulch - that %s was tainted%s!",
|
|
(mons[mnum].mlet == S_FUNGUS) ? "fungoid vegetation"
|
|
: vegetarian(&mons[mnum]) ? "protoplasm"
|
|
: "meat",
|
|
cannibal ? ", you cannibal" : "");
|
|
if (Sick_resistance) {
|
|
pline("It doesn't seem at all sickening, though...");
|
|
} else {
|
|
long sick_time;
|
|
|
|
sick_time = (long) rn1(10, 10);
|
|
/* make sure new ill doesn't result in improvement */
|
|
if (Sick && (sick_time > Sick))
|
|
sick_time = (Sick > 1L) ? Sick - 1L : 1L;
|
|
make_sick(sick_time, corpse_xname(otmp, "rotted", CXN_NORMAL),
|
|
TRUE, SICK_VOMITABLE);
|
|
|
|
pline("(It must have died too long ago to be safe to eat.)");
|
|
}
|
|
if (carried(otmp))
|
|
useup(otmp);
|
|
else
|
|
useupf(otmp, 1L);
|
|
return 2;
|
|
} else if (acidic(&mons[mnum]) && !Acid_resistance) {
|
|
tp++;
|
|
You("have a very bad case of stomach acid."); /* not body_part() */
|
|
losehp(rnd(15), !glob ? "acidic corpse" : "acidic glob",
|
|
KILLED_BY_AN); /* acid damage */
|
|
} else if (poisonous(&mons[mnum]) && rn2(5)) {
|
|
tp++;
|
|
pline("Ecch - that must have been poisonous!");
|
|
if (!Poison_resistance) {
|
|
poison_strdmg(rnd(4), rnd(15),
|
|
!glob ? "poisonous corpse" : "poisonous glob",
|
|
KILLED_BY_AN);
|
|
} else
|
|
You("seem unaffected by the poison.");
|
|
|
|
/* now any corpse left too long will make you mildly ill */
|
|
} else if ((rotted > 5L || (rotted > 3L && rn2(5))) && !Sick_resistance) {
|
|
tp++;
|
|
You_feel("%ssick.", (Sick) ? "very " : "");
|
|
losehp(rnd(8), !glob ? "cadaver" : "rotted glob", KILLED_BY_AN);
|
|
}
|
|
|
|
/* delay is weight dependent */
|
|
svc.context.victual.reqtime
|
|
= 3 + ((!glob ? mons[mnum].cwt : otmp->owt) >> 6);
|
|
|
|
if (!tp && !nonrotting_corpse(mnum) && (otmp->orotten || !rn2(7))) {
|
|
if (rottenfood(otmp)) {
|
|
otmp->orotten = TRUE;
|
|
otmp = touchfood(otmp);
|
|
if (!otmp)
|
|
return 1;
|
|
retcode = 1;
|
|
}
|
|
|
|
if (!mons[otmp->corpsenm].cnutrit) {
|
|
/* no nutrition: rots away, no message if you passed out */
|
|
if (!retcode)
|
|
pline_The("corpse rots away completely.");
|
|
if (carried(otmp))
|
|
useup(otmp);
|
|
else
|
|
useupf(otmp, 1L);
|
|
retcode = 2;
|
|
}
|
|
|
|
if (!retcode)
|
|
consume_oeaten(otmp, 2); /* oeaten >>= 2 */
|
|
} else if ((mnum == PM_COCKATRICE || mnum == PM_CHICKATRICE)
|
|
&& (Stone_resistance || Hallucination)) {
|
|
pline("This tastes just like chicken!");
|
|
} else if (mnum == PM_FLOATING_EYE && u.umonnum == PM_RAVEN) {
|
|
You("peck the eyeball with delight.");
|
|
} else if (tp) {
|
|
; /* we've already delivered a message; don't add "it tastes okay" */
|
|
} else {
|
|
/* yummy is always False for omnivores, palatable always True */
|
|
boolean yummy = (vegan(&mons[mnum])
|
|
? (!carnivorous(gy.youmonst.data)
|
|
&& herbivorous(gy.youmonst.data))
|
|
: (carnivorous(gy.youmonst.data)
|
|
&& !herbivorous(gy.youmonst.data))),
|
|
palatable = ((vegetarian(&mons[mnum])
|
|
? herbivorous(gy.youmonst.data)
|
|
: carnivorous(gy.youmonst.data))
|
|
&& rn2(10)
|
|
&& (rotted < 1 || !rn2((int) rotted + 1)));
|
|
const char *pmxnam = food_xname(otmp, FALSE);
|
|
static const char *const palatable_msgs[] = {
|
|
/* first char: T = tastes ... , I = is ... */
|
|
/* veggies are always just "okay" */
|
|
"Tokay", "Istringy", "Igamey", "Ifatty", "Itough"
|
|
};
|
|
int idx = vegetarian(&mons[mnum]) ? 0 : rn2(SIZE(palatable_msgs));
|
|
const char *palat_msg = palatable_msgs[idx];
|
|
boolean use_is = (Hallucination || (palatable && *palat_msg == 'I'));
|
|
|
|
if (!strncmpi(pmxnam, "the ", 4))
|
|
pmxnam += 4;
|
|
pline("%s%s %s %s%c",
|
|
type_is_pname(&mons[mnum])
|
|
? "" : the_unique_pm(&mons[mnum]) ? "The " : "This ",
|
|
pmxnam,
|
|
use_is ? "is" : "tastes",
|
|
/* tiger reference is to TV ads for "Frosted Flakes",
|
|
breakfast cereal targeted at kids by "Tony the tiger" */
|
|
Hallucination
|
|
? (yummy ? ((u.umonnum == PM_TIGER) ? "gr-r-reat" : "gnarly")
|
|
: palatable ? "copacetic" : "grody")
|
|
: (yummy ? "delicious" : palatable ?
|
|
&palat_msg[1] : "terrible"),
|
|
(yummy || !palatable) ? '!' : '.');
|
|
}
|
|
|
|
return retcode;
|
|
}
|
|
|
|
/* called as you start to eat */
|
|
staticfn void
|
|
start_eating(struct obj *otmp, boolean already_partly_eaten)
|
|
{
|
|
const char *old_nomovemsg, *save_nomovemsg;
|
|
static char msgbuf[BUFSZ];
|
|
|
|
debugpline2("start_eating: %s (victual = %s)",
|
|
/* note: fmt_ptr() returns a static buffer but supports
|
|
several such so we don't need to copy the first result
|
|
before calling it a second time */
|
|
fmt_ptr((genericptr_t) otmp),
|
|
fmt_ptr((genericptr_t) svc.context.victual.piece));
|
|
debugpline1("reqtime = %d", svc.context.victual.reqtime);
|
|
debugpline1("(original reqtime = %d)", objects[otmp->otyp].oc_delay);
|
|
debugpline1("nmod = %d", svc.context.victual.nmod);
|
|
debugpline1("oeaten = %d", otmp->oeaten);
|
|
svc.context.victual.fullwarn = svc.context.victual.doreset = 0;
|
|
svc.context.victual.eating = 1;
|
|
|
|
if (otmp->otyp == CORPSE || otmp->globby) {
|
|
cprefx(svc.context.victual.piece->corpsenm);
|
|
if (!svc.context.victual.piece || !svc.context.victual.eating) {
|
|
/* rider revived, or hero died and was lifesaved */
|
|
return;
|
|
}
|
|
}
|
|
|
|
old_nomovemsg = gn.nomovemsg;
|
|
if (bite()) {
|
|
/* survived choking, finish off food that's nearly done;
|
|
need this to handle cockatrice eggs, fortune cookies, etc */
|
|
if (++svc.context.victual.usedtime >= svc.context.victual.reqtime) {
|
|
/* don't want done_eating() to issue gn.nomovemsg if it
|
|
is due to vomit() called by bite() */
|
|
save_nomovemsg = gn.nomovemsg;
|
|
if (!old_nomovemsg)
|
|
gn.nomovemsg = 0;
|
|
done_eating(FALSE);
|
|
if (!old_nomovemsg)
|
|
gn.nomovemsg = save_nomovemsg;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (++svc.context.victual.usedtime >= svc.context.victual.reqtime) {
|
|
/* print "finish eating" message if they just resumed -dlc */
|
|
done_eating((svc.context.victual.reqtime > 1
|
|
|| already_partly_eaten) ? TRUE : FALSE);
|
|
return;
|
|
}
|
|
|
|
Sprintf(msgbuf, "eating %s", food_xname(otmp, TRUE));
|
|
set_occupation(eatfood, msgbuf, 0);
|
|
}
|
|
|
|
/* used by shrink_glob() timer routine */
|
|
boolean
|
|
eating_glob(struct obj *glob)
|
|
{
|
|
return (go.occupation == eatfood && glob == svc.context.victual.piece);
|
|
}
|
|
|
|
/* scare nearby monster when hero eats garlic */
|
|
staticfn void
|
|
garlic_breath(struct monst *mtmp)
|
|
{
|
|
if (olfaction(mtmp->data) && distu(mtmp->mx, mtmp->my) < 7)
|
|
monflee(mtmp, 0, FALSE, FALSE);
|
|
}
|
|
|
|
/*
|
|
* Called on "first bite" of (non-corpse) food, after touchfood() has
|
|
* marked it 'partly eaten'. Used for non-rotten non-tin non-corpse food.
|
|
* Messages should use present tense since multi-turn food won't be
|
|
* finishing at the time they're issued.
|
|
* Returns FALSE if eating should not succeed for whatever reason.
|
|
*/
|
|
staticfn boolean
|
|
fprefx(struct obj *otmp)
|
|
{
|
|
switch (otmp->otyp) {
|
|
case EGG:
|
|
if (otmp->corpsenm == PM_PYROLISK) {
|
|
if (carried(otmp))
|
|
useup(otmp);
|
|
else
|
|
useupf(otmp, 1L);
|
|
explode(u.ux, u.uy, -11, d(3, 6), 0, EXPL_FIERY);
|
|
return FALSE;
|
|
} else if (stale_egg(otmp)) {
|
|
pline("Ugh. Rotten egg."); /* perhaps others like it */
|
|
/* increasing existing nausea means that it will take longer
|
|
before eventual vomit, but also means that constitution
|
|
will be abused more times before illness completes */
|
|
make_vomiting((Vomiting & TIMEOUT) + (long) d(10, 4), TRUE);
|
|
} else
|
|
goto give_feedback;
|
|
break;
|
|
case FOOD_RATION: /* nutrition 800 */
|
|
/* 200+800 remains below 1000+1, the satiation threshold */
|
|
if (u.uhunger <= 200)
|
|
pline("%s!", Hallucination ? "Oh wow, like, superior, man"
|
|
: "This food really hits the spot");
|
|
|
|
/* 700-1+800 remains below 1500, the choking threshold which
|
|
triggers "you're having a hard time getting it down" feedback */
|
|
else if (u.uhunger < 700)
|
|
pline("This satiates your %s!", body_part(STOMACH));
|
|
/* [satiation message may be inaccurate if eating gets interrupted] */
|
|
break;
|
|
case TRIPE_RATION:
|
|
if (carnivorous(gy.youmonst.data) && !humanoid(gy.youmonst.data)) {
|
|
pline("This tripe ration is surprisingly good!");
|
|
} else if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC))) {
|
|
pline(Hallucination ? "Tastes great! Less filling!"
|
|
: "Mmm, tripe... not bad!");
|
|
} else {
|
|
pline("Yak - dog food!");
|
|
more_experienced(1, 0);
|
|
newexplevel();
|
|
/* not cannibalism, but we use similar criteria
|
|
for deciding whether to be sickened by this meal */
|
|
if (rn2(2) && !CANNIBAL_ALLOWED())
|
|
make_vomiting((long) rn1(svc.context.victual.reqtime, 14),
|
|
FALSE);
|
|
}
|
|
break;
|
|
case LEMBAS_WAFER:
|
|
if (maybe_polyd(is_orc(gy.youmonst.data), Race_if(PM_ORC))) {
|
|
pline("%s", "!#?&* elf kibble!");
|
|
break;
|
|
} else if (maybe_polyd(is_elf(gy.youmonst.data), Race_if(PM_ELF))) {
|
|
pline("A little goes a long way.");
|
|
break;
|
|
}
|
|
goto give_feedback;
|
|
case MEATBALL:
|
|
case MEAT_STICK:
|
|
case ENORMOUS_MEATBALL:
|
|
case MEAT_RING:
|
|
goto give_feedback;
|
|
case CLOVE_OF_GARLIC:
|
|
if (is_undead(gy.youmonst.data)) {
|
|
make_vomiting((long) rn1(svc.context.victual.reqtime, 5), FALSE);
|
|
break;
|
|
}
|
|
iter_mons(garlic_breath);
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
default:
|
|
if (otmp->otyp == SLIME_MOLD && !otmp->cursed
|
|
&& otmp->spe == svc.context.current_fruit) {
|
|
pline("My, this is a %s %s!",
|
|
Hallucination ? "primo" : "yummy",
|
|
singular(otmp, xname));
|
|
} else if (otmp->otyp == APPLE && otmp->cursed && !Sleep_resistance) {
|
|
; /* skip core joke; feedback deferred til fpostfx() */
|
|
|
|
#if defined(MAC) || defined(MACOS)
|
|
/* KMH -- Why should Unix have all the fun?
|
|
We check MACOS before UNIX to get the Apple-specific apple
|
|
message; the '#if UNIX' code will still kick in for pear. */
|
|
} else if (otmp->otyp == APPLE) {
|
|
pline("Delicious! Must be a Macintosh!");
|
|
#endif
|
|
|
|
#ifdef UNIX
|
|
} else if (otmp->otyp == APPLE || otmp->otyp == PEAR) {
|
|
if (!Hallucination) {
|
|
pline("Core dumped.");
|
|
} else {
|
|
/* based on an old Usenet joke, a fake a.out manual page */
|
|
int x = rnd(100);
|
|
|
|
pline("%s -- core dumped.",
|
|
(x <= 75)
|
|
? "Segmentation fault"
|
|
: (x <= 99)
|
|
? "Bus error"
|
|
: "Yo' mama");
|
|
}
|
|
#endif
|
|
} else {
|
|
give_feedback:
|
|
pline("This %s is %s", singular(otmp, xname),
|
|
otmp->cursed
|
|
? (Hallucination ? "grody!" : "terrible!")
|
|
: (otmp->otyp == CRAM_RATION
|
|
|| otmp->otyp == K_RATION
|
|
|| otmp->otyp == C_RATION)
|
|
? "bland."
|
|
: Hallucination ? "gnarly!" : "delicious!");
|
|
}
|
|
break; /* default */
|
|
} /* switch */
|
|
return TRUE;
|
|
}
|
|
|
|
/* increment a combat intrinsic with limits on its growth */
|
|
staticfn int
|
|
bounded_increase(int old, int inc, int typ)
|
|
{
|
|
int absold, absinc, sgnold, sgninc;
|
|
|
|
/* don't include any amount coming from worn rings (caller handles
|
|
'protection' differently) */
|
|
if (uright && uright->otyp == typ && typ != RIN_PROTECTION)
|
|
old -= uright->spe;
|
|
if (uleft && uleft->otyp == typ && typ != RIN_PROTECTION)
|
|
old -= uleft->spe;
|
|
absold = abs(old), absinc = abs(inc);
|
|
sgnold = sgn(old), sgninc = sgn(inc);
|
|
|
|
if (absinc == 0 || sgnold != sgninc || absold + absinc < 10) {
|
|
; /* use inc as-is */
|
|
} else if (absold + absinc < 20) {
|
|
absinc = rnd(absinc); /* 1..n */
|
|
if (absold + absinc < 10)
|
|
absinc = 10 - absold;
|
|
inc = sgninc * absinc;
|
|
} else if (absold + absinc < 40) {
|
|
absinc = rn2(absinc) ? 1 : 0;
|
|
if (absold + absinc < 20)
|
|
absinc = rnd(20 - absold);
|
|
inc = sgninc * absinc;
|
|
} else {
|
|
inc = 0; /* no further increase allowed via this method */
|
|
}
|
|
/* put amount from worn rings back */
|
|
if (uright && uright->otyp == typ && typ != RIN_PROTECTION)
|
|
old += uright->spe;
|
|
if (uleft && uleft->otyp == typ && typ != RIN_PROTECTION)
|
|
old += uleft->spe;
|
|
return old + inc;
|
|
}
|
|
|
|
staticfn void
|
|
accessory_has_effect(struct obj *otmp)
|
|
{
|
|
pline("Magic spreads through your body as you digest the %s.",
|
|
(otmp->oclass == RING_CLASS) ? "ring" : "amulet");
|
|
}
|
|
|
|
staticfn void
|
|
eataccessory(struct obj *otmp)
|
|
{
|
|
int typ = otmp->otyp;
|
|
long oldprop;
|
|
|
|
/* Note: rings are not so common that this is unbalancing. */
|
|
/* (How often do you even _find_ 3 rings of polymorph in a game?) */
|
|
oldprop = u.uprops[objects[typ].oc_oprop].intrinsic;
|
|
if (otmp == uleft || otmp == uright) {
|
|
Ring_gone(otmp);
|
|
if (u.uhp <= 0)
|
|
return; /* died from sink fall */
|
|
}
|
|
otmp->known = otmp->dknown = 1; /* by taste */
|
|
if (!rn2(otmp->oclass == RING_CLASS ? 3 : 5)) {
|
|
switch (otmp->otyp) {
|
|
default:
|
|
if (!objects[typ].oc_oprop)
|
|
break; /* should never happen */
|
|
|
|
if (!(u.uprops[objects[typ].oc_oprop].intrinsic & FROMOUTSIDE))
|
|
accessory_has_effect(otmp);
|
|
|
|
u.uprops[objects[typ].oc_oprop].intrinsic |= FROMOUTSIDE;
|
|
|
|
switch (typ) {
|
|
case RIN_SEE_INVISIBLE:
|
|
set_mimic_blocking();
|
|
see_monsters();
|
|
if (Invis && !oldprop && !ESee_invisible
|
|
&& !perceives(gy.youmonst.data) && !Blind) {
|
|
newsym(u.ux, u.uy);
|
|
pline("Suddenly you can see yourself.");
|
|
makeknown(typ);
|
|
}
|
|
break;
|
|
case RIN_INVISIBILITY:
|
|
if (!oldprop && !EInvis && !BInvis && !See_invisible
|
|
&& !Blind) {
|
|
newsym(u.ux, u.uy);
|
|
Your("body takes on a %s transparency...",
|
|
Hallucination ? "normal" : "strange");
|
|
makeknown(typ);
|
|
}
|
|
break;
|
|
case RIN_PROTECTION_FROM_SHAPE_CHAN:
|
|
rescham();
|
|
break;
|
|
case RIN_LEVITATION:
|
|
/* undo the `.intrinsic |= FROMOUTSIDE' done above */
|
|
u.uprops[LEVITATION].intrinsic = oldprop;
|
|
if (!Levitation) {
|
|
float_up();
|
|
incr_itimeout(&HLevitation, d(10, 20));
|
|
makeknown(typ);
|
|
}
|
|
break;
|
|
} /* inner switch */
|
|
break; /* default case of outer switch */
|
|
|
|
case RIN_ADORNMENT:
|
|
accessory_has_effect(otmp);
|
|
if (adjattrib(A_CHA, otmp->spe, -1))
|
|
makeknown(typ);
|
|
break;
|
|
case RIN_GAIN_STRENGTH:
|
|
accessory_has_effect(otmp);
|
|
if (adjattrib(A_STR, otmp->spe, -1))
|
|
makeknown(typ);
|
|
break;
|
|
case RIN_GAIN_CONSTITUTION:
|
|
accessory_has_effect(otmp);
|
|
if (adjattrib(A_CON, otmp->spe, -1))
|
|
makeknown(typ);
|
|
break;
|
|
case RIN_INCREASE_ACCURACY:
|
|
accessory_has_effect(otmp);
|
|
u.uhitinc = (schar) bounded_increase((int) u.uhitinc, otmp->spe,
|
|
RIN_INCREASE_ACCURACY);
|
|
break;
|
|
case RIN_INCREASE_DAMAGE:
|
|
accessory_has_effect(otmp);
|
|
u.udaminc = (schar) bounded_increase((int) u.udaminc, otmp->spe,
|
|
RIN_INCREASE_DAMAGE);
|
|
break;
|
|
case RIN_PROTECTION:
|
|
case AMULET_OF_GUARDING:
|
|
accessory_has_effect(otmp);
|
|
HProtection |= FROMOUTSIDE;
|
|
u.ublessed = bounded_increase(u.ublessed,
|
|
(typ == RIN_PROTECTION) ? otmp->spe
|
|
: 2, /* fixed amount for amulet */
|
|
typ);
|
|
disp.botl = TRUE;
|
|
break;
|
|
case RIN_FREE_ACTION:
|
|
/* Give sleep resistance instead */
|
|
if (!(HSleep_resistance & FROMOUTSIDE))
|
|
accessory_has_effect(otmp);
|
|
if (!Sleep_resistance)
|
|
You_feel("wide awake.");
|
|
HSleep_resistance |= FROMOUTSIDE;
|
|
break;
|
|
case AMULET_OF_CHANGE:
|
|
accessory_has_effect(otmp);
|
|
makeknown(typ);
|
|
change_sex();
|
|
You("are suddenly very %s!",
|
|
flags.female ? "feminine" : "masculine");
|
|
disp.botl = TRUE;
|
|
break;
|
|
case AMULET_OF_UNCHANGING:
|
|
/* un-change: it's a pun */
|
|
if (!Unchanging && Upolyd) {
|
|
accessory_has_effect(otmp);
|
|
makeknown(typ);
|
|
rehumanize();
|
|
}
|
|
break;
|
|
case AMULET_OF_STRANGULATION: /* bad idea! */
|
|
/* no message--this gives no permanent effect */
|
|
choke(otmp);
|
|
break;
|
|
case AMULET_OF_RESTFUL_SLEEP: { /* another bad idea! */
|
|
long newnap = (long) rnd(100), oldnap = (HSleepy & TIMEOUT);
|
|
|
|
if (!(HSleepy & FROMOUTSIDE))
|
|
accessory_has_effect(otmp);
|
|
HSleepy |= FROMOUTSIDE;
|
|
/* might also be wearing one; use shorter of two timeouts */
|
|
if (newnap < oldnap || oldnap == 0L)
|
|
HSleepy = (HSleepy & ~TIMEOUT) | newnap;
|
|
break;
|
|
}
|
|
case RIN_SUSTAIN_ABILITY:
|
|
case AMULET_OF_LIFE_SAVING:
|
|
case AMULET_OF_FLYING:
|
|
case AMULET_OF_REFLECTION: /* nice try */
|
|
/* can't eat Amulet of Yendor or fakes,
|
|
* and no oc_prop even if you could -3.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* called after eating non-food */
|
|
staticfn void
|
|
eatspecial(void)
|
|
{
|
|
struct obj *otmp = svc.context.victual.piece;
|
|
|
|
/* lesshungry wants an occupation to handle choke messages correctly */
|
|
set_occupation(eatfood, "eating non-food", 0);
|
|
lesshungry(svc.context.victual.nmod);
|
|
go.occupation = 0;
|
|
svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */
|
|
|
|
if (otmp->oclass == COIN_CLASS) {
|
|
if (carried(otmp))
|
|
useupall(otmp);
|
|
else
|
|
useupf(otmp, otmp->quan);
|
|
vault_gd_watching(GD_EATGOLD);
|
|
return;
|
|
}
|
|
if (objects[otmp->otyp].oc_material == PAPER) {
|
|
#ifdef MAIL_STRUCTURES
|
|
if (otmp->otyp == SCR_MAIL)
|
|
/* no nutrition */
|
|
pline("This junk mail is less than satisfying.");
|
|
else
|
|
#endif
|
|
if (otmp->otyp == SCR_SCARE_MONSTER)
|
|
/* to eat scroll, hero is currently polymorphed into a monster */
|
|
pline("Yuck%c", otmp->blessed ? '!' : '.');
|
|
else if (otmp->oclass == SCROLL_CLASS
|
|
/* check description after checking for specific scrolls */
|
|
&& objdescr_is(otmp, "YUM YUM"))
|
|
pline("Yum%c", otmp->blessed ? '!' : '.');
|
|
else
|
|
pline("Needs salt...");
|
|
}
|
|
if (otmp->oclass == POTION_CLASS) {
|
|
otmp->quan++; /* dopotion() does a useup() */
|
|
(void) dopotion(otmp);
|
|
} else if (otmp->oclass == RING_CLASS || otmp->oclass == AMULET_CLASS) {
|
|
eataccessory(otmp);
|
|
} else if (otmp->otyp == LEASH && otmp->leashmon) {
|
|
o_unleash(otmp);
|
|
}
|
|
|
|
/* KMH -- idea by "Tommy the Terrorist" */
|
|
if (otmp->otyp == TRIDENT && !otmp->cursed) {
|
|
/* sugarless chewing gum which used to be heavily advertised on TV */
|
|
pline(Hallucination ? "Four out of five dentists agree."
|
|
: "That was pure chewing satisfaction!");
|
|
exercise(A_WIS, TRUE);
|
|
}
|
|
if (otmp->otyp == FLINT && !otmp->cursed) {
|
|
/* chewable vitamin for kids based on "The Flintstones" TV cartoon */
|
|
pline("Yabba-dabba delicious!");
|
|
exercise(A_CON, TRUE);
|
|
}
|
|
|
|
if (otmp == uwep && otmp->quan == 1L)
|
|
uwepgone();
|
|
if (otmp == uquiver && otmp->quan == 1L)
|
|
uqwepgone();
|
|
if (otmp == uswapwep && otmp->quan == 1L)
|
|
uswapwepgone();
|
|
|
|
if (otmp == uball)
|
|
unpunish();
|
|
if (otmp == uchain)
|
|
unpunish(); /* but no useup() */
|
|
else if (carried(otmp))
|
|
useup(otmp);
|
|
else
|
|
useupf(otmp, 1L);
|
|
}
|
|
|
|
/* NOTE: the order of these words exactly corresponds to the
|
|
order of oc_material values #define'd in objclass.h. */
|
|
static const char *const foodwords[] = {
|
|
"meal", "liquid", "wax", "food", "meat", "paper",
|
|
"cloth", "leather", "wood", "bone", "scale", "metal",
|
|
"metal", "metal", "silver", "gold", "platinum", "mithril",
|
|
"plastic", "glass", "rich food", "stone"
|
|
};
|
|
|
|
staticfn const char *
|
|
foodword(struct obj *otmp)
|
|
{
|
|
if (otmp->oclass == FOOD_CLASS)
|
|
return "food";
|
|
if (otmp->oclass == GEM_CLASS && objects[otmp->otyp].oc_material == GLASS
|
|
&& otmp->dknown)
|
|
makeknown(otmp->otyp);
|
|
return foodwords[objects[otmp->otyp].oc_material];
|
|
}
|
|
|
|
/* called after consuming (non-corpse) food */
|
|
staticfn void
|
|
fpostfx(struct obj *otmp)
|
|
{
|
|
switch (otmp->otyp) {
|
|
case SPRIG_OF_WOLFSBANE:
|
|
if (ismnum(u.ulycn) || is_were(gy.youmonst.data))
|
|
you_unwere(TRUE);
|
|
break;
|
|
case CARROT:
|
|
if (!u.uswallow
|
|
|| !attacktype_fordmg(u.ustuck->data, AT_ENGL, AD_BLND))
|
|
make_blinded((long) u.ucreamed, TRUE);
|
|
break;
|
|
case FORTUNE_COOKIE:
|
|
outrumor(bcsign(otmp), BY_COOKIE);
|
|
if (!Blind)
|
|
if (!u.uconduct.literate++)
|
|
livelog_printf(LL_CONDUCT,
|
|
"became literate by reading the fortune inside a cookie");
|
|
break;
|
|
case LUMP_OF_ROYAL_JELLY:
|
|
if (gy.youmonst.data == &mons[PM_KILLER_BEE] && !Unchanging
|
|
&& polymon(PM_QUEEN_BEE))
|
|
break;
|
|
|
|
/* This stuff seems to be VERY healthy! */
|
|
gainstr(otmp, 1, TRUE);
|
|
if (Upolyd) {
|
|
u.mh += otmp->cursed ? -rnd(20) : rnd(20), disp.botl = TRUE;
|
|
if (u.mh > u.mhmax) {
|
|
if (!rn2(17))
|
|
setuhpmax(u.mhmax + 1, FALSE);
|
|
u.mh = u.mhmax;
|
|
} else if (u.mh <= 0) {
|
|
rehumanize();
|
|
}
|
|
} else {
|
|
u.uhp += otmp->cursed ? -rnd(20) : rnd(20), disp.botl = TRUE;
|
|
if (u.uhp > u.uhpmax) {
|
|
if (!rn2(17))
|
|
setuhpmax(u.uhpmax + 1, FALSE);
|
|
u.uhp = u.uhpmax;
|
|
} else if (u.uhp <= 0) {
|
|
svk.killer.format = KILLED_BY_AN;
|
|
Strcpy(svk.killer.name, "rotten lump of royal jelly");
|
|
done(POISONING);
|
|
}
|
|
}
|
|
if (!otmp->cursed)
|
|
heal_legs(0);
|
|
break;
|
|
case EGG:
|
|
if (ismnum(otmp->corpsenm)
|
|
&& flesh_petrifies(&mons[otmp->corpsenm])) {
|
|
if (!Stone_resistance
|
|
&& !(poly_when_stoned(gy.youmonst.data)
|
|
&& polymon(PM_STONE_GOLEM))) {
|
|
if (!Stoned) {
|
|
Sprintf(svk.killer.name, "%s egg",
|
|
mons[otmp->corpsenm].pmnames[NEUTRAL]);
|
|
make_stoned(5L, (char *) 0, KILLED_BY_AN,
|
|
svk.killer.name);
|
|
}
|
|
}
|
|
/* note: no "tastes like chicken" message for eggs */
|
|
}
|
|
break;
|
|
case EUCALYPTUS_LEAF:
|
|
if (Sick && !otmp->cursed)
|
|
make_sick(0L, (char *) 0, TRUE, SICK_ALL);
|
|
if (Vomiting && !otmp->cursed)
|
|
make_vomiting(0L, TRUE);
|
|
break;
|
|
case APPLE:
|
|
if (otmp->cursed && !Sleep_resistance) {
|
|
/* Snow White; 'poisoned' applies to [a subset of] weapons,
|
|
not food, so we substitute cursed; fortunately our hero
|
|
won't have to wait for a prince to be rescued/revived */
|
|
if (Race_if(PM_DWARF) && Hallucination) {
|
|
verbalize("Heigh-ho, ho-hum, I think I'll skip work today.");
|
|
} else if (Deaf || !flags.acoustics) {
|
|
You("fall asleep.");
|
|
} else {
|
|
Soundeffect(se_sinister_laughter, 100);
|
|
You_hear("sinister laughter as you fall asleep...");
|
|
}
|
|
fall_asleep(-rn1(11, 20), TRUE);
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
/* intended for eating a spellbook while polymorphed, but not used;
|
|
"leather" applied to appearance, not composition, and has been
|
|
changed to "leathery" to reflect that */
|
|
staticfn boolean leather_cover(struct obj *);
|
|
|
|
staticfn boolean
|
|
leather_cover(struct obj *otmp)
|
|
{
|
|
const char *odesc = OBJ_DESCR(objects[otmp->otyp]);
|
|
|
|
if (odesc && (otmp->oclass == SPBOOK_CLASS)) {
|
|
if (!strcmp(odesc, "leather"))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* return 0 if the food was not dangerous.
|
|
* return 1 if the food was dangerous and you chose to stop.
|
|
* return 2 if the food was dangerous and you chose to eat it anyway.
|
|
*/
|
|
staticfn int
|
|
edibility_prompts(struct obj *otmp)
|
|
{
|
|
/* Blessed food detection grants hero a one-use
|
|
* ability to detect food that is unfit for consumption
|
|
* or dangerous and avoid it.
|
|
*/
|
|
char buf[BUFSZ], foodsmell[BUFSZ],
|
|
it_or_they[QBUFSZ];
|
|
/* 3.7: decaying globs don't become tainted anymore; in 3.6, they did */
|
|
boolean cadaver = (otmp->otyp == CORPSE), stoneorslime = FALSE;
|
|
int material = objects[otmp->otyp].oc_material, mnum = otmp->corpsenm;
|
|
long rotted = 0L;
|
|
|
|
Strcpy(foodsmell, Tobjnam(otmp, "smell"));
|
|
Strcpy(it_or_they, (otmp->quan == 1L) ? "it" : "they");
|
|
|
|
if (cadaver || otmp->otyp == EGG || otmp->otyp == TIN
|
|
|| otmp->otyp == GLOB_OF_GREEN_SLIME) {
|
|
/* These checks must match those in eatcorpse() */
|
|
stoneorslime = (ismnum(mnum)
|
|
&& flesh_petrifies(&mons[mnum])
|
|
&& !Stone_resistance
|
|
&& !poly_when_stoned(gy.youmonst.data));
|
|
|
|
if (mnum == PM_GREEN_SLIME || otmp->otyp == GLOB_OF_GREEN_SLIME)
|
|
stoneorslime = (!Unchanging && !slimeproof(gy.youmonst.data));
|
|
|
|
if (cadaver && !nonrotting_corpse(mnum)) {
|
|
long age = peek_at_iced_corpse_age(otmp);
|
|
|
|
/* worst case rather than random
|
|
in this calculation to force prompt */
|
|
rotted = (svm.moves - age) / (10L + 0 /* was rn2(20) */);
|
|
if (otmp->cursed)
|
|
rotted += 2L;
|
|
else if (otmp->blessed)
|
|
rotted -= 2L;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* These problems with food should be checked in
|
|
* order from most detrimental to least detrimental.
|
|
*/
|
|
buf[0] = '\0';
|
|
if (cadaver && rotted > 5L && !Sick_resistance) {
|
|
/* Tainted meat */
|
|
Snprintf(buf, sizeof buf, "%s like %s could be tainted!",
|
|
foodsmell, it_or_they);
|
|
} else if (stoneorslime) {
|
|
Snprintf(buf, sizeof buf,
|
|
"%s like %s could be something very dangerous!",
|
|
foodsmell, it_or_they);
|
|
} else if (cadaver && rotted > 5L && Sick_resistance) {
|
|
/* Tainted meat with Sick_resistance (testing for that is
|
|
redundant; we don't get this far for !Sick_resistance)
|
|
needs to be done now even though there is no danger because
|
|
it can't match after the rotten (cadaver && rotted > 3) test */
|
|
Snprintf(buf, sizeof buf, "%s like %s could be tainted.",
|
|
foodsmell, it_or_they);
|
|
} else if (otmp->orotten || (cadaver && rotted > 3L)) {
|
|
/* Rotten */
|
|
Snprintf(buf, sizeof buf, "%s like %s could be rotten!",
|
|
foodsmell, it_or_they);
|
|
} else if (cadaver && poisonous(&mons[mnum]) && !Poison_resistance) {
|
|
/* poisonous */
|
|
Snprintf(buf, sizeof buf, "%s like %s might be poisonous!",
|
|
foodsmell, it_or_they);
|
|
} else if (otmp->otyp == APPLE && otmp->cursed && !Sleep_resistance) {
|
|
/* causes sleep, for long enough to be dangerous */
|
|
Snprintf(buf, sizeof buf, "%s like %s might have been poisoned.",
|
|
foodsmell, it_or_they);
|
|
} else if (cadaver && !vegetarian(&mons[mnum])
|
|
&& !u.uconduct.unvegetarian && Role_if(PM_MONK)) {
|
|
Snprintf(buf, sizeof buf, "%s unhealthy.", foodsmell);
|
|
} else if (cadaver && acidic(&mons[mnum]) && !Acid_resistance) {
|
|
Snprintf(buf, sizeof buf, "%s rather acidic.", foodsmell);
|
|
} else if (Upolyd && u.umonnum == PM_RUST_MONSTER && is_metallic(otmp)
|
|
&& otmp->oerodeproof) {
|
|
Snprintf(buf, sizeof buf, "%s disgusting to you right now.",
|
|
foodsmell);
|
|
|
|
/*
|
|
* Breaks conduct, but otherwise safe.
|
|
*/
|
|
} else if (!u.uconduct.unvegan
|
|
&& ((material == LEATHER || material == BONE
|
|
|| material == DRAGON_HIDE || material == WAX)
|
|
|| (cadaver && !vegan(&mons[mnum])))) {
|
|
Snprintf(buf, sizeof buf, "%s foul and unfamiliar to you.",
|
|
foodsmell);
|
|
} else if (!u.uconduct.unvegetarian
|
|
&& ((material == LEATHER || material == BONE
|
|
|| material == DRAGON_HIDE)
|
|
|| (cadaver && !vegetarian(&mons[mnum])))) {
|
|
Snprintf(buf, sizeof buf, "%s unfamiliar to you.", foodsmell);
|
|
}
|
|
|
|
if (*buf) {
|
|
Snprintf(eos(buf), sizeof buf - strlen(buf), " Eat %s anyway?",
|
|
(otmp->quan == 1L) ? "it" : "one");
|
|
return (yn_function(buf, ynchars, 'n', TRUE) == 'n') ? 1 : 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
staticfn int
|
|
doeat_nonfood(struct obj *otmp)
|
|
{
|
|
int basenutrit; /* nutrition of full item */
|
|
int ll_conduct = 0;
|
|
boolean nodelicious = FALSE;
|
|
int material;
|
|
|
|
svc.context.victual.reqtime = 1;
|
|
svc.context.victual.piece = otmp;
|
|
svc.context.victual.o_id = otmp->o_id;
|
|
/* Don't split it, we don't need to if it's 1 move */
|
|
svc.context.victual.usedtime = 0;
|
|
svc.context.victual.canchoke = (u.uhs == SATIATED);
|
|
/* Note: gold weighs 1 pt. for each 1000 pieces (see
|
|
pickup.c) so gold and non-gold is consistent. */
|
|
if (otmp->oclass == COIN_CLASS)
|
|
basenutrit = ((otmp->quan > 200000L) ? 2000
|
|
: (int) (otmp->quan / 100L));
|
|
else if (otmp->oclass == BALL_CLASS || otmp->oclass == CHAIN_CLASS)
|
|
basenutrit = weight(otmp);
|
|
/* oc_nutrition is usually weight anyway */
|
|
else
|
|
basenutrit = objects[otmp->otyp].oc_nutrition;
|
|
#ifdef MAIL_STRUCTURES
|
|
if (otmp->otyp == SCR_MAIL) {
|
|
basenutrit = 0;
|
|
nodelicious = TRUE;
|
|
}
|
|
#endif
|
|
svc.context.victual.nmod = basenutrit;
|
|
svc.context.victual.eating = 1; /* needed for lesshungry() */
|
|
|
|
if (!u.uconduct.food++) {
|
|
ll_conduct++;
|
|
livelog_printf(LL_CONDUCT, "ate for the first time (%s)",
|
|
food_xname(otmp, FALSE));
|
|
}
|
|
material = objects[otmp->otyp].oc_material;
|
|
if (material == LEATHER || material == BONE
|
|
|| material == DRAGON_HIDE || material == WAX) {
|
|
if (!u.uconduct.unvegan++ && !ll_conduct) {
|
|
livelog_printf(LL_CONDUCT,
|
|
"consumed animal products for the first time, by eating %s",
|
|
an(food_xname(otmp, FALSE)));
|
|
ll_conduct++;
|
|
}
|
|
if (material != WAX) {
|
|
if (!u.uconduct.unvegetarian && !ll_conduct)
|
|
livelog_printf(LL_CONDUCT,
|
|
"tasted meat by-products for the first time, by eating %s",
|
|
an(food_xname(otmp, FALSE)));
|
|
violated_vegetarian();
|
|
}
|
|
}
|
|
|
|
if (otmp->cursed) {
|
|
(void) rottenfood(otmp);
|
|
nodelicious = TRUE;
|
|
} else if (objects[otmp->otyp].oc_material == PAPER)
|
|
nodelicious = TRUE;
|
|
|
|
if (otmp->oclass == WEAPON_CLASS && otmp->opoisoned) {
|
|
pline("Ecch - that must have been poisonous!");
|
|
if (!Poison_resistance) {
|
|
poison_strdmg(rnd(4), rnd(15), xname(otmp), KILLED_BY_AN);
|
|
} else
|
|
You("seem unaffected by the poison.");
|
|
} else if (!nodelicious) {
|
|
pline("%s%s is delicious!",
|
|
(obj_is_pname(otmp)
|
|
&& otmp->oartifact < ART_ORB_OF_DETECTION)
|
|
? ""
|
|
: "This ",
|
|
(otmp->oclass == COIN_CLASS)
|
|
? foodword(otmp)
|
|
: singular(otmp, xname));
|
|
}
|
|
eatspecial();
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* the #eat command */
|
|
int
|
|
doeat(void)
|
|
{
|
|
struct obj *otmp;
|
|
int basenutrit; /* nutrition of full item */
|
|
boolean dont_start = FALSE,
|
|
already_partly_eaten;
|
|
int ll_conduct = 0;
|
|
|
|
if (Strangled) {
|
|
pline("If you can't breathe air, how can you consume solids?");
|
|
return ECMD_OK;
|
|
}
|
|
if (!(otmp = floorfood("eat", 0)))
|
|
return ECMD_OK;
|
|
if (check_capacity((char *) 0))
|
|
return ECMD_OK;
|
|
|
|
if (u.uedibility) {
|
|
int res = edibility_prompts(otmp);
|
|
|
|
if (res) {
|
|
Your(
|
|
"%s stops tingling and your sense of smell returns to normal.",
|
|
body_part(NOSE));
|
|
u.uedibility = 0;
|
|
if (res == 1)
|
|
return ECMD_OK;
|
|
}
|
|
}
|
|
|
|
/* from floorfood(), &hands_obj means iron bars at current spot */
|
|
if (otmp == &hands_obj) {
|
|
/* hero in metallivore form is eating [diggable] iron bars
|
|
at current location so skip the other assorted checks;
|
|
operates as if digging rather than via the eat occupation */
|
|
if (still_chewing(u.ux, u.uy) && levl[u.ux][u.uy].typ == IRONBARS) {
|
|
/* this is verbose, but player will see the hero rather than the
|
|
bars so wouldn't know that more turns of eating are required */
|
|
You("pause to swallow.");
|
|
}
|
|
return ECMD_TIME;
|
|
}
|
|
/* We have to make non-foods take 1 move to eat, unless we want to
|
|
* do ridiculous amounts of coding to deal with partly eaten plate
|
|
* mails, players who polymorph back to human in the middle of their
|
|
* metallic meal, etc....
|
|
*/
|
|
if (!is_edible(otmp)) {
|
|
You("cannot eat that!");
|
|
return ECMD_OK;
|
|
} else if ((otmp->owornmask & (W_ARMOR | W_TOOL | W_AMUL | W_SADDLE))
|
|
!= 0) {
|
|
/* let them eat rings */
|
|
You_cant("eat %s you're wearing.", something);
|
|
return ECMD_OK;
|
|
} else if (!(carried(otmp) ? retouch_object(&otmp, FALSE)
|
|
: touch_artifact(otmp, &gy.youmonst))) {
|
|
return ECMD_TIME; /* got blasted so use a turn */
|
|
}
|
|
if (is_metallic(otmp) && u.umonnum == PM_RUST_MONSTER
|
|
&& otmp->oerodeproof) {
|
|
otmp->rknown = TRUE;
|
|
if (otmp->quan > 1L) {
|
|
if (!carried(otmp))
|
|
(void) splitobj(otmp, otmp->quan - 1L);
|
|
else
|
|
otmp = splitobj(otmp, 1L);
|
|
}
|
|
pline("Ulch - that %s was rustproofed!", xname(otmp));
|
|
/* The regurgitated object's rustproofing is gone now */
|
|
otmp->oerodeproof = 0;
|
|
make_stunned((HStun & TIMEOUT) + (long) rn2(10), TRUE);
|
|
/*
|
|
* We don't expect rust monsters to be wielding welded weapons
|
|
* or wearing cursed rings which were rustproofed, but guard
|
|
* against the possibility just in case.
|
|
*/
|
|
if (welded(otmp) || (otmp->cursed && (otmp->owornmask & W_RING))) {
|
|
set_bknown(otmp, 1); /* for ring; welded() does this for weapon */
|
|
You("spit out %s.", the(xname(otmp)));
|
|
} else {
|
|
You("spit %s out onto the %s.", the(xname(otmp)),
|
|
surface(u.ux, u.uy));
|
|
if (carried(otmp)) {
|
|
/* no need to check for leash in use; it's not metallic */
|
|
if (otmp->owornmask)
|
|
remove_worn_item(otmp, FALSE);
|
|
freeinv(otmp);
|
|
dropy(otmp);
|
|
}
|
|
stackobj(otmp);
|
|
}
|
|
return ECMD_TIME;
|
|
}
|
|
/* KMH -- Slow digestion is... indigestible */
|
|
if (otmp->otyp == RIN_SLOW_DIGESTION) {
|
|
pline("This ring is indigestible!");
|
|
(void) rottenfood(otmp);
|
|
if (otmp->dknown)
|
|
trycall(otmp);
|
|
return ECMD_TIME;
|
|
}
|
|
if (otmp->oclass != FOOD_CLASS)
|
|
return doeat_nonfood(otmp);
|
|
|
|
|
|
if (otmp == svc.context.victual.piece) {
|
|
boolean one_bite_left = (svc.context.victual.usedtime + 1
|
|
>= svc.context.victual.reqtime);
|
|
|
|
/* If they weren't able to choke, they don't suddenly become able to
|
|
* choke just because they were interrupted. On the other hand, if
|
|
* they were able to choke before, if they lost food it's possible
|
|
* they shouldn't be able to choke now.
|
|
*/
|
|
if (u.uhs != SATIATED)
|
|
svc.context.victual.canchoke = 0;
|
|
svc.context.victual.o_id = 0;
|
|
otmp = touchfood(otmp);
|
|
if (otmp) {
|
|
svc.context.victual.piece = otmp;
|
|
svc.context.victual.o_id = otmp->o_id;
|
|
} else {
|
|
do_reset_eat();
|
|
}
|
|
/* if there's only one bite left, there sometimes won't be any
|
|
"you finish eating" message when done; use different wording
|
|
for resuming with one bite remaining instead of trying to
|
|
determine whether or not "you finish" is going to be given */
|
|
You("%s your meal.",
|
|
!one_bite_left ? "resume" : "consume the last bite of");
|
|
if (otmp)
|
|
start_eating(otmp, FALSE);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* nothing in progress - so try to find something. */
|
|
/* tins are a special case */
|
|
/* tins must also check conduct separately in case they're discarded */
|
|
if (otmp->otyp == TIN) {
|
|
start_tin(otmp);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* KMH, conduct */
|
|
if (!u.uconduct.food++) {
|
|
livelog_printf(LL_CONDUCT, "ate for the first time - %s",
|
|
food_xname(otmp, FALSE));
|
|
ll_conduct++;
|
|
}
|
|
|
|
already_partly_eaten = otmp->oeaten ? TRUE : FALSE;
|
|
otmp = touchfood(otmp);
|
|
if (otmp) {
|
|
svc.context.victual.piece = otmp;
|
|
svc.context.victual.o_id = otmp->o_id;
|
|
svc.context.victual.usedtime = 0;
|
|
} else {
|
|
do_reset_eat();
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* Now we need to calculate delay and nutritional info.
|
|
* The base nutrition calculated here and in eatcorpse() accounts
|
|
* for normal vs. rotten food. The reqtime and nutrit values are
|
|
* then adjusted in accordance with the amount of food left.
|
|
*/
|
|
if (otmp->otyp == CORPSE || otmp->globby) {
|
|
int tmp = eatcorpse(otmp);
|
|
|
|
if (tmp == 2) {
|
|
/* used up */
|
|
svc.context.victual = zero_victual; /* victual.piece=0, .o_id=0 */
|
|
return ECMD_TIME;
|
|
} else if (tmp)
|
|
dont_start = TRUE;
|
|
/* if not used up, eatcorpse sets up reqtime and may modify oeaten */
|
|
} else {
|
|
/* No checks for WAX, LEATHER, BONE, DRAGON_HIDE. These are
|
|
* all handled in the != FOOD_CLASS case, above.
|
|
*/
|
|
switch (objects[otmp->otyp].oc_material) {
|
|
case FLESH:
|
|
if (!u.uconduct.unvegan++ && !ll_conduct) {
|
|
livelog_printf(LL_CONDUCT,
|
|
"consumed animal products for the first time, by eating %s",
|
|
an(food_xname(otmp, FALSE)));
|
|
ll_conduct++;
|
|
}
|
|
if (otmp->otyp != EGG) {
|
|
if (!u.uconduct.unvegetarian && !ll_conduct)
|
|
livelog_printf(LL_CONDUCT,
|
|
"tasted meat for the first time, by eating %s",
|
|
an(food_xname(otmp, FALSE)));
|
|
|
|
violated_vegetarian();
|
|
}
|
|
break;
|
|
default:
|
|
if (otmp->otyp == PANCAKE || otmp->otyp == FORTUNE_COOKIE /*eggs*/
|
|
|| otmp->otyp == CREAM_PIE || otmp->otyp == CANDY_BAR /*milk*/
|
|
|| otmp->otyp == LUMP_OF_ROYAL_JELLY)
|
|
if (!u.uconduct.unvegan++ && !ll_conduct)
|
|
livelog_printf(LL_CONDUCT,
|
|
"consumed animal products (%s) for the first time",
|
|
food_xname(otmp, FALSE));
|
|
break;
|
|
}
|
|
|
|
svc.context.victual.reqtime = objects[otmp->otyp].oc_delay;
|
|
if (otmp->otyp != FORTUNE_COOKIE
|
|
&& (otmp->cursed || (!nonrotting_food(otmp->otyp)
|
|
&& (svm.moves - otmp->age)
|
|
> (otmp->blessed ? 50L : 30L)
|
|
&& (otmp->orotten || !rn2(7))))) {
|
|
if (rottenfood(otmp)) {
|
|
otmp->orotten = TRUE;
|
|
dont_start = TRUE;
|
|
}
|
|
consume_oeaten(otmp, 1); /* oeaten >>= 1 */
|
|
} else if (!already_partly_eaten) {
|
|
if (!fprefx(otmp)) {
|
|
do_reset_eat();
|
|
return ECMD_TIME;
|
|
}
|
|
} else {
|
|
You("%s %s.",
|
|
(svc.context.victual.reqtime == 1) ? "eat" : "begin eating",
|
|
doname(otmp));
|
|
}
|
|
}
|
|
|
|
/* re-calc the nutrition */
|
|
basenutrit = (int) obj_nutrition(otmp);
|
|
|
|
debugpline3(
|
|
"before rounddiv: victual.reqtime == %d, oeaten == %d, basenutrit == %d",
|
|
svc.context.victual.reqtime, otmp->oeaten, basenutrit);
|
|
|
|
svc.context.victual.reqtime
|
|
= (basenutrit == 0) ? 0
|
|
: rounddiv(svc.context.victual.reqtime * (long) otmp->oeaten,
|
|
basenutrit);
|
|
|
|
debugpline1("after rounddiv: victual.reqtime == %d",
|
|
svc.context.victual.reqtime);
|
|
/*
|
|
* calculate the modulo value (nutrit. units per round eating)
|
|
* note: this isn't exact - you actually lose a little nutrition due
|
|
* to this method.
|
|
* TODO: add in a "remainder" value to be given at the end of the meal.
|
|
*/
|
|
if (svc.context.victual.reqtime == 0 || otmp->oeaten == 0)
|
|
/* possible if most has been eaten before */
|
|
svc.context.victual.nmod = 0;
|
|
else if ((int) otmp->oeaten >= svc.context.victual.reqtime)
|
|
svc.context.victual.nmod = -((int) otmp->oeaten
|
|
/ svc.context.victual.reqtime);
|
|
else
|
|
svc.context.victual.nmod = svc.context.victual.reqtime % otmp->oeaten;
|
|
svc.context.victual.canchoke = (u.uhs == SATIATED);
|
|
|
|
if (!dont_start)
|
|
start_eating(otmp, already_partly_eaten);
|
|
else
|
|
otmp->owt = weight(otmp);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* getobj callback for object to be opened with a tin opener */
|
|
staticfn int
|
|
tinopen_ok(struct obj *obj)
|
|
{
|
|
if (obj && obj->otyp == TIN)
|
|
return GETOBJ_SUGGEST;
|
|
|
|
return GETOBJ_EXCLUDE;
|
|
}
|
|
|
|
|
|
int
|
|
use_tin_opener(struct obj *obj)
|
|
{
|
|
struct obj *otmp;
|
|
int res = ECMD_OK;
|
|
|
|
if (!carrying(TIN)) {
|
|
You("have no tin to open.");
|
|
return ECMD_OK;
|
|
}
|
|
|
|
if (obj != uwep) {
|
|
if (obj->cursed && obj->bknown) {
|
|
char qbuf[QBUFSZ];
|
|
|
|
if (ynq(safe_qbuf(qbuf, "Really wield ", "?",
|
|
obj, doname, thesimpleoname, "that")) != 'y')
|
|
return ECMD_OK;
|
|
}
|
|
if (!wield_tool(obj, "use"))
|
|
return ECMD_OK;
|
|
res = ECMD_TIME;
|
|
}
|
|
|
|
otmp = getobj("open", tinopen_ok, GETOBJ_NOFLAGS);
|
|
if (!otmp)
|
|
return (res|ECMD_CANCEL);
|
|
|
|
start_tin(otmp);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* Take a single bite from a piece of food, checking for choking and
|
|
* modifying usedtime. Returns 1 if they choked and survived, 0 otherwise.
|
|
*/
|
|
staticfn int
|
|
bite(void)
|
|
{
|
|
/* hack to pacify static analyzer incorporated into gcc 12.2 */
|
|
sa_victual(&svc.context.victual);
|
|
|
|
if (svc.context.victual.canchoke && u.uhunger >= 2000) {
|
|
choke(svc.context.victual.piece);
|
|
return 1;
|
|
}
|
|
if (svc.context.victual.doreset) {
|
|
do_reset_eat();
|
|
return 0;
|
|
}
|
|
gf.force_save_hs = TRUE;
|
|
if (svc.context.victual.nmod < 0) {
|
|
lesshungry(adj_victual_nutrition(/*-svc.context.victual.nmod*/));
|
|
consume_oeaten(svc.context.victual.piece,
|
|
svc.context.victual.nmod); /* -= -nmod */
|
|
} else if (svc.context.victual.nmod > 0
|
|
&& (svc.context.victual.usedtime % svc.context.victual.nmod)) {
|
|
lesshungry(1);
|
|
consume_oeaten(svc.context.victual.piece, -1); /* -= 1 */
|
|
}
|
|
gf.force_save_hs = FALSE;
|
|
recalc_wt();
|
|
return 0;
|
|
}
|
|
|
|
/* as time goes by - called by moveloop(every move) & domove(melee attack) */
|
|
void
|
|
gethungry(void)
|
|
{
|
|
int accessorytime;
|
|
|
|
if (u.uinvulnerable || iflags.debug_hunger)
|
|
return; /* you don't feel hungrier */
|
|
|
|
/* being polymorphed into a creature which doesn't eat prevents
|
|
this first uhunger decrement, but to stay in such form the hero
|
|
will need to wear an Amulet of Unchanging so still burn a small
|
|
amount of nutrition in the 'moves % 20' ring/amulet check below */
|
|
if ((!Unaware || !rn2(10)) /* slow metabolic rate while asleep */
|
|
&& (carnivorous(gy.youmonst.data)
|
|
|| herbivorous(gy.youmonst.data)
|
|
|| metallivorous(gy.youmonst.data))
|
|
&& !Slow_digestion)
|
|
u.uhunger--; /* ordinary food consumption */
|
|
|
|
/*
|
|
* 3.7: trigger is randomized instead of (moves % N). Makes
|
|
* ring juggling (using the 'time' option to see the turn counter
|
|
* in order to time swapping of a pair of rings of slow digestion,
|
|
* wearing one on one hand, then putting on the other and taking
|
|
* off the first, then vice versa, over and over and over and ...
|
|
* to avoid any hunger from wearing a ring) become ineffective.
|
|
* Also causes melee-induced hunger to vary from turn-based hunger
|
|
* instead of just replicating that.
|
|
*/
|
|
accessorytime = rn2(20); /* rn2(20) replaces (int) (svm.moves % 20L) */
|
|
if (accessorytime % 2) { /* odd */
|
|
/* Regeneration uses up food, unless due to an artifact */
|
|
if ((HRegeneration & ~FROMFORM)
|
|
|| (ERegeneration & ~(W_ARTI | W_WEP)))
|
|
u.uhunger--;
|
|
if (near_capacity() > SLT_ENCUMBER)
|
|
u.uhunger--;
|
|
} else { /* even */
|
|
if (Hunger)
|
|
u.uhunger--;
|
|
/* Conflict uses up food too */
|
|
if (HConflict || (EConflict & (~W_ARTI)))
|
|
u.uhunger--;
|
|
/*
|
|
* +0 charged rings don't do anything, so don't affect hunger.
|
|
* Slow digestion cancels movement and melee hunger but still
|
|
* causes ring hunger.
|
|
* Possessing the real Amulet imposes a separate hunger penalty
|
|
* from wearing an amulet (so gets a double penalty when worn).
|
|
*
|
|
* 3.7.0: Worn meat rings don't affect hunger.
|
|
* Same with worn cheap plastic imitation of the Amulet.
|
|
* +0 ring of protection might do something (enhanced "magical
|
|
* cancellation") if hero doesn't have protection from some
|
|
* other source (cloak or second ring).
|
|
*
|
|
* [If wearing duplicate rings whose effects don't stack,
|
|
* should they both consume nutrition, or just one of them?
|
|
* Two +0 rings of protection are treated as if only one,
|
|
* but this could apply to most rings.]
|
|
*/
|
|
switch (accessorytime) { /* note: use even cases among 0..19 only */
|
|
case 0:
|
|
/* 3.7: if not wearing a ring of slow digestion, obtaining
|
|
that property from worn armor (white dragon scales/mail)
|
|
causes the armor to burn nutrition; since it's not
|
|
actually a ring, we don't check for it on the ring
|
|
turns; because of that, wearing two (non-slow digestion)
|
|
rings plus the armor consumes more nutrition that one
|
|
non-slow digestion ring plus ring of slow digestion */
|
|
if (Slow_digestion
|
|
&& (!uright || uright->otyp != RIN_SLOW_DIGESTION)
|
|
&& (!uleft || uleft->otyp != RIN_SLOW_DIGESTION))
|
|
u.uhunger--;
|
|
break;
|
|
case 4:
|
|
if (uleft && uleft->otyp != MEAT_RING
|
|
/* more hungry if +/- is nonzero or +/- doesn't apply or
|
|
+0 ring of protection is only source of protection;
|
|
need to check whether both rings are +0 protection or
|
|
they'd both slip by the "is there another source?" test,
|
|
but don't do that for both rings or they will both be
|
|
treated as supplying "MC" when only one matters;
|
|
note: amulet of guarding overrides both +0 rings and
|
|
is caught by the (EProtection & ~W_RINGx) == 0L tests */
|
|
&& (uleft->spe
|
|
|| !objects[uleft->otyp].oc_charged
|
|
|| (uleft->otyp == RIN_PROTECTION
|
|
&& ((EProtection & ~W_RINGL) == 0L
|
|
|| ((EProtection & ~W_RINGL) == W_RINGR
|
|
&& uright && uright->otyp == RIN_PROTECTION
|
|
&& !uright->spe)))))
|
|
u.uhunger--;
|
|
break;
|
|
case 8:
|
|
if (uamul && uamul->otyp != FAKE_AMULET_OF_YENDOR)
|
|
u.uhunger--;
|
|
break;
|
|
case 12:
|
|
if (uright && uright->otyp != MEAT_RING
|
|
&& (uright->spe
|
|
|| !objects[uright->otyp].oc_charged
|
|
|| (uright->otyp == RIN_PROTECTION
|
|
&& (EProtection & ~W_RINGR) == 0L)))
|
|
u.uhunger--;
|
|
break;
|
|
case 16:
|
|
if (u.uhave.amulet)
|
|
u.uhunger--;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
newuhs(TRUE);
|
|
}
|
|
|
|
/* called after vomiting and after performing feats of magic */
|
|
void
|
|
morehungry(int num)
|
|
{
|
|
u.uhunger -= num;
|
|
newuhs(TRUE);
|
|
}
|
|
|
|
/* called after eating (and after drinking fruit juice) */
|
|
void
|
|
lesshungry(int num)
|
|
{
|
|
/* See comments in newuhs() for discussion on force_save_hs */
|
|
boolean iseating = (go.occupation == eatfood) || gf.force_save_hs;
|
|
|
|
debugpline1("lesshungry(%d)", num);
|
|
u.uhunger += num;
|
|
if (u.uhunger >= 2000) {
|
|
if (!iseating || svc.context.victual.canchoke) {
|
|
if (iseating) {
|
|
choke(svc.context.victual.piece);
|
|
reset_eat();
|
|
} else {
|
|
choke((go.occupation == opentin) ? svc.context.tin.tin : 0);
|
|
/* no reset_eat() */
|
|
}
|
|
}
|
|
} else {
|
|
/* Have lesshungry() report when you're nearly full so all eating
|
|
* warns when you're about to choke.
|
|
*/
|
|
if (u.uhunger >= 1500 && !Hunger
|
|
&& (!svc.context.victual.eating
|
|
|| (svc.context.victual.eating
|
|
&& !svc.context.victual.fullwarn))) {
|
|
pline("You're having a hard time getting all of it down.");
|
|
gn.nomovemsg = "You're finally finished.";
|
|
if (!svc.context.victual.eating) {
|
|
gm.multi = -2;
|
|
} else {
|
|
svc.context.victual.fullwarn = 1;
|
|
if (svc.context.victual.canchoke
|
|
&& (svc.context.victual.reqtime
|
|
- svc.context.victual.usedtime) > 1) {
|
|
/* food with one bite left will not survive a stop */
|
|
if (!paranoid_query(ParanoidEating, "Continue eating?")) {
|
|
reset_eat();
|
|
gn.nomovemsg = (char *) 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
newuhs(FALSE);
|
|
}
|
|
|
|
staticfn int
|
|
unfaint(void)
|
|
{
|
|
(void) Hear_again();
|
|
if (u.uhs > FAINTING)
|
|
u.uhs = FAINTING;
|
|
stop_occupation();
|
|
disp.botl = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
boolean
|
|
is_fainted(void)
|
|
{
|
|
return (boolean) (u.uhs == FAINTED);
|
|
}
|
|
|
|
/* call when a faint must be prematurely terminated */
|
|
void
|
|
reset_faint(void)
|
|
{
|
|
if (ga.afternmv == unfaint)
|
|
unmul("You revive.");
|
|
}
|
|
|
|
/* compute and comment on your (new?) hunger status */
|
|
void
|
|
newuhs(boolean incr)
|
|
{
|
|
unsigned newhs;
|
|
static unsigned save_hs;
|
|
static boolean saved_hs = FALSE;
|
|
int h = u.uhunger;
|
|
|
|
newhs = (h > 1000)
|
|
? SATIATED
|
|
: (h > 150) ? NOT_HUNGRY
|
|
: (h > 50) ? HUNGRY : (h > 0) ? WEAK : FAINTING;
|
|
|
|
/* While you're eating, you may pass from WEAK to HUNGRY to NOT_HUNGRY.
|
|
* This should not produce the message "you only feel hungry now";
|
|
* that message should only appear if HUNGRY is an endpoint. Therefore
|
|
* we check to see if we're in the middle of eating. If so, we save
|
|
* the first hunger status, and at the end of eating we decide what
|
|
* message to print based on the _entire_ meal, not on each little bit.
|
|
*/
|
|
/* It is normally possible to check if you are in the middle of a meal
|
|
* by checking occupation == eatfood, but there is one special case:
|
|
* start_eating() can call bite() for your first bite before it
|
|
* sets the occupation.
|
|
* Anyone who wants to get that case to work _without_ an ugly static
|
|
* force_save_hs variable, feel free.
|
|
*/
|
|
/* Note: If you become a certain hunger status in the middle of the
|
|
* meal, and still have that same status at the end of the meal,
|
|
* this will incorrectly print the associated message at the end of
|
|
* the meal instead of the middle. Such a case is currently
|
|
* impossible, but could become possible if a message for SATIATED
|
|
* were added or if HUNGRY and WEAK were separated by a big enough
|
|
* gap to fit two bites.
|
|
*/
|
|
if (go.occupation == eatfood || gf.force_save_hs) {
|
|
if (!saved_hs) {
|
|
save_hs = u.uhs;
|
|
saved_hs = TRUE;
|
|
}
|
|
u.uhs = newhs;
|
|
return;
|
|
} else {
|
|
if (saved_hs) {
|
|
u.uhs = save_hs;
|
|
saved_hs = FALSE;
|
|
}
|
|
}
|
|
|
|
if (newhs == FAINTING) {
|
|
/* u,uhunger is likely to be negative at this point */
|
|
int uhunger_div_by_10 = sgn(u.uhunger) * ((abs(u.uhunger) + 5) / 10);
|
|
|
|
if (is_fainted())
|
|
newhs = FAINTED;
|
|
if (u.uhs <= WEAK || rn2(20 - uhunger_div_by_10) >= 19) {
|
|
if (!is_fainted() && gm.multi >= 0 /* %% */) {
|
|
int duration = 10 - uhunger_div_by_10;
|
|
|
|
/* stop what you're doing, then faint */
|
|
stop_occupation();
|
|
You("faint from lack of food.");
|
|
incr_itimeout(&HDeaf, duration);
|
|
disp.botl = TRUE;
|
|
nomul(-duration);
|
|
gm.multi_reason = "fainted from lack of food";
|
|
gn.nomovemsg = "You regain consciousness.";
|
|
ga.afternmv = unfaint;
|
|
newhs = FAINTED;
|
|
if (!Levitation)
|
|
selftouch("Falling, you");
|
|
}
|
|
|
|
/* this used to be -(200 + 20 * Con) but that was when being asleep
|
|
suppressed per-turn uhunger decrement but being fainted didn't;
|
|
now uhunger becomes more negative at a slower rate */
|
|
} else if (u.uhunger < -(100 + 10 * (int) ACURR(A_CON))) {
|
|
u.uhs = STARVED;
|
|
disp.botl = TRUE;
|
|
bot();
|
|
You("die from starvation.");
|
|
svk.killer.format = KILLED_BY;
|
|
Strcpy(svk.killer.name, "starvation");
|
|
done(STARVING);
|
|
/* if we return, we lifesaved, and that calls newuhs */
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (newhs != u.uhs) {
|
|
if (newhs >= WEAK && u.uhs < WEAK) {
|
|
/* this used to be losestr(1) which had the potential to
|
|
be fatal (still handled below) by reducing HP if it
|
|
tried to take base strength below minimum of 3 */
|
|
ATEMP(A_STR) = -1; /* temporary loss overrides Fixed_abil */
|
|
/* defer context.botl status update until after hunger message */
|
|
} else if (newhs < WEAK && u.uhs >= WEAK) {
|
|
/* this used to be losestr(-1) which could be abused by
|
|
becoming weak while wearing ring of sustain ability,
|
|
removing ring, eating to 'restore' strength which boosted
|
|
strength by a point each time the cycle was performed;
|
|
substituting "while polymorphed" for sustain ability and
|
|
"rehumanize" for ring removal might have done that too */
|
|
ATEMP(A_STR) = 0; /* repair of loss also overrides Fixed_abil */
|
|
/* defer context.botl status update until after hunger message */
|
|
}
|
|
|
|
switch (newhs) {
|
|
case HUNGRY:
|
|
if (Hallucination) {
|
|
You(!incr ? "now have a lesser case of the munchies."
|
|
: "are getting the munchies.");
|
|
} else
|
|
You("%s.", !incr ? "only feel hungry now"
|
|
: (u.uhunger < 145) ? "feel hungry"
|
|
: "are beginning to feel hungry");
|
|
if (incr && go.occupation
|
|
&& (go.occupation != eatfood && go.occupation != opentin))
|
|
stop_occupation();
|
|
end_running(TRUE);
|
|
break;
|
|
case WEAK:
|
|
if (Hallucination)
|
|
pline(!incr ? "You still have the munchies."
|
|
: "The munchies are interfering with your motor capabilities.");
|
|
else if (incr && (Role_if(PM_WIZARD) || Race_if(PM_ELF)
|
|
|| Role_if(PM_VALKYRIE)))
|
|
pline("%s needs food, badly!",
|
|
(Role_if(PM_WIZARD) || Role_if(PM_VALKYRIE))
|
|
? gu.urole.name.m
|
|
: "Elf");
|
|
else
|
|
You("%s weak.", !incr ? "are still"
|
|
: (u.uhunger < 45) ? "feel"
|
|
: "are beginning to feel");
|
|
if (incr && go.occupation
|
|
&& (go.occupation != eatfood && go.occupation != opentin))
|
|
stop_occupation();
|
|
end_running(TRUE);
|
|
break;
|
|
}
|
|
u.uhs = newhs;
|
|
disp.botl = TRUE;
|
|
bot();
|
|
if ((Upolyd ? u.mh : u.uhp) < 1) {
|
|
You("die from hunger and exhaustion.");
|
|
svk.killer.format = KILLED_BY;
|
|
Strcpy(svk.killer.name, "exhaustion");
|
|
done(STARVING);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* getobj callback for object to eat - effectively just wraps is_edible() */
|
|
staticfn int
|
|
eat_ok(struct obj *obj)
|
|
{
|
|
/* 'getobj_else' will be non-zero if floor food is present and
|
|
player declined to eat that; used to insert "else" into
|
|
"you don't have anything [else] to eat" if not carrying any food */
|
|
if (!obj)
|
|
return getobj_else ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE;
|
|
|
|
if (is_edible(obj))
|
|
return GETOBJ_SUGGEST;
|
|
|
|
/* make sure to exclude, not downplay, gold (if not is_edible) in order to
|
|
* produce the "You cannot eat gold" message in getobj */
|
|
if (obj->oclass == COIN_CLASS)
|
|
return GETOBJ_EXCLUDE;
|
|
|
|
return GETOBJ_EXCLUDE_SELECTABLE;
|
|
}
|
|
|
|
/* getobj callback for object to be offered (corpses and things that look like
|
|
* the Amulet only */
|
|
staticfn int
|
|
offer_ok(struct obj *obj)
|
|
{
|
|
if (!obj)
|
|
return getobj_else ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE;
|
|
|
|
if (obj->oclass != FOOD_CLASS && obj->oclass != AMULET_CLASS)
|
|
return GETOBJ_EXCLUDE;
|
|
|
|
if (obj->otyp != CORPSE && obj->otyp != AMULET_OF_YENDOR
|
|
&& obj->otyp != FAKE_AMULET_OF_YENDOR)
|
|
return GETOBJ_EXCLUDE_SELECTABLE;
|
|
|
|
/* suppress corpses on astral, amulets elsewhere
|
|
* (!astral && amulet) || (astral && !amulet) */
|
|
if (Is_astralevel(&u.uz) ^ (obj->oclass == AMULET_CLASS))
|
|
return GETOBJ_DOWNPLAY;
|
|
|
|
return GETOBJ_SUGGEST;
|
|
}
|
|
|
|
/* getobj callback for object to be tinned */
|
|
staticfn int
|
|
tin_ok(struct obj *obj)
|
|
{
|
|
if (!obj)
|
|
return getobj_else ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE;
|
|
|
|
if (obj->oclass != FOOD_CLASS)
|
|
return GETOBJ_EXCLUDE;
|
|
|
|
if (obj->otyp != CORPSE || !tinnable(obj))
|
|
return GETOBJ_EXCLUDE_SELECTABLE;
|
|
|
|
return GETOBJ_SUGGEST;
|
|
}
|
|
|
|
/* Returns an object representing food.
|
|
* Object may be either on floor or in inventory.
|
|
*/
|
|
struct obj *
|
|
floorfood(
|
|
const char *verb,
|
|
int corpsecheck) /* 0, no check, 1, corpses, 2, tinnable corpses */
|
|
{
|
|
struct obj *otmp;
|
|
char qbuf[QBUFSZ];
|
|
char c;
|
|
struct permonst *uptr = gy.youmonst.data;
|
|
boolean feeding = !strcmp(verb, "eat"), /* corpsecheck==0 */
|
|
offering = !strcmp(verb, "sacrifice"); /* corpsecheck==1 */
|
|
|
|
getobj_else = 0; /* haven't asked about floor food; is used to vary
|
|
* "you don't have anything [else] to eat" when
|
|
* floor food has been declined and inventory lacks
|
|
* any suitable items */
|
|
/* if we can't touch floor objects then use invent food only;
|
|
same when 'm' prefix is used--for #eat, it means "skip floor food" */
|
|
if (iflags.menu_requested
|
|
|| !can_reach_floor(TRUE) || (feeding && u.usteed)
|
|
|| (is_pool_or_lava(u.ux, u.uy)
|
|
&& (Wwalking || is_clinger(uptr) || (Flying && !Breathless))))
|
|
goto skipfloor;
|
|
|
|
if (feeding && metallivorous(uptr)) {
|
|
struct obj *gold;
|
|
struct trap *ttmp = t_at(u.ux, u.uy);
|
|
|
|
if (ttmp && ttmp->tseen && ttmp->ttyp == BEAR_TRAP) {
|
|
boolean u_in_beartrap = (u.utrap && u.utraptype == TT_BEARTRAP);
|
|
|
|
/* If not already stuck in the trap, perhaps there should
|
|
be a chance to becoming trapped? Probably not, because
|
|
then the trap would just get eaten on the _next_ turn... */
|
|
Sprintf(qbuf, "There is a bear trap here (%s); eat it?",
|
|
u_in_beartrap ? "holding you" : "armed");
|
|
if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y') {
|
|
struct obj *beartrap;
|
|
|
|
deltrap(ttmp);
|
|
if (u_in_beartrap)
|
|
reset_utrap(TRUE);
|
|
beartrap = mksobj(BEARTRAP, TRUE, FALSE);
|
|
Sprintf(qbuf,"You only manage to %s the bear trap.",
|
|
u_in_beartrap ? "free yourself from" : "disarm");
|
|
if (check_capacity(qbuf) && beartrap) {
|
|
obj_extract_self(beartrap);
|
|
dropy(beartrap); /* put it on the floor */
|
|
return (struct obj *) 0;
|
|
}
|
|
return beartrap;
|
|
} else if (c == 'q') {
|
|
return (struct obj *) 0;
|
|
}
|
|
++getobj_else;
|
|
}
|
|
if (levl[u.ux][u.uy].typ == IRONBARS) {
|
|
/* already verified that hero is metallivorous above */
|
|
boolean nodig = (levl[u.ux][u.uy].wall_info & W_NONDIGGABLE) != 0;
|
|
|
|
c = 'n';
|
|
Strcpy(qbuf, "There are iron bars here");
|
|
if (nodig || u.uhunger > 1500) {
|
|
pline("%s but you %s eat them.", qbuf,
|
|
nodig ? "cannot" : "are too full to");
|
|
} else {
|
|
Strcat(qbuf, (!svc.context.digging.chew
|
|
|| !u_at(svc.context.digging.pos.x,
|
|
svc.context.digging.pos.y)
|
|
|| !on_level(&svc.context.digging.level, &u.uz))
|
|
? "; eat them?"
|
|
: "; resume eating them?");
|
|
c = yn_function(qbuf, ynqchars, 'n', TRUE);
|
|
}
|
|
if (c == 'y')
|
|
return &hands_obj;
|
|
else if (c == 'q')
|
|
return (struct obj *) 0;
|
|
++getobj_else;
|
|
}
|
|
if (uptr != &mons[PM_RUST_MONSTER]
|
|
&& (gold = g_at(u.ux, u.uy)) != 0) {
|
|
if (gold->quan == 1L)
|
|
Sprintf(qbuf, "There is 1 gold piece here; eat it?");
|
|
else
|
|
Sprintf(qbuf, "There are %ld gold pieces here; eat them?",
|
|
gold->quan);
|
|
if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y') {
|
|
return gold;
|
|
} else if (c == 'q') {
|
|
return (struct obj *) 0;
|
|
}
|
|
++getobj_else;
|
|
}
|
|
}
|
|
|
|
/* Is there some food (probably a heavy corpse) here on the ground? */
|
|
for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) {
|
|
if (corpsecheck
|
|
? (otmp->otyp == CORPSE
|
|
&& (corpsecheck == 1 || tinnable(otmp)))
|
|
: feeding ? (otmp->oclass != COIN_CLASS && is_edible(otmp))
|
|
: otmp->oclass == FOOD_CLASS) {
|
|
char qsfx[QBUFSZ];
|
|
boolean one = (otmp->quan == 1L);
|
|
|
|
/* if blind and without gloves, attempting to eat (or tin or
|
|
offer) a cockatrice corpse is fatal before asking whether
|
|
or not to use it; otherwise, 'm<dir>' followed by 'e' could
|
|
be used to locate cockatrice corpses without touching them */
|
|
if (otmp->otyp == CORPSE && will_feel_cockatrice(otmp, FALSE)) {
|
|
feel_cockatrice(otmp, FALSE);
|
|
/* if life-saved (or poly'd into stone golem), terminate
|
|
attempt to eat off floor */
|
|
return (struct obj *) 0;
|
|
}
|
|
/* "There is <an object> here; <verb> it?" or
|
|
"There are <N objects> here; <verb> one?" */
|
|
Sprintf(qbuf, "There %s ", otense(otmp, "are"));
|
|
Sprintf(qsfx, " here; %s %s?", verb, one ? "it" : "one");
|
|
(void) safe_qbuf(qbuf, qbuf, qsfx, otmp, doname, ansimpleoname,
|
|
one ? something : (const char *) "things");
|
|
if ((c = yn_function(qbuf, ynqchars, 'n', TRUE)) == 'y')
|
|
return otmp;
|
|
else if (c == 'q')
|
|
return (struct obj *) 0;
|
|
++getobj_else;
|
|
}
|
|
}
|
|
|
|
skipfloor:
|
|
/* We cannot use GETOBJ_PROMPT since we don't want a prompt in the case
|
|
where nothing edible is being carried. */
|
|
if (feeding) {
|
|
otmp = getobj("eat", eat_ok, GETOBJ_NOFLAGS);
|
|
} else if (offering) {
|
|
otmp = getobj("sacrifice", offer_ok, GETOBJ_NOFLAGS);
|
|
} else if (corpsecheck == 2) {
|
|
otmp = getobj(verb, tin_ok, GETOBJ_NOFLAGS);
|
|
} else {
|
|
impossible("floorfood: unknown request (%s)", verb);
|
|
otmp = (struct obj *) 0;
|
|
}
|
|
if (otmp && corpsecheck && !(offering && otmp->oclass == AMULET_CLASS)) {
|
|
if (otmp->otyp != CORPSE || (corpsecheck == 2 && !tinnable(otmp))) {
|
|
You_cant("%s that!", verb);
|
|
otmp = (struct obj *) 0;
|
|
}
|
|
}
|
|
/* resetting 'getobj_else' here isn't essential; it will be cleared the
|
|
next time it needs to be used */
|
|
getobj_else = 0;
|
|
return otmp;
|
|
}
|
|
|
|
/* Side effects of vomiting */
|
|
/* added nomul (MRS) - it makes sense, you're too busy being sick! */
|
|
void
|
|
vomit(void) /* A good idea from David Neves */
|
|
{
|
|
boolean spewed = FALSE;
|
|
|
|
if (cantvomit(gy.youmonst.data)) {
|
|
/* doesn't cure food poisoning; message assumes that we aren't
|
|
dealing with some esoteric body_part() */
|
|
Your("jaw gapes convulsively.");
|
|
} else {
|
|
if (Sick && (u.usick_type & SICK_VOMITABLE) != 0)
|
|
make_sick(0L, (char *) 0, TRUE, SICK_VOMITABLE);
|
|
/* if not enough in stomach to actually vomit then dry heave;
|
|
vomiting_dialog() gives a vomit message when its countdown
|
|
reaches 0, but only if u.uhs < FAINTING (and !cantvomit()) */
|
|
if (u.uhs >= FAINTING)
|
|
Your("%s heaves convulsively!", body_part(STOMACH));
|
|
else
|
|
spewed = TRUE;
|
|
}
|
|
|
|
/* nomul()/You_can_move_again used to be unconditional, which was
|
|
viable while eating but not for Vomiting countdown where hero might
|
|
be immobilized for some other reason at the time vomit() is called */
|
|
if (gm.multi >= -2) {
|
|
nomul(-2);
|
|
gm.multi_reason = "vomiting";
|
|
gn.nomovemsg = You_can_move_again;
|
|
}
|
|
|
|
if (spewed) {
|
|
struct attack
|
|
*mattk = attacktype_fordmg(gy.youmonst.data, AT_BREA, AD_ACID);
|
|
|
|
/* currently, only yellow dragons can breathe acid */
|
|
if (mattk) {
|
|
You("breathe acid on yourself..."); /* [why?] */
|
|
ubreatheu(mattk);
|
|
}
|
|
/* vomiting on an altar is, all things considered, rather impolite */
|
|
if (IS_ALTAR(levl[u.ux][u.uy].typ))
|
|
altar_wrath(u.ux, u.uy);
|
|
/* if poly'd into acidic form, stomach acid is stronger than normal */
|
|
if (acidic(gy.youmonst.data)) {
|
|
/* TODO: if there's a web here, destroy that too (before ice) */
|
|
if (is_ice(u.ux, u.uy))
|
|
melt_ice(u.ux, u.uy,
|
|
"Your stomach acid melts straight through the ice!");
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
eaten_stat(int base, struct obj *obj)
|
|
{
|
|
long uneaten_amt, full_amount;
|
|
|
|
/* get full_amount first; obj_nutrition() might modify obj->oeaten */
|
|
full_amount = (long) obj_nutrition(obj);
|
|
uneaten_amt = (long) obj->oeaten;
|
|
if (uneaten_amt > full_amount) {
|
|
impossible(
|
|
"partly eaten food (%ld) more nutritious than untouched food (%ld)",
|
|
uneaten_amt, full_amount);
|
|
uneaten_amt = full_amount;
|
|
}
|
|
|
|
base = (int) (full_amount ? (long) base * uneaten_amt / full_amount : 0L);
|
|
return (base < 1) ? 1 : base;
|
|
}
|
|
|
|
/* reduce obj's oeaten field, making sure it never hits or passes 0 */
|
|
void
|
|
consume_oeaten(struct obj *obj, int amt)
|
|
{
|
|
if (!obj_nutrition(obj)) {
|
|
char itembuf[40];
|
|
int otyp = obj->otyp;
|
|
|
|
if (otyp == CORPSE || otyp == EGG || otyp == TIN) {
|
|
Strcpy(itembuf, (otyp == CORPSE) ? "corpse"
|
|
: (otyp == EGG) ? "egg"
|
|
: (otyp == TIN) ? "tin" : "other?");
|
|
Sprintf(eos(itembuf), " [%d]", obj->corpsenm);
|
|
} else {
|
|
Sprintf(itembuf, "%d", otyp);
|
|
}
|
|
impossible(
|
|
"oeaten: attempting to set 0 nutrition food (%s) partially eaten",
|
|
itembuf);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This is a hack to try to squelch several long standing mystery
|
|
* food bugs. A better solution would be to rewrite the entire
|
|
* victual handling mechanism from scratch using a less complex
|
|
* model. Alternatively, this routine could call done_eating()
|
|
* or food_disappears() but its callers would need revisions to
|
|
* cope with svc.context.victual.piece unexpectedly going away.
|
|
*
|
|
* Multi-turn eating operates by setting the food's oeaten field
|
|
* to its full nutritional value and then running a counter which
|
|
* independently keeps track of whether there is any food left.
|
|
* The oeaten field can reach exactly zero on the last turn, and
|
|
* the object isn't removed from inventory until the next turn
|
|
* when the "you finish eating" message gets delivered, so the
|
|
* food would be restored to the status of untouched during that
|
|
* interval. This resulted in unexpected encumbrance messages
|
|
* at the end of a meal (if near enough to a threshold) and would
|
|
* yield full food if there was an interruption on the critical
|
|
* turn. Also, there have been reports over the years of food
|
|
* becoming massively heavy or producing unlimited satiation;
|
|
* this would occur if reducing oeaten via subtraction attempted
|
|
* to drop it below 0 since its unsigned type would produce a
|
|
* huge positive value instead. So far, no one has figured out
|
|
* _why_ that inappropriate subtraction might sometimes happen.
|
|
*/
|
|
|
|
if (amt > 0) {
|
|
/* bit shift to divide the remaining amount of food */
|
|
obj->oeaten >>= amt;
|
|
} else {
|
|
/* simple decrement; value is negative so we actually add it */
|
|
if ((int) obj->oeaten > -amt)
|
|
obj->oeaten += amt;
|
|
else
|
|
obj->oeaten = 0;
|
|
}
|
|
|
|
/* mustn't let partly-eaten drop all the way to 0 or the item would
|
|
become restored to untouched; set to no bites left */
|
|
if (obj->oeaten == 0) {
|
|
if (obj == svc.context.victual.piece) /* always true unless wishing */
|
|
svc.context.victual.reqtime = svc.context.victual.usedtime;
|
|
obj->oeaten = 1; /* smallest possible positive value */
|
|
}
|
|
}
|
|
|
|
/* called when eatfood occupation has been interrupted,
|
|
or in the case of theft, is about to be interrupted */
|
|
boolean
|
|
maybe_finished_meal(boolean stopping)
|
|
{
|
|
/* in case consume_oeaten() has decided that the food is all gone */
|
|
if (go.occupation == eatfood
|
|
&& svc.context.victual.usedtime >= svc.context.victual.reqtime) {
|
|
if (stopping)
|
|
go.occupation = 0; /* for do_reset_eat */
|
|
/* eatfood() calls done_eating() to use up svc.context.victual.piece */
|
|
(void) eatfood();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* called by revive(); sort of the opposite of maybe_finished_meal() */
|
|
void
|
|
cant_finish_meal(struct obj *corpse)
|
|
{
|
|
/*
|
|
* When a corpse gets resurrected, the makemon() for that might
|
|
* call stop_occupation(). If that happens, prevent it from using
|
|
* up the corpse via maybe_finished_meal() when there's not enough
|
|
* left for another bite. revive() needs continued access to the
|
|
* corpse and will delete it when done.
|
|
*/
|
|
if (go.occupation == eatfood && svc.context.victual.piece == corpse) {
|
|
/* normally performed by done_eating() */
|
|
svc.context.victual = zero_victual; /* victual.piece = 0, .o_id = 0 */
|
|
|
|
if (!corpse->oeaten)
|
|
corpse->oeaten = 1; /* [see consume_oeaten()] */
|
|
go.occupation = donull; /* any non-Null other than eatfood() */
|
|
stop_occupation();
|
|
newuhs(FALSE);
|
|
}
|
|
}
|
|
|
|
/* Tin of <something> to the rescue? Decide whether current occupation
|
|
is an attempt to eat a tin of something capable of saving hero's life.
|
|
We don't care about consumption of non-tinned food here because special
|
|
effects there take place on first bite rather than at end of occupation.
|
|
[Popeye the Sailor gets out of trouble by eating tins of spinach. :-] */
|
|
boolean
|
|
Popeye(int threat)
|
|
{
|
|
struct obj *otin;
|
|
int mndx;
|
|
|
|
if (go.occupation != opentin)
|
|
return FALSE;
|
|
otin = svc.context.tin.tin;
|
|
/* make sure hero still has access to tin */
|
|
if (!carried(otin)
|
|
&& (!obj_here(otin, u.ux, u.uy) || !can_reach_floor(TRUE)))
|
|
return FALSE;
|
|
/* unknown tin is assumed to be helpful */
|
|
if (!otin->known)
|
|
return TRUE;
|
|
/* known tin is helpful if it will stop life-threatening problem */
|
|
mndx = otin->corpsenm;
|
|
switch (threat) {
|
|
/* note: not used; hunger code bypasses stop_occupation() when eating */
|
|
case HUNGER:
|
|
return (boolean) (mndx != NON_PM || otin->spe == 1);
|
|
/* flesh from lizards and acidic critters stops petrification */
|
|
case STONED:
|
|
return (boolean) (ismnum(mndx)
|
|
&& (mndx == PM_LIZARD || acidic(&mons[mndx])));
|
|
/* polymorph into a fiery monster */
|
|
case SLIMED:
|
|
return (boolean) polyfood(otin);
|
|
/* no tins can cure these (yet?) */
|
|
case SICK:
|
|
case VOMITING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* the hero has swallowed a monster whole as a purple worm or similar, and has
|
|
finished digesting its corpse (called via ga.afternmv) */
|
|
int
|
|
Finish_digestion(void)
|
|
{
|
|
if (gc.corpsenm_digested != NON_PM) {
|
|
cpostfx(gc.corpsenm_digested);
|
|
gc.corpsenm_digested = NON_PM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*eat.c*/
|