add MALE, FEMALE, and gender-neutral names for individual monster species
to the mons array. The gender-neutral name (NEUTRAL) is mandatory, the
MALE and FEMALE versions are not.
replace code uses of the mname field of permonst with one of the three
potentially-available gender-specific names.
consolidate some separate mons entries that differed only by species into a
single mons entry (caveman, cavewoman and priest,priestess etc.)
consolidate several "* lord" and "* queen/* king" monst entries into
their single species, and allow both genders on some where it makes some
sense (there is probably more work and cleanup to come out of this at some
point, and the chosen gender-neutral name variations are not cast in stone
if someone has better suggestions).
related function or macro additions:
pmname(pm, gender) to get the gender variation of the permonst name. It
guards against monsters that haven't got anything except NEUTRAL naming
and falls back to the NEUTRAL version if FEMALE and MALE versions are
missing.
Ugender to obtain the current hero gender.
Mgender(mtmp) to obtain the gender of a monster
While the code can safely refer directly to pmnames[NEUTRAL] safely in the
code because it always exists, the other two (pmnames[MALE] and
pmnames[FEMALE] may not exist so use:
pmname(ptr, gidx)
where -ptr is a permonst *
-gidx is an index into the pmnames array field of the
permonst struct
pmname() checks for a valid index and checks for null-pointers for
pmnames[MALE] and pmnames[FEMALE], and will fall back to pmnames[NEUTRAL] if
the pointer requested if the requested variation is unavailable, or if the
gidx is out-of-range.
Allow code to specify makemon flags to request female or male (via MM_MALE
and MM_FEMALE flags respectively)to makedefs, since the species alone doesn't
distinguish male/female anymore. Specifying MM_MALE or MM_FEMALE won't
override the pm M2_MALE and M2_FEMALE flags on a mons[] entry.
male and female tiles have been added to win/share/monsters.txt.
The majority are duplicated placeholders except for those that were
separate mons entries before. Perhaps someone will contribute artwork in the
future to make the male and female variations visually distinguishable.
tilemapping via has the MALE tile indexes in the glyph2tile[]
array produced at build time. If a window port has information that the
FEMALE tile is required, it just has to increment the index returned
from the glyph2tile[] array by 1.
statues already preserved gender of the monster through STATUE_FEMALE
and STATUE_MALE, so ensure that pmnames takes that into consideration.
I expect some refinement will be required after broad play-testing puts it to
the test.
consolidate caveman,cavewoman and priest,priestess monst.c entries etc
This commit will require a bump of editlevel in patchlevel.h because it alters
the index numbers of the monsters due to the consolidation of some. Those
index numbers are saved in some other structures, even though the mons[] array
itself is not part of the savefile.
Window Port Interface Change
Also add a parameter to print_glyph to convey additional information beyond
the glyph to the window ports. Every single window port was calling back to
mapglyph for the information anyway, so just included it in the interface and
produce the information right in the display core.
The mapglyph() function uses will be eliminated, although there are still some
in the code yet to be dealt with.
win32, tty, x11, Qt, msdos window ports have all had adjustments done to
utilize the new parameter instead of calling mapglyph, but some of those
window ports have not been thoroughly tested since the changes.
Interface change additional info:
print_glyph(window, x, y, glyph, bkglyph, *glyphmod)
-- Print the glyph at (x,y) on the given window. Glyphs are
integers at the interface, mapped to whatever the window-
port wants (symbol, font, color, attributes, ...there's
a 1-1 map between glyphs and distinct things on the map).
-- bkglyph is a background glyph for potential use by some
graphical or tiled environments to allow the depiction
to fall against a background consistent with the grid
around x,y. If bkglyph is NO_GLYPH, then the parameter
should be ignored (do nothing with it).
-- glyphmod provides extended information about the glyph
that window ports can use to enhance the display in
various ways.
unsigned int glyphmod[NUM_GLYPHMOD]
where:
glyphmod[GM_TTYCHAR] is the text characters associated
with the original NetHack display.
glyphmod[GM_FLAGS] are the special flags that denote
additional information that window
ports can use.
glyphmod[GM_COLOR] is the text character
color associated with the original
NetHack display.
Support for including the glyphmod info in the display glyph buffer
alongside the glyph itself was added and is the default operation.
That can be turned off by defining UNBUFFERED_GLYPHMOD at compile time.
With UNBUFFERED_GLYPHMOD operation, a call will be placed to map_glyphmod()
immediately prior to every print_glyph() call.
1838 lines
62 KiB
C
1838 lines
62 KiB
C
/* NetHack 3.7 dokick.c $NHDT-Date: 1608673689 2020/12/22 21:48:09 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.162 $ */
|
|
/* Copyright (c) Izchak Miller, Mike Stephenson, Steve Linhart, 1989. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
#define is_bigfoot(x) ((x) == &mons[PM_SASQUATCH])
|
|
#define martial() \
|
|
(martial_bonus() || is_bigfoot(g.youmonst.data) \
|
|
|| (uarmf && uarmf->otyp == KICKING_BOOTS))
|
|
|
|
/* g.kickedobj (decl.c) tracks a kicked object until placed or destroyed */
|
|
|
|
static void FDECL(kickdmg, (struct monst *, BOOLEAN_P));
|
|
static boolean FDECL(maybe_kick_monster, (struct monst *,
|
|
XCHAR_P, XCHAR_P));
|
|
static void FDECL(kick_monster, (struct monst *, XCHAR_P, XCHAR_P));
|
|
static int FDECL(kick_object, (XCHAR_P, XCHAR_P, char *));
|
|
static int FDECL(really_kick_object, (XCHAR_P, XCHAR_P));
|
|
static char *FDECL(kickstr, (char *, const char *));
|
|
static void FDECL(otransit_msg, (struct obj *, BOOLEAN_P, BOOLEAN_P, long));
|
|
static void FDECL(drop_to, (coord *, SCHAR_P, XCHAR_P, XCHAR_P));
|
|
|
|
static const char kick_passes_thru[] = "kick passes harmlessly through";
|
|
|
|
/* kicking damage when not poly'd into a form with a kick attack */
|
|
static void
|
|
kickdmg(mon, clumsy)
|
|
struct monst *mon;
|
|
boolean clumsy;
|
|
{
|
|
int mdx, mdy;
|
|
int dmg = (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 15;
|
|
int specialdmg, kick_skill = P_NONE;
|
|
boolean trapkilled = FALSE;
|
|
|
|
if (uarmf && uarmf->otyp == KICKING_BOOTS)
|
|
dmg += 5;
|
|
|
|
/* excessive wt affects dex, so it affects dmg */
|
|
if (clumsy)
|
|
dmg /= 2;
|
|
|
|
/* kicking a dragon or an elephant will not harm it */
|
|
if (thick_skinned(mon->data))
|
|
dmg = 0;
|
|
|
|
/* attacking a shade is normally useless */
|
|
if (mon->data == &mons[PM_SHADE])
|
|
dmg = 0;
|
|
|
|
specialdmg = special_dmgval(&g.youmonst, mon, W_ARMF, (long *) 0);
|
|
|
|
if (mon->data == &mons[PM_SHADE] && !specialdmg) {
|
|
pline_The("%s.", kick_passes_thru);
|
|
/* doesn't exercise skill or abuse alignment or frighten pet,
|
|
and shades have no passive counterattack */
|
|
return;
|
|
}
|
|
|
|
if (M_AP_TYPE(mon))
|
|
seemimic(mon);
|
|
|
|
check_caitiff(mon);
|
|
|
|
/* squeeze some guilt feelings... */
|
|
if (mon->mtame) {
|
|
abuse_dog(mon);
|
|
if (mon->mtame)
|
|
monflee(mon, (dmg ? rnd(dmg) : 1), FALSE, FALSE);
|
|
else
|
|
mon->mflee = 0;
|
|
}
|
|
|
|
if (dmg > 0) {
|
|
/* convert potential damage to actual damage */
|
|
dmg = rnd(dmg);
|
|
if (martial()) {
|
|
if (dmg > 1)
|
|
kick_skill = P_MARTIAL_ARTS;
|
|
dmg += rn2(ACURR(A_DEX) / 2 + 1);
|
|
}
|
|
/* a good kick exercises your dex */
|
|
exercise(A_DEX, TRUE);
|
|
}
|
|
dmg += specialdmg; /* for blessed (or hypothetically, silver) boots */
|
|
if (uarmf)
|
|
dmg += uarmf->spe;
|
|
dmg += u.udaminc; /* add ring(s) of increase damage */
|
|
if (dmg > 0)
|
|
mon->mhp -= dmg;
|
|
if (!DEADMONSTER(mon) && martial() && !bigmonst(mon->data) && !rn2(3)
|
|
&& mon->mcanmove && mon != u.ustuck && !mon->mtrapped) {
|
|
/* see if the monster has a place to move into */
|
|
mdx = mon->mx + u.dx;
|
|
mdy = mon->my + u.dy;
|
|
if (goodpos(mdx, mdy, mon, 0)) {
|
|
pline("%s reels from the blow.", Monnam(mon));
|
|
if (m_in_out_region(mon, mdx, mdy)) {
|
|
remove_monster(mon->mx, mon->my);
|
|
newsym(mon->mx, mon->my);
|
|
place_monster(mon, mdx, mdy);
|
|
newsym(mon->mx, mon->my);
|
|
set_apparxy(mon);
|
|
if (mintrap(mon) == 2)
|
|
trapkilled = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
(void) passive(mon, uarmf, TRUE, !DEADMONSTER(mon), AT_KICK, FALSE);
|
|
if (DEADMONSTER(mon) && !trapkilled)
|
|
killed(mon);
|
|
|
|
/* may bring up a dialog, so put this after all messages */
|
|
if (kick_skill != P_NONE) /* exercise proficiency */
|
|
use_skill(kick_skill, 1);
|
|
}
|
|
|
|
static boolean
|
|
maybe_kick_monster(mon, x, y)
|
|
struct monst *mon;
|
|
xchar x, y;
|
|
{
|
|
if (mon) {
|
|
boolean save_forcefight = g.context.forcefight;
|
|
|
|
g.bhitpos.x = x;
|
|
g.bhitpos.y = y;
|
|
if (!mon->mpeaceful || !canspotmon(mon))
|
|
g.context.forcefight = TRUE; /* attack even if invisible */
|
|
/* kicking might be halted by discovery of hidden monster,
|
|
by player declining to attack peaceful monster,
|
|
or by passing out due to encumbrance */
|
|
if (attack_checks(mon, (struct obj *) 0) || overexertion())
|
|
mon = 0; /* don't kick after all */
|
|
g.context.forcefight = save_forcefight;
|
|
}
|
|
return (boolean) (mon != 0);
|
|
}
|
|
|
|
static void
|
|
kick_monster(mon, x, y)
|
|
struct monst *mon;
|
|
xchar x, y;
|
|
{
|
|
boolean clumsy = FALSE;
|
|
int i, j;
|
|
|
|
/* anger target even if wild miss will occur */
|
|
setmangry(mon, TRUE);
|
|
|
|
if (Levitation && !rn2(3) && verysmall(mon->data)
|
|
&& !is_flyer(mon->data)) {
|
|
pline("Floating in the air, you miss wildly!");
|
|
exercise(A_DEX, FALSE);
|
|
(void) passive(mon, uarmf, FALSE, 1, AT_KICK, FALSE);
|
|
return;
|
|
}
|
|
|
|
/* reveal hidden target even if kick ends up missing (note: being
|
|
hidden doesn't affect chance to hit so neither does this reveal) */
|
|
if (mon->mundetected
|
|
|| (M_AP_TYPE(mon) && M_AP_TYPE(mon) != M_AP_MONSTER)) {
|
|
if (M_AP_TYPE(mon))
|
|
seemimic(mon);
|
|
mon->mundetected = 0;
|
|
if (!canspotmon(mon))
|
|
map_invisible(x, y);
|
|
else
|
|
newsym(x, y);
|
|
There("is %s here.",
|
|
canspotmon(mon) ? a_monnam(mon) : "something hidden");
|
|
}
|
|
|
|
/* Kick attacks by kicking monsters are normal attacks, not special.
|
|
* This is almost always worthless, since you can either take one turn
|
|
* and do all your kicks, or else take one turn and attack the monster
|
|
* normally, getting all your attacks _including_ all your kicks.
|
|
* If you have >1 kick attack, you get all of them.
|
|
*/
|
|
if (Upolyd && attacktype(g.youmonst.data, AT_KICK)) {
|
|
struct attack *uattk;
|
|
int sum, kickdieroll, armorpenalty, specialdmg,
|
|
attknum = 0,
|
|
tmp = find_roll_to_hit(mon, AT_KICK, (struct obj *) 0, &attknum,
|
|
&armorpenalty);
|
|
|
|
for (i = 0; i < NATTK; i++) {
|
|
/* first of two kicks might have provoked counterattack
|
|
that has incapacitated the hero (ie, floating eye) */
|
|
if (g.multi < 0)
|
|
break;
|
|
|
|
uattk = &g.youmonst.data->mattk[i];
|
|
/* we only care about kicking attacks here */
|
|
if (uattk->aatyp != AT_KICK)
|
|
continue;
|
|
|
|
kickdieroll = rnd(20);
|
|
specialdmg = special_dmgval(&g.youmonst, mon, W_ARMF, (long *) 0);
|
|
if (mon->data == &mons[PM_SHADE] && !specialdmg) {
|
|
/* doesn't matter whether it would have hit or missed,
|
|
and shades have no passive counterattack */
|
|
Your("%s %s.", kick_passes_thru, mon_nam(mon));
|
|
break; /* skip any additional kicks */
|
|
} else if (tmp > kickdieroll) {
|
|
You("kick %s.", mon_nam(mon));
|
|
sum = damageum(mon, uattk, specialdmg);
|
|
(void) passive(mon, uarmf, (boolean) (sum > 0),
|
|
(sum != 2), AT_KICK, FALSE);
|
|
if (sum == 2)
|
|
break; /* Defender died */
|
|
} else {
|
|
missum(mon, uattk, (tmp + armorpenalty > kickdieroll));
|
|
(void) passive(mon, uarmf, FALSE, 1, AT_KICK, FALSE);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
i = -inv_weight();
|
|
j = weight_cap();
|
|
|
|
if (i < (j * 3) / 10) {
|
|
if (!rn2((i < j / 10) ? 2 : (i < j / 5) ? 3 : 4)) {
|
|
if (martial() && !rn2(2))
|
|
goto doit;
|
|
Your("clumsy kick does no damage.");
|
|
(void) passive(mon, uarmf, FALSE, 1, AT_KICK, FALSE);
|
|
return;
|
|
}
|
|
if (i < j / 10)
|
|
clumsy = TRUE;
|
|
else if (!rn2((i < j / 5) ? 2 : 3))
|
|
clumsy = TRUE;
|
|
}
|
|
|
|
if (Fumbling)
|
|
clumsy = TRUE;
|
|
|
|
else if (uarm && objects[uarm->otyp].oc_bulky && ACURR(A_DEX) < rnd(25))
|
|
clumsy = TRUE;
|
|
doit:
|
|
You("kick %s.", mon_nam(mon));
|
|
if (!rn2(clumsy ? 3 : 4) && (clumsy || !bigmonst(mon->data))
|
|
&& mon->mcansee && !mon->mtrapped && !thick_skinned(mon->data)
|
|
&& mon->data->mlet != S_EEL && haseyes(mon->data) && mon->mcanmove
|
|
&& !mon->mstun && !mon->mconf && !mon->msleeping
|
|
&& mon->data->mmove >= 12) {
|
|
if (!nohands(mon->data) && !rn2(martial() ? 5 : 3)) {
|
|
pline("%s blocks your %skick.", Monnam(mon),
|
|
clumsy ? "clumsy " : "");
|
|
(void) passive(mon, uarmf, FALSE, 1, AT_KICK, FALSE);
|
|
return;
|
|
} else {
|
|
maybe_mnexto(mon);
|
|
if (mon->mx != x || mon->my != y) {
|
|
(void) unmap_invisible(x, y);
|
|
pline("%s %s, %s evading your %skick.", Monnam(mon),
|
|
(can_teleport(mon->data) && !noteleport_level(mon))
|
|
? "teleports"
|
|
: is_floater(mon->data)
|
|
? "floats"
|
|
: is_flyer(mon->data) ? "swoops"
|
|
: (nolimbs(mon->data)
|
|
|| slithy(mon->data))
|
|
? "slides"
|
|
: "jumps",
|
|
clumsy ? "easily" : "nimbly", clumsy ? "clumsy " : "");
|
|
(void) passive(mon, uarmf, FALSE, 1, AT_KICK, FALSE);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
kickdmg(mon, clumsy);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if caught (the gold taken care of), FALSE otherwise.
|
|
* The gold object is *not* attached to the fobj chain!
|
|
*/
|
|
boolean
|
|
ghitm(mtmp, gold)
|
|
register struct monst *mtmp;
|
|
register struct obj *gold;
|
|
{
|
|
boolean msg_given = FALSE;
|
|
|
|
if (!likes_gold(mtmp->data) && !mtmp->isshk && !mtmp->ispriest
|
|
&& !mtmp->isgd && !is_mercenary(mtmp->data)) {
|
|
wakeup(mtmp, TRUE);
|
|
} else if (!mtmp->mcanmove) {
|
|
/* too light to do real damage */
|
|
if (canseemon(mtmp)) {
|
|
pline_The("%s harmlessly %s %s.", xname(gold),
|
|
otense(gold, "hit"), mon_nam(mtmp));
|
|
msg_given = TRUE;
|
|
}
|
|
} else {
|
|
long umoney, value = gold->quan * objects[gold->otyp].oc_cost;
|
|
|
|
mtmp->msleeping = 0;
|
|
finish_meating(mtmp);
|
|
if (!mtmp->isgd && !rn2(4)) /* not always pleasing */
|
|
setmangry(mtmp, TRUE);
|
|
/* greedy monsters catch gold */
|
|
if (cansee(mtmp->mx, mtmp->my))
|
|
pline("%s catches the gold.", Monnam(mtmp));
|
|
(void) mpickobj(mtmp, gold);
|
|
gold = (struct obj *) 0; /* obj has been freed */
|
|
if (mtmp->isshk) {
|
|
long robbed = ESHK(mtmp)->robbed;
|
|
|
|
if (robbed) {
|
|
robbed -= value;
|
|
if (robbed < 0L)
|
|
robbed = 0L;
|
|
pline_The("amount %scovers %s recent losses.",
|
|
!robbed ? "" : "partially ", mhis(mtmp));
|
|
ESHK(mtmp)->robbed = robbed;
|
|
if (!robbed)
|
|
make_happy_shk(mtmp, FALSE);
|
|
} else {
|
|
if (mtmp->mpeaceful) {
|
|
ESHK(mtmp)->credit += value;
|
|
You("have %ld %s in credit.", ESHK(mtmp)->credit,
|
|
currency(ESHK(mtmp)->credit));
|
|
} else
|
|
verbalize("Thanks, scum!");
|
|
}
|
|
} else if (mtmp->ispriest) {
|
|
if (mtmp->mpeaceful)
|
|
verbalize("Thank you for your contribution.");
|
|
else
|
|
verbalize("Thanks, scum!");
|
|
} else if (mtmp->isgd) {
|
|
umoney = money_cnt(g.invent);
|
|
/* Some of these are iffy, because a hostile guard
|
|
won't become peaceful and resume leading hero
|
|
out of the vault. If he did do that, player
|
|
could try fighting, then weasle out of being
|
|
killed by throwing his/her gold when losing. */
|
|
verbalize(umoney ? "Drop the rest and follow me."
|
|
: hidden_gold(TRUE)
|
|
? "You still have hidden gold. Drop it now."
|
|
: mtmp->mpeaceful
|
|
? "I'll take care of that; please move along."
|
|
: "I'll take that; now get moving.");
|
|
} else if (is_mercenary(mtmp->data)) {
|
|
long goldreqd = 0L;
|
|
|
|
if (rn2(3)) {
|
|
if (mtmp->data == &mons[PM_SOLDIER])
|
|
goldreqd = 100L;
|
|
else if (mtmp->data == &mons[PM_SERGEANT])
|
|
goldreqd = 250L;
|
|
else if (mtmp->data == &mons[PM_LIEUTENANT])
|
|
goldreqd = 500L;
|
|
else if (mtmp->data == &mons[PM_CAPTAIN])
|
|
goldreqd = 750L;
|
|
|
|
if (goldreqd) {
|
|
umoney = money_cnt(g.invent);
|
|
if (value
|
|
> goldreqd
|
|
+ (umoney + u.ulevel * rn2(5)) / ACURR(A_CHA))
|
|
mtmp->mpeaceful = TRUE;
|
|
}
|
|
}
|
|
if (mtmp->mpeaceful)
|
|
verbalize("That should do. Now beat it!");
|
|
else
|
|
verbalize("That's not enough, coward!");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (!msg_given)
|
|
miss(xname(gold), mtmp);
|
|
return FALSE;
|
|
}
|
|
|
|
/* container is kicked, dropped, thrown or otherwise impacted by player.
|
|
* Assumes container is on floor. Checks contents for possible damage. */
|
|
void
|
|
container_impact_dmg(obj, x, y)
|
|
struct obj *obj;
|
|
xchar x, y; /* coordinates where object was before the impact, not after */
|
|
{
|
|
struct monst *shkp;
|
|
struct obj *otmp, *otmp2;
|
|
long loss = 0L;
|
|
boolean costly, insider, frominv;
|
|
|
|
/* only consider normal containers */
|
|
if (!Is_container(obj) || !Has_contents(obj) || Is_mbag(obj))
|
|
return;
|
|
|
|
costly = ((shkp = shop_keeper(*in_rooms(x, y, SHOPBASE)))
|
|
&& costly_spot(x, y));
|
|
insider = (*u.ushops && inside_shop(u.ux, u.uy)
|
|
&& *in_rooms(x, y, SHOPBASE) == *u.ushops);
|
|
/* if dropped or thrown, shop ownership flags are set on this obj */
|
|
frominv = (obj != g.kickedobj);
|
|
|
|
for (otmp = obj->cobj; otmp; otmp = otmp2) {
|
|
const char *result = (char *) 0;
|
|
|
|
otmp2 = otmp->nobj;
|
|
if (objects[otmp->otyp].oc_material == GLASS
|
|
&& otmp->oclass != GEM_CLASS && !obj_resists(otmp, 33, 100)) {
|
|
result = "shatter";
|
|
} else if (otmp->otyp == EGG && !rn2(3)) {
|
|
result = "cracking";
|
|
}
|
|
if (result) {
|
|
if (otmp->otyp == MIRROR)
|
|
change_luck(-2);
|
|
|
|
/* eggs laid by you. penalty is -1 per egg, max 5,
|
|
* but it's always exactly 1 that breaks */
|
|
if (otmp->otyp == EGG && otmp->spe && otmp->corpsenm >= LOW_PM)
|
|
change_luck(-1);
|
|
You_hear("a muffled %s.", result);
|
|
if (costly) {
|
|
if (frominv && !otmp->unpaid)
|
|
otmp->no_charge = 1;
|
|
loss +=
|
|
stolen_value(otmp, x, y, (boolean) shkp->mpeaceful, TRUE);
|
|
}
|
|
if (otmp->quan > 1L) {
|
|
useup(otmp);
|
|
} else {
|
|
obj_extract_self(otmp);
|
|
obfree(otmp, (struct obj *) 0);
|
|
}
|
|
/* contents of this container are no longer known */
|
|
obj->cknown = 0;
|
|
}
|
|
}
|
|
if (costly && loss) {
|
|
if (!insider) {
|
|
You("caused %ld %s worth of damage!", loss, currency(loss));
|
|
make_angry_shk(shkp, x, y);
|
|
} else {
|
|
You("owe %s %ld %s for objects destroyed.", mon_nam(shkp), loss,
|
|
currency(loss));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* jacket around really_kick_object */
|
|
static int
|
|
kick_object(x, y, kickobjnam)
|
|
xchar x, y;
|
|
char *kickobjnam;
|
|
{
|
|
int res = 0;
|
|
|
|
*kickobjnam = '\0';
|
|
/* if a pile, the "top" object gets kicked */
|
|
g.kickedobj = g.level.objects[x][y];
|
|
if (g.kickedobj) {
|
|
/* kick object; if doing is fatal, done() will clean up g.kickedobj */
|
|
Strcpy(kickobjnam, killer_xname(g.kickedobj)); /* matters iff res==0 */
|
|
res = really_kick_object(x, y);
|
|
g.kickedobj = (struct obj *) 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* guts of kick_object */
|
|
static int
|
|
really_kick_object(x, y)
|
|
xchar x, y;
|
|
{
|
|
int range;
|
|
struct monst *mon, *shkp = 0;
|
|
struct trap *trap;
|
|
char bhitroom;
|
|
boolean costly, isgold, slide = FALSE;
|
|
|
|
/* g.kickedobj should always be set due to conditions of call */
|
|
if (!g.kickedobj || g.kickedobj->otyp == BOULDER || g.kickedobj == uball
|
|
|| g.kickedobj == uchain)
|
|
return 0;
|
|
|
|
if ((trap = t_at(x, y)) != 0) {
|
|
if ((is_pit(trap->ttyp) && !Passes_walls) || trap->ttyp == WEB) {
|
|
if (!trap->tseen)
|
|
find_trap(trap);
|
|
You_cant("kick %s that's in a %s!", something,
|
|
Hallucination ? "tizzy"
|
|
: (trap->ttyp == WEB) ? "web"
|
|
: "pit");
|
|
return 1;
|
|
}
|
|
if (trap->ttyp == STATUE_TRAP) {
|
|
activate_statue_trap(trap, x,y, FALSE);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (Fumbling && !rn2(3)) {
|
|
Your("clumsy kick missed.");
|
|
return 1;
|
|
}
|
|
|
|
if (!uarmf && g.kickedobj->otyp == CORPSE
|
|
&& touch_petrifies(&mons[g.kickedobj->corpsenm])
|
|
&& !Stone_resistance) {
|
|
You("kick %s with your bare %s.",
|
|
corpse_xname(g.kickedobj, (const char *) 0, CXN_PFX_THE),
|
|
makeplural(body_part(FOOT)));
|
|
if (poly_when_stoned(g.youmonst.data) && polymon(PM_STONE_GOLEM)) {
|
|
; /* hero has been transformed but kick continues */
|
|
} else {
|
|
/* normalize body shape here; foot, not body_part(FOOT) */
|
|
Sprintf(g.killer.name, "kicking %s barefoot",
|
|
killer_xname(g.kickedobj));
|
|
instapetrify(g.killer.name);
|
|
}
|
|
}
|
|
|
|
isgold = (g.kickedobj->oclass == COIN_CLASS);
|
|
{
|
|
int k_owt = (int) g.kickedobj->owt;
|
|
|
|
/* for non-gold stack, 1 item will be split off below (unless an
|
|
early return occurs, so we aren't moving the split to here);
|
|
calculate the range for that 1 rather than for the whole stack */
|
|
if (g.kickedobj->quan > 1L && !isgold) {
|
|
long save_quan = g.kickedobj->quan;
|
|
|
|
g.kickedobj->quan = 1L;
|
|
k_owt = weight(g.kickedobj);
|
|
g.kickedobj->quan = save_quan;
|
|
}
|
|
|
|
/* range < 2 means the object will not move
|
|
(maybe dexterity should also figure here) */
|
|
range = ((int) ACURRSTR) / 2 - k_owt / 40;
|
|
}
|
|
|
|
if (martial())
|
|
range += rnd(3);
|
|
|
|
if (is_pool(x, y)) {
|
|
/* you're in the water too; significantly reduce range */
|
|
range = range / 3 + 1; /* {1,2}=>1, {3,4,5}=>2, {6,7,8}=>3 */
|
|
} else if (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)) {
|
|
/* you're in air, since is_pool did not match */
|
|
range += rnd(3);
|
|
} else {
|
|
if (is_ice(x, y))
|
|
range += rnd(3), slide = TRUE;
|
|
if (g.kickedobj->greased)
|
|
range += rnd(3), slide = TRUE;
|
|
}
|
|
|
|
/* Mjollnir is magically too heavy to kick */
|
|
if (g.kickedobj->oartifact == ART_MJOLLNIR)
|
|
range = 1;
|
|
|
|
/* see if the object has a place to move into */
|
|
if (!ZAP_POS(levl[x + u.dx][y + u.dy].typ)
|
|
|| closed_door(x + u.dx, y + u.dy))
|
|
range = 1;
|
|
|
|
costly = (!(g.kickedobj->no_charge && !Has_contents(g.kickedobj))
|
|
&& (shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0
|
|
&& costly_spot(x, y));
|
|
|
|
if (IS_ROCK(levl[x][y].typ) || closed_door(x, y)) {
|
|
if ((!martial() && rn2(20) > ACURR(A_DEX))
|
|
|| IS_ROCK(levl[u.ux][u.uy].typ) || closed_door(u.ux, u.uy)) {
|
|
if (Blind)
|
|
pline("It doesn't come loose.");
|
|
else
|
|
pline("%s %sn't come loose.",
|
|
The(distant_name(g.kickedobj, xname)),
|
|
otense(g.kickedobj, "do"));
|
|
return (!rn2(3) || martial());
|
|
}
|
|
if (Blind)
|
|
pline("It comes loose.");
|
|
else
|
|
pline("%s %s loose.", The(distant_name(g.kickedobj, xname)),
|
|
otense(g.kickedobj, "come"));
|
|
obj_extract_self(g.kickedobj);
|
|
newsym(x, y);
|
|
if (costly && (!costly_spot(u.ux, u.uy)
|
|
|| !index(u.urooms, *in_rooms(x, y, SHOPBASE))))
|
|
addtobill(g.kickedobj, FALSE, FALSE, FALSE);
|
|
if (!flooreffects(g.kickedobj, u.ux, u.uy, "fall")) {
|
|
place_object(g.kickedobj, u.ux, u.uy);
|
|
stackobj(g.kickedobj);
|
|
newsym(u.ux, u.uy);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* a box gets a chance of breaking open here */
|
|
if (Is_box(g.kickedobj)) {
|
|
boolean otrp = g.kickedobj->otrapped;
|
|
|
|
if (range < 2)
|
|
pline("THUD!");
|
|
container_impact_dmg(g.kickedobj, x, y);
|
|
if (g.kickedobj->olocked) {
|
|
if (!rn2(5) || (martial() && !rn2(2))) {
|
|
You("break open the lock!");
|
|
breakchestlock(g.kickedobj, FALSE);
|
|
if (otrp)
|
|
(void) chest_trap(g.kickedobj, LEG, FALSE);
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (!rn2(3) || (martial() && !rn2(2))) {
|
|
pline_The("lid slams open, then falls shut.");
|
|
g.kickedobj->lknown = 1;
|
|
if (otrp)
|
|
(void) chest_trap(g.kickedobj, LEG, FALSE);
|
|
return 1;
|
|
}
|
|
}
|
|
if (range < 2)
|
|
return 1;
|
|
/* else let it fall through to the next cases... */
|
|
}
|
|
|
|
/* fragile objects should not be kicked */
|
|
if (hero_breaks(g.kickedobj, g.kickedobj->ox, g.kickedobj->oy, FALSE))
|
|
return 1;
|
|
|
|
/* too heavy to move. range is calculated as potential distance from
|
|
* player, so range == 2 means the object may move up to one square
|
|
* from its current position
|
|
*/
|
|
if (range < 2) {
|
|
if (!Is_box(g.kickedobj))
|
|
pline("Thump!");
|
|
return (!rn2(3) || martial());
|
|
}
|
|
|
|
if (g.kickedobj->quan > 1L) {
|
|
if (!isgold) {
|
|
g.kickedobj = splitobj(g.kickedobj, 1L);
|
|
} else {
|
|
if (rn2(20)) {
|
|
static NEARDATA const char *const flyingcoinmsg[] = {
|
|
"scatter the coins", "knock coins all over the place",
|
|
"send coins flying in all directions",
|
|
};
|
|
|
|
pline("Thwwpingg!");
|
|
You("%s!", flyingcoinmsg[rn2(SIZE(flyingcoinmsg))]);
|
|
(void) scatter(x, y, rn2(3) + 1, VIS_EFFECTS | MAY_HIT,
|
|
g.kickedobj);
|
|
newsym(x, y);
|
|
return 1;
|
|
}
|
|
if (g.kickedobj->quan > 300L) {
|
|
pline("Thump!");
|
|
return (!rn2(3) || martial());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (slide && !Blind)
|
|
pline("Whee! %s %s across the %s.", Doname2(g.kickedobj),
|
|
otense(g.kickedobj, "slide"), surface(x, y));
|
|
|
|
if (costly && !isgold)
|
|
addtobill(g.kickedobj, FALSE, FALSE, TRUE);
|
|
obj_extract_self(g.kickedobj);
|
|
(void) snuff_candle(g.kickedobj);
|
|
newsym(x, y);
|
|
mon = bhit(u.dx, u.dy, range, KICKED_WEAPON,
|
|
(int FDECL((*), (MONST_P, OBJ_P))) 0,
|
|
(int FDECL((*), (OBJ_P, OBJ_P))) 0, &g.kickedobj);
|
|
if (!g.kickedobj)
|
|
return 1; /* object broken */
|
|
|
|
if (mon) {
|
|
if (mon->isshk && g.kickedobj->where == OBJ_MINVENT
|
|
&& g.kickedobj->ocarry == mon)
|
|
return 1; /* alert shk caught it */
|
|
g.notonhead = (mon->mx != g.bhitpos.x || mon->my != g.bhitpos.y);
|
|
if (isgold ? ghitm(mon, g.kickedobj) /* caught? */
|
|
: thitmonst(mon, g.kickedobj)) /* hit && used up? */
|
|
return 1;
|
|
}
|
|
|
|
/* the object might have fallen down a hole;
|
|
ship_object() will have taken care of shop billing */
|
|
if (g.kickedobj->where == OBJ_MIGRATING)
|
|
return 1;
|
|
|
|
bhitroom = *in_rooms(g.bhitpos.x, g.bhitpos.y, SHOPBASE);
|
|
if (costly && (!costly_spot(g.bhitpos.x, g.bhitpos.y)
|
|
|| *in_rooms(x, y, SHOPBASE) != bhitroom)) {
|
|
if (isgold)
|
|
costly_gold(x, y, g.kickedobj->quan);
|
|
else
|
|
(void) stolen_value(g.kickedobj, x, y, (boolean) shkp->mpeaceful,
|
|
FALSE);
|
|
costly = FALSE; /* already billed */
|
|
}
|
|
|
|
if (flooreffects(g.kickedobj, g.bhitpos.x, g.bhitpos.y, "fall"))
|
|
return 1;
|
|
if (costly) {
|
|
long gt = 0L;
|
|
|
|
/* costly + landed outside shop handled above; must be inside shop */
|
|
if (g.kickedobj->unpaid)
|
|
subfrombill(g.kickedobj, shkp);
|
|
|
|
/* if billed for contained gold during kick, get a refund now */
|
|
if (Has_contents(g.kickedobj)
|
|
&& (gt = contained_gold(g.kickedobj, TRUE)) > 0L)
|
|
donate_gold(gt, shkp, FALSE);
|
|
}
|
|
place_object(g.kickedobj, g.bhitpos.x, g.bhitpos.y);
|
|
stackobj(g.kickedobj);
|
|
newsym(g.kickedobj->ox, g.kickedobj->oy);
|
|
return 1;
|
|
}
|
|
|
|
/* cause of death if kicking kills kicker */
|
|
static char *
|
|
kickstr(buf, kickobjnam)
|
|
char *buf;
|
|
const char *kickobjnam;
|
|
{
|
|
const char *what;
|
|
|
|
if (*kickobjnam)
|
|
what = kickobjnam;
|
|
else if (g.maploc == &g.nowhere)
|
|
what = "nothing";
|
|
else if (IS_DOOR(g.maploc->typ))
|
|
what = "a door";
|
|
else if (IS_TREE(g.maploc->typ))
|
|
what = "a tree";
|
|
else if (IS_STWALL(g.maploc->typ))
|
|
what = "a wall";
|
|
else if (IS_ROCK(g.maploc->typ))
|
|
what = "a rock";
|
|
else if (IS_THRONE(g.maploc->typ))
|
|
what = "a throne";
|
|
else if (IS_FOUNTAIN(g.maploc->typ))
|
|
what = "a fountain";
|
|
else if (IS_GRAVE(g.maploc->typ))
|
|
what = "a headstone";
|
|
else if (IS_SINK(g.maploc->typ))
|
|
what = "a sink";
|
|
else if (IS_ALTAR(g.maploc->typ))
|
|
what = "an altar";
|
|
else if (IS_DRAWBRIDGE(g.maploc->typ))
|
|
what = "a drawbridge";
|
|
else if (g.maploc->typ == STAIRS)
|
|
what = "the stairs";
|
|
else if (g.maploc->typ == LADDER)
|
|
what = "a ladder";
|
|
else if (g.maploc->typ == IRONBARS)
|
|
what = "an iron bar";
|
|
else
|
|
what = "something weird";
|
|
return strcat(strcpy(buf, "kicking "), what);
|
|
}
|
|
|
|
int
|
|
dokick()
|
|
{
|
|
int x, y;
|
|
int avrg_attrib;
|
|
int dmg = 0, glyph, oldglyph = -1;
|
|
register struct monst *mtmp;
|
|
boolean no_kick = FALSE;
|
|
char buf[BUFSZ], kickobjnam[BUFSZ];
|
|
|
|
kickobjnam[0] = '\0';
|
|
if (nolimbs(g.youmonst.data) || slithy(g.youmonst.data)) {
|
|
You("have no legs to kick with.");
|
|
no_kick = TRUE;
|
|
} else if (verysmall(g.youmonst.data)) {
|
|
You("are too small to do any kicking.");
|
|
no_kick = TRUE;
|
|
} else if (u.usteed) {
|
|
if (yn_function("Kick your steed?", ynchars, 'y') == 'y') {
|
|
You("kick %s.", mon_nam(u.usteed));
|
|
kick_steed();
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else if (Wounded_legs) {
|
|
legs_in_no_shape("kicking", FALSE);
|
|
no_kick = TRUE;
|
|
} else if (near_capacity() > SLT_ENCUMBER) {
|
|
Your("load is too heavy to balance yourself for a kick.");
|
|
no_kick = TRUE;
|
|
} else if (g.youmonst.data->mlet == S_LIZARD) {
|
|
Your("legs cannot kick effectively.");
|
|
no_kick = TRUE;
|
|
} else if (u.uinwater && !rn2(2)) {
|
|
Your("slow motion kick doesn't hit anything.");
|
|
no_kick = TRUE;
|
|
} else if (u.utrap) {
|
|
no_kick = TRUE;
|
|
switch (u.utraptype) {
|
|
case TT_PIT:
|
|
if (!Passes_walls)
|
|
pline("There's not enough room to kick down here.");
|
|
else
|
|
no_kick = FALSE;
|
|
break;
|
|
case TT_WEB:
|
|
case TT_BEARTRAP:
|
|
You_cant("move your %s!", body_part(LEG));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (no_kick) {
|
|
/* ignore direction typed before player notices kick failed */
|
|
display_nhwindow(WIN_MESSAGE, TRUE); /* --More-- */
|
|
return 0;
|
|
}
|
|
|
|
if (!getdir((char *) 0))
|
|
return 0;
|
|
if (!u.dx && !u.dy)
|
|
return 0;
|
|
|
|
x = u.ux + u.dx;
|
|
y = u.uy + u.dy;
|
|
|
|
/* KMH -- Kicking boots always succeed */
|
|
if (uarmf && uarmf->otyp == KICKING_BOOTS)
|
|
avrg_attrib = 99;
|
|
else
|
|
avrg_attrib = (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 3;
|
|
|
|
if (u.uswallow) {
|
|
switch (rn2(3)) {
|
|
case 0:
|
|
You_cant("move your %s!", body_part(LEG));
|
|
break;
|
|
case 1:
|
|
if (is_animal(u.ustuck->data)) {
|
|
pline("%s burps loudly.", Monnam(u.ustuck));
|
|
break;
|
|
}
|
|
/*FALLTHRU*/
|
|
default:
|
|
Your("feeble kick has no effect.");
|
|
break;
|
|
}
|
|
return 1;
|
|
} else if (u.utrap && u.utraptype == TT_PIT) {
|
|
/* must be Passes_walls */
|
|
You("kick at the side of the pit.");
|
|
return 1;
|
|
}
|
|
if (Levitation) {
|
|
int xx, yy;
|
|
|
|
xx = u.ux - u.dx;
|
|
yy = u.uy - u.dy;
|
|
/* doors can be opened while levitating, so they must be
|
|
* reachable for bracing purposes
|
|
* Possible extension: allow bracing against stuff on the side?
|
|
*/
|
|
if (isok(xx, yy) && !IS_ROCK(levl[xx][yy].typ)
|
|
&& !IS_DOOR(levl[xx][yy].typ)
|
|
&& (!Is_airlevel(&u.uz) || !OBJ_AT(xx, yy))) {
|
|
You("have nothing to brace yourself against.");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
mtmp = isok(x, y) ? m_at(x, y) : 0;
|
|
/* might not kick monster if it is hidden and becomes revealed,
|
|
if it is peaceful and player declines to attack, or if the
|
|
hero passes out due to encumbrance with low hp; g.context.move
|
|
will be 1 unless player declines to kick peaceful monster */
|
|
if (mtmp) {
|
|
oldglyph = glyph_at(x, y);
|
|
if (!maybe_kick_monster(mtmp, x, y))
|
|
return g.context.move;
|
|
}
|
|
|
|
wake_nearby();
|
|
u_wipe_engr(2);
|
|
|
|
if (!isok(x, y)) {
|
|
g.maploc = &g.nowhere;
|
|
goto ouch;
|
|
}
|
|
g.maploc = &levl[x][y];
|
|
|
|
/*
|
|
* The next five tests should stay in their present order:
|
|
* monsters, pools, objects, non-doors, doors.
|
|
*
|
|
* [FIXME: Monsters who are hidden underneath objects or
|
|
* in pools should lead to hero kicking the concealment
|
|
* rather than the monster, probably exposing the hidden
|
|
* monster in the process. And monsters who are hidden on
|
|
* ceiling shouldn't be kickable (unless hero is flying?);
|
|
* kicking toward them should just target whatever is on
|
|
* the floor at that spot.]
|
|
*/
|
|
|
|
if (mtmp) {
|
|
/* save mtmp->data (for recoil) in case mtmp gets killed */
|
|
struct permonst *mdat = mtmp->data;
|
|
|
|
kick_monster(mtmp, x, y);
|
|
glyph = glyph_at(x, y);
|
|
/* see comment in attack_checks() */
|
|
if (DEADMONSTER(mtmp)) { /* DEADMONSTER() */
|
|
/* if we mapped an invisible monster and immediately
|
|
killed it, we don't want to forget what we thought
|
|
was there before the kick */
|
|
if (glyph != oldglyph && glyph_is_invisible(glyph))
|
|
show_glyph(x, y, oldglyph);
|
|
} else if (!canspotmon(mtmp)
|
|
/* check <x,y>; monster that evades kick by jumping
|
|
to an unseen square doesn't leave an I behind */
|
|
&& mtmp->mx == x && mtmp->my == y
|
|
&& !glyph_is_invisible(glyph)
|
|
&& !(u.uswallow && mtmp == u.ustuck)) {
|
|
map_invisible(x, y);
|
|
}
|
|
/* recoil if floating */
|
|
if ((Is_airlevel(&u.uz) || Levitation) && g.context.move) {
|
|
int range;
|
|
|
|
range =
|
|
((int) g.youmonst.data->cwt + (weight_cap() + inv_weight()));
|
|
if (range < 1)
|
|
range = 1; /* divide by zero avoidance */
|
|
range = (3 * (int) mdat->cwt) / range;
|
|
|
|
if (range < 1)
|
|
range = 1;
|
|
hurtle(-u.dx, -u.dy, range, TRUE);
|
|
}
|
|
return 1;
|
|
}
|
|
(void) unmap_invisible(x, y);
|
|
if (is_pool(x, y) ^ !!u.uinwater) {
|
|
/* objects normally can't be removed from water by kicking */
|
|
You("splash some %s around.", hliquid("water"));
|
|
return 1;
|
|
}
|
|
|
|
if (OBJ_AT(x, y) && (!Levitation || Is_airlevel(&u.uz)
|
|
|| Is_waterlevel(&u.uz) || sobj_at(BOULDER, x, y))) {
|
|
if (kick_object(x, y, kickobjnam)) {
|
|
if (Is_airlevel(&u.uz))
|
|
hurtle(-u.dx, -u.dy, 1, TRUE); /* assume it's light */
|
|
return 1;
|
|
}
|
|
goto ouch;
|
|
}
|
|
|
|
if (!IS_DOOR(g.maploc->typ)) {
|
|
if (g.maploc->typ == SDOOR) {
|
|
if (!Levitation && rn2(30) < avrg_attrib) {
|
|
cvt_sdoor_to_door(g.maploc); /* ->typ = DOOR */
|
|
pline("Crash! %s a secret door!",
|
|
/* don't "kick open" when it's locked
|
|
unless it also happens to be trapped */
|
|
(g.maploc->doormask & (D_LOCKED | D_TRAPPED)) == D_LOCKED
|
|
? "Your kick uncovers"
|
|
: "You kick open");
|
|
exercise(A_DEX, TRUE);
|
|
if (g.maploc->doormask & D_TRAPPED) {
|
|
g.maploc->doormask = D_NODOOR;
|
|
b_trapped("door", FOOT);
|
|
} else if (g.maploc->doormask != D_NODOOR
|
|
&& !(g.maploc->doormask & D_LOCKED))
|
|
g.maploc->doormask = D_ISOPEN;
|
|
feel_newsym(x, y); /* we know it's gone */
|
|
if (g.maploc->doormask == D_ISOPEN
|
|
|| g.maploc->doormask == D_NODOOR)
|
|
unblock_point(x, y); /* vision */
|
|
return 1;
|
|
} else
|
|
goto ouch;
|
|
}
|
|
if (g.maploc->typ == SCORR) {
|
|
if (!Levitation && rn2(30) < avrg_attrib) {
|
|
pline("Crash! You kick open a secret passage!");
|
|
exercise(A_DEX, TRUE);
|
|
g.maploc->typ = CORR;
|
|
feel_newsym(x, y); /* we know it's gone */
|
|
unblock_point(x, y); /* vision */
|
|
return 1;
|
|
} else
|
|
goto ouch;
|
|
}
|
|
if (IS_THRONE(g.maploc->typ)) {
|
|
register int i;
|
|
if (Levitation)
|
|
goto dumb;
|
|
if ((Luck < 0 || g.maploc->doormask) && !rn2(3)) {
|
|
g.maploc->typ = ROOM;
|
|
g.maploc->doormask = 0; /* don't leave loose ends.. */
|
|
(void) mkgold((long) rnd(200), x, y);
|
|
if (Blind)
|
|
pline("CRASH! You destroy it.");
|
|
else {
|
|
pline("CRASH! You destroy the throne.");
|
|
newsym(x, y);
|
|
}
|
|
exercise(A_DEX, TRUE);
|
|
return 1;
|
|
} else if (Luck > 0 && !rn2(3) && !g.maploc->looted) {
|
|
(void) mkgold((long) rn1(201, 300), x, y);
|
|
i = Luck + 1;
|
|
if (i > 6)
|
|
i = 6;
|
|
while (i--)
|
|
(void) mksobj_at(
|
|
rnd_class(DILITHIUM_CRYSTAL, LUCKSTONE - 1), x, y,
|
|
FALSE, TRUE);
|
|
if (Blind)
|
|
You("kick %s loose!", something);
|
|
else {
|
|
You("kick loose some ornamental coins and gems!");
|
|
newsym(x, y);
|
|
}
|
|
/* prevent endless milking */
|
|
g.maploc->looted = T_LOOTED;
|
|
return 1;
|
|
} else if (!rn2(4)) {
|
|
if (dunlev(&u.uz) < dunlevs_in_dungeon(&u.uz)) {
|
|
fall_through(FALSE, 0);
|
|
return 1;
|
|
} else
|
|
goto ouch;
|
|
}
|
|
goto ouch;
|
|
}
|
|
if (IS_ALTAR(g.maploc->typ)) {
|
|
if (Levitation)
|
|
goto dumb;
|
|
You("kick %s.", (Blind ? something : "the altar"));
|
|
altar_wrath(x, y);
|
|
if (!rn2(3))
|
|
goto ouch;
|
|
exercise(A_DEX, TRUE);
|
|
return 1;
|
|
}
|
|
if (IS_FOUNTAIN(g.maploc->typ)) {
|
|
if (Levitation)
|
|
goto dumb;
|
|
You("kick %s.", (Blind ? something : "the fountain"));
|
|
if (!rn2(3))
|
|
goto ouch;
|
|
/* make metal boots rust */
|
|
if (uarmf && rn2(3))
|
|
if (water_damage(uarmf, "metal boots", TRUE) == ER_NOTHING) {
|
|
Your("boots get wet.");
|
|
/* could cause short-lived fumbling here */
|
|
}
|
|
exercise(A_DEX, TRUE);
|
|
return 1;
|
|
}
|
|
if (IS_GRAVE(g.maploc->typ)) {
|
|
if (Levitation)
|
|
goto dumb;
|
|
if (rn2(4))
|
|
goto ouch;
|
|
exercise(A_WIS, FALSE);
|
|
if (Role_if(PM_ARCHEOLOGIST) || Role_if(PM_SAMURAI)
|
|
|| ((u.ualign.type == A_LAWFUL) && (u.ualign.record > -10))) {
|
|
adjalign(-sgn(u.ualign.type));
|
|
}
|
|
g.maploc->typ = ROOM;
|
|
g.maploc->doormask = 0;
|
|
(void) mksobj_at(ROCK, x, y, TRUE, FALSE);
|
|
del_engr_at(x, y);
|
|
if (Blind)
|
|
pline("Crack! %s broke!", Something);
|
|
else {
|
|
pline_The("headstone topples over and breaks!");
|
|
newsym(x, y);
|
|
}
|
|
return 1;
|
|
}
|
|
if (g.maploc->typ == IRONBARS)
|
|
goto ouch;
|
|
if (IS_TREE(g.maploc->typ)) {
|
|
struct obj *treefruit;
|
|
|
|
/* nothing, fruit or trouble? 75:23.5:1.5% */
|
|
if (rn2(3)) {
|
|
if (!rn2(6) && !(g.mvitals[PM_KILLER_BEE].mvflags & G_GONE))
|
|
You_hear("a low buzzing."); /* a warning */
|
|
goto ouch;
|
|
}
|
|
if (rn2(15) && !(g.maploc->looted & TREE_LOOTED)
|
|
&& (treefruit = rnd_treefruit_at(x, y))) {
|
|
long nfruit = 8L - rnl(7), nfall;
|
|
short frtype = treefruit->otyp;
|
|
|
|
treefruit->quan = nfruit;
|
|
treefruit->owt = weight(treefruit);
|
|
if (is_plural(treefruit))
|
|
pline("Some %s fall from the tree!", xname(treefruit));
|
|
else
|
|
pline("%s falls from the tree!", An(xname(treefruit)));
|
|
nfall = scatter(x, y, 2, MAY_HIT, treefruit);
|
|
if (nfall != nfruit) {
|
|
/* scatter left some in the tree, but treefruit
|
|
* may not refer to the correct object */
|
|
treefruit = mksobj(frtype, TRUE, FALSE);
|
|
treefruit->quan = nfruit - nfall;
|
|
pline("%ld %s got caught in the branches.",
|
|
nfruit - nfall, xname(treefruit));
|
|
dealloc_obj(treefruit);
|
|
}
|
|
exercise(A_DEX, TRUE);
|
|
exercise(A_WIS, TRUE); /* discovered a new food source! */
|
|
newsym(x, y);
|
|
g.maploc->looted |= TREE_LOOTED;
|
|
return 1;
|
|
} else if (!(g.maploc->looted & TREE_SWARM)) {
|
|
int cnt = rnl(4) + 2;
|
|
int made = 0;
|
|
coord mm;
|
|
|
|
mm.x = x;
|
|
mm.y = y;
|
|
while (cnt--) {
|
|
if (enexto(&mm, mm.x, mm.y, &mons[PM_KILLER_BEE])
|
|
&& makemon(&mons[PM_KILLER_BEE], mm.x, mm.y,
|
|
MM_ANGRY))
|
|
made++;
|
|
}
|
|
if (made)
|
|
pline("You've attracted the tree's former occupants!");
|
|
else
|
|
You("smell stale honey.");
|
|
g.maploc->looted |= TREE_SWARM;
|
|
return 1;
|
|
}
|
|
goto ouch;
|
|
}
|
|
if (IS_SINK(g.maploc->typ)) {
|
|
int gend = poly_gender();
|
|
|
|
if (Levitation)
|
|
goto dumb;
|
|
if (rn2(5)) {
|
|
if (!Deaf)
|
|
pline("Klunk! The pipes vibrate noisily.");
|
|
else
|
|
pline("Klunk!");
|
|
exercise(A_DEX, TRUE);
|
|
return 1;
|
|
} else if (!(g.maploc->looted & S_LPUDDING) && !rn2(3)
|
|
&& !(g.mvitals[PM_BLACK_PUDDING].mvflags & G_GONE)) {
|
|
if (Blind)
|
|
You_hear("a gushing sound.");
|
|
else
|
|
pline("A %s ooze gushes up from the drain!",
|
|
hcolor(NH_BLACK));
|
|
(void) makemon(&mons[PM_BLACK_PUDDING], x, y, NO_MM_FLAGS);
|
|
exercise(A_DEX, TRUE);
|
|
newsym(x, y);
|
|
g.maploc->looted |= S_LPUDDING;
|
|
return 1;
|
|
} else if (!(g.maploc->looted & S_LDWASHER) && !rn2(3)
|
|
&& !(g.mvitals[PM_AMOROUS_DEMON].mvflags & G_GONE)) {
|
|
/* can't resist... */
|
|
pline("%s returns!", (Blind ? Something : "The dish washer"));
|
|
if (makemon(&mons[PM_AMOROUS_DEMON], x, y,
|
|
(gend == 1 || (gend == 2 && rn2(2)))
|
|
? MM_FEMALE : MM_MALE))
|
|
newsym(x, y);
|
|
g.maploc->looted |= S_LDWASHER;
|
|
exercise(A_DEX, TRUE);
|
|
return 1;
|
|
} else if (!rn2(3)) {
|
|
if (Blind && Deaf)
|
|
Sprintf(buf, " %s", body_part(FACE));
|
|
else
|
|
buf[0] = '\0';
|
|
pline("%s%s%s.", !Deaf ? "Flupp! " : "",
|
|
!Blind
|
|
? "Muddy waste pops up from the drain"
|
|
: !Deaf
|
|
? "You hear a sloshing sound" /* Deaf-aware */
|
|
: "Something splashes you in the", buf);
|
|
if (!(g.maploc->looted & S_LRING)) { /* once per sink */
|
|
if (!Blind)
|
|
You_see("a ring shining in its midst.");
|
|
(void) mkobj_at(RING_CLASS, x, y, TRUE);
|
|
newsym(x, y);
|
|
exercise(A_DEX, TRUE);
|
|
exercise(A_WIS, TRUE); /* a discovery! */
|
|
g.maploc->looted |= S_LRING;
|
|
}
|
|
return 1;
|
|
}
|
|
goto ouch;
|
|
}
|
|
if (g.maploc->typ == STAIRS || g.maploc->typ == LADDER
|
|
|| IS_STWALL(g.maploc->typ)) {
|
|
if (!IS_STWALL(g.maploc->typ) && g.maploc->ladder == LA_DOWN)
|
|
goto dumb;
|
|
ouch:
|
|
pline("Ouch! That hurts!");
|
|
exercise(A_DEX, FALSE);
|
|
exercise(A_STR, FALSE);
|
|
if (isok(x, y)) {
|
|
if (Blind)
|
|
feel_location(x, y); /* we know we hit it */
|
|
if (is_drawbridge_wall(x, y) >= 0) {
|
|
pline_The("drawbridge is unaffected.");
|
|
/* update maploc to refer to the drawbridge */
|
|
(void) find_drawbridge(&x, &y);
|
|
g.maploc = &levl[x][y];
|
|
}
|
|
}
|
|
if (!rn2(3))
|
|
set_wounded_legs(RIGHT_SIDE, 5 + rnd(5));
|
|
dmg = rnd(ACURR(A_CON) > 15 ? 3 : 5);
|
|
losehp(Maybe_Half_Phys(dmg), kickstr(buf, kickobjnam), KILLED_BY);
|
|
if (Is_airlevel(&u.uz) || Levitation)
|
|
hurtle(-u.dx, -u.dy, rn1(2, 4), TRUE); /* assume it's heavy */
|
|
return 1;
|
|
}
|
|
goto dumb;
|
|
}
|
|
|
|
if (g.maploc->doormask == D_ISOPEN || g.maploc->doormask == D_BROKEN
|
|
|| g.maploc->doormask == D_NODOOR) {
|
|
dumb:
|
|
exercise(A_DEX, FALSE);
|
|
if (martial() || ACURR(A_DEX) >= 16 || rn2(3)) {
|
|
You("kick at empty space.");
|
|
if (Blind)
|
|
feel_location(x, y);
|
|
} else {
|
|
pline("Dumb move! You strain a muscle.");
|
|
exercise(A_STR, FALSE);
|
|
set_wounded_legs(RIGHT_SIDE, 5 + rnd(5));
|
|
}
|
|
if ((Is_airlevel(&u.uz) || Levitation) && rn2(2))
|
|
hurtle(-u.dx, -u.dy, 1, TRUE);
|
|
return 1; /* uses a turn */
|
|
}
|
|
|
|
/* not enough leverage to kick open doors while levitating */
|
|
if (Levitation)
|
|
goto ouch;
|
|
|
|
exercise(A_DEX, TRUE);
|
|
/* door is known to be CLOSED or LOCKED */
|
|
if (rnl(35) < avrg_attrib + (!martial() ? 0 : ACURR(A_DEX))) {
|
|
boolean shopdoor = *in_rooms(x, y, SHOPBASE) ? TRUE : FALSE;
|
|
/* break the door */
|
|
if (g.maploc->doormask & D_TRAPPED) {
|
|
if (flags.verbose)
|
|
You("kick the door.");
|
|
exercise(A_STR, FALSE);
|
|
g.maploc->doormask = D_NODOOR;
|
|
b_trapped("door", FOOT);
|
|
} else if (ACURR(A_STR) > 18 && !rn2(5) && !shopdoor) {
|
|
pline("As you kick the door, it shatters to pieces!");
|
|
exercise(A_STR, TRUE);
|
|
g.maploc->doormask = D_NODOOR;
|
|
} else {
|
|
pline("As you kick the door, it crashes open!");
|
|
exercise(A_STR, TRUE);
|
|
g.maploc->doormask = D_BROKEN;
|
|
}
|
|
feel_newsym(x, y); /* we know we broke it */
|
|
unblock_point(x, y); /* vision */
|
|
if (shopdoor) {
|
|
add_damage(x, y, SHOP_DOOR_COST);
|
|
pay_for_damage("break", FALSE);
|
|
}
|
|
if (in_town(x, y))
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if (is_watch(mtmp->data) && couldsee(mtmp->mx, mtmp->my)
|
|
&& mtmp->mpeaceful) {
|
|
mon_yells(mtmp, "Halt, thief! You're under arrest!");
|
|
(void) angry_guards(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (Blind)
|
|
feel_location(x, y); /* we know we hit it */
|
|
exercise(A_STR, TRUE);
|
|
pline("WHAMMM!!!");
|
|
if (in_town(x, y))
|
|
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
|
|
if (DEADMONSTER(mtmp))
|
|
continue;
|
|
if (is_watch(mtmp->data) && mtmp->mpeaceful
|
|
&& couldsee(mtmp->mx, mtmp->my)) {
|
|
if (levl[x][y].looted & D_WARNED) {
|
|
mon_yells(mtmp,
|
|
"Halt, vandal! You're under arrest!");
|
|
(void) angry_guards(FALSE);
|
|
} else {
|
|
mon_yells(mtmp, "Hey, stop damaging that door!");
|
|
levl[x][y].looted |= D_WARNED;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
drop_to(cc, loc, x,y)
|
|
coord *cc;
|
|
schar loc;
|
|
xchar x,y;
|
|
{
|
|
stairway *stway = stairway_at(x, y);
|
|
|
|
/* cover all the MIGR_xxx choices generated by down_gate() */
|
|
switch (loc) {
|
|
case MIGR_RANDOM: /* trap door or hole */
|
|
if (Is_stronghold(&u.uz)) {
|
|
cc->x = valley_level.dnum;
|
|
cc->y = valley_level.dlevel;
|
|
break;
|
|
} else if (In_endgame(&u.uz) || Is_botlevel(&u.uz)) {
|
|
cc->y = cc->x = 0;
|
|
break;
|
|
}
|
|
/*FALLTHRU*/
|
|
case MIGR_STAIRS_UP:
|
|
case MIGR_LADDER_UP:
|
|
case MIGR_SSTAIRS:
|
|
if (stway) {
|
|
cc->x = stway->tolev.dnum;
|
|
cc->y = stway->tolev.dlevel;
|
|
} else {
|
|
cc->x = u.uz.dnum;
|
|
cc->y = u.uz.dlevel + 1;
|
|
}
|
|
break;
|
|
default:
|
|
case MIGR_NOWHERE:
|
|
/* y==0 means "nowhere", in which case x doesn't matter */
|
|
cc->y = cc->x = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* player or missile impacts location, causing objects to fall down */
|
|
void
|
|
impact_drop(missile, x, y, dlev)
|
|
struct obj *missile; /* caused impact, won't drop itself */
|
|
xchar x, y; /* location affected */
|
|
xchar dlev; /* if !0 send to dlev near player */
|
|
{
|
|
schar toloc;
|
|
register struct obj *obj, *obj2;
|
|
register struct monst *shkp;
|
|
long oct, dct, price, debit, robbed;
|
|
boolean angry, costly, isrock;
|
|
coord cc;
|
|
|
|
if (!OBJ_AT(x, y))
|
|
return;
|
|
|
|
toloc = down_gate(x, y);
|
|
drop_to(&cc, toloc, x, y);
|
|
if (!cc.y)
|
|
return;
|
|
|
|
if (dlev) {
|
|
/* send objects next to player falling through trap door.
|
|
* checked in obj_delivery().
|
|
*/
|
|
toloc = MIGR_WITH_HERO;
|
|
cc.y = dlev;
|
|
}
|
|
|
|
costly = costly_spot(x, y);
|
|
price = debit = robbed = 0L;
|
|
angry = FALSE;
|
|
shkp = (struct monst *) 0;
|
|
/* if 'costly', we must keep a record of ESHK(shkp) before
|
|
* it undergoes changes through the calls to stolen_value.
|
|
* the angry bit must be reset, if needed, in this fn, since
|
|
* stolen_value is called under the 'silent' flag to avoid
|
|
* unsavory pline repetitions.
|
|
*/
|
|
if (costly) {
|
|
if ((shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0) {
|
|
debit = ESHK(shkp)->debit;
|
|
robbed = ESHK(shkp)->robbed;
|
|
angry = !shkp->mpeaceful;
|
|
}
|
|
}
|
|
|
|
isrock = (missile && missile->otyp == ROCK);
|
|
oct = dct = 0L;
|
|
for (obj = g.level.objects[x][y]; obj; obj = obj2) {
|
|
obj2 = obj->nexthere;
|
|
if (obj == missile)
|
|
continue;
|
|
/* number of objects in the pile */
|
|
oct += obj->quan;
|
|
if (obj == uball || obj == uchain)
|
|
continue;
|
|
/* boulders can fall too, but rarely & never due to rocks */
|
|
if ((isrock && obj->otyp == BOULDER)
|
|
|| rn2(obj->otyp == BOULDER ? 30 : 3))
|
|
continue;
|
|
obj_extract_self(obj);
|
|
|
|
if (costly) {
|
|
price += stolen_value(obj, x, y,
|
|
(costly_spot(u.ux, u.uy)
|
|
&& index(u.urooms,
|
|
*in_rooms(x, y, SHOPBASE))),
|
|
TRUE);
|
|
/* set obj->no_charge to 0 */
|
|
if (Has_contents(obj))
|
|
picked_container(obj); /* does the right thing */
|
|
if (obj->oclass != COIN_CLASS)
|
|
obj->no_charge = 0;
|
|
}
|
|
|
|
add_to_migration(obj);
|
|
obj->ox = cc.x;
|
|
obj->oy = cc.y;
|
|
obj->owornmask = (long) toloc;
|
|
|
|
/* number of fallen objects */
|
|
dct += obj->quan;
|
|
}
|
|
|
|
if (dct && cansee(x, y)) { /* at least one object fell */
|
|
const char *what = (dct == 1L ? "object falls" : "objects fall");
|
|
|
|
if (missile)
|
|
pline("From the impact, %sother %s.",
|
|
dct == oct ? "the " : dct == 1L ? "an" : "", what);
|
|
else if (oct == dct)
|
|
pline("%s adjacent %s %s.", dct == 1L ? "The" : "All the", what,
|
|
g.gate_str);
|
|
else
|
|
pline("%s adjacent %s %s.",
|
|
dct == 1L ? "One of the" : "Some of the",
|
|
dct == 1L ? "objects falls" : what, g.gate_str);
|
|
}
|
|
|
|
if (costly && shkp && price) {
|
|
if (ESHK(shkp)->robbed > robbed) {
|
|
You("removed %ld %s worth of goods!", price, currency(price));
|
|
if (cansee(shkp->mx, shkp->my)) {
|
|
if (ESHK(shkp)->customer[0] == 0)
|
|
(void) strncpy(ESHK(shkp)->customer, g.plname, PL_NSIZ);
|
|
if (angry)
|
|
pline("%s is infuriated!", Monnam(shkp));
|
|
else
|
|
pline("\"%s, you are a thief!\"", g.plname);
|
|
} else
|
|
You_hear("a scream, \"Thief!\"");
|
|
hot_pursuit(shkp);
|
|
(void) angry_guards(FALSE);
|
|
return;
|
|
}
|
|
if (ESHK(shkp)->debit > debit) {
|
|
long amt = (ESHK(shkp)->debit - debit);
|
|
You("owe %s %ld %s for goods lost.", Monnam(shkp), amt,
|
|
currency(amt));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* NOTE: ship_object assumes otmp was FREED from fobj or invent.
|
|
* <x,y> is the point of drop. otmp is _not_ an <x,y> resident:
|
|
* otmp is either a kicked, dropped, or thrown object.
|
|
*/
|
|
boolean
|
|
ship_object(otmp, x, y, shop_floor_obj)
|
|
xchar x, y;
|
|
struct obj *otmp;
|
|
boolean shop_floor_obj;
|
|
{
|
|
schar toloc;
|
|
xchar ox, oy;
|
|
coord cc;
|
|
struct obj *obj;
|
|
struct trap *t;
|
|
boolean nodrop, unpaid, container, impact = FALSE, chainthere = FALSE;
|
|
long n = 0L;
|
|
|
|
if (!otmp)
|
|
return FALSE;
|
|
if ((toloc = down_gate(x, y)) == MIGR_NOWHERE)
|
|
return FALSE;
|
|
drop_to(&cc, toloc, x, y);
|
|
if (!cc.y)
|
|
return FALSE;
|
|
|
|
/* objects other than attached iron ball always fall down ladder,
|
|
but have a chance of staying otherwise */
|
|
nodrop = (otmp == uball) || (otmp == uchain)
|
|
|| (toloc != MIGR_LADDER_UP && rn2(3));
|
|
|
|
container = Has_contents(otmp);
|
|
unpaid = is_unpaid(otmp);
|
|
|
|
if (OBJ_AT(x, y)) {
|
|
for (obj = g.level.objects[x][y]; obj; obj = obj->nexthere) {
|
|
if (obj == uchain)
|
|
chainthere = TRUE;
|
|
else if (obj != otmp)
|
|
n += obj->quan;
|
|
}
|
|
if (n)
|
|
impact = TRUE;
|
|
}
|
|
/* boulders never fall through trap doors, but they might knock
|
|
other things down before plugging the hole */
|
|
if (otmp->otyp == BOULDER && ((t = t_at(x, y)) != 0)
|
|
&& is_hole(t->ttyp)) {
|
|
if (impact)
|
|
impact_drop(otmp, x, y, 0);
|
|
return FALSE; /* let caller finish the drop */
|
|
}
|
|
|
|
if (cansee(x, y))
|
|
otransit_msg(otmp, nodrop, chainthere, n);
|
|
|
|
if (nodrop) {
|
|
if (impact)
|
|
impact_drop(otmp, x, y, 0);
|
|
return FALSE;
|
|
}
|
|
|
|
if (unpaid || shop_floor_obj) {
|
|
if (unpaid) {
|
|
(void) stolen_value(otmp, u.ux, u.uy, TRUE, FALSE);
|
|
} else {
|
|
ox = otmp->ox;
|
|
oy = otmp->oy;
|
|
(void) stolen_value(
|
|
otmp, ox, oy,
|
|
(costly_spot(u.ux, u.uy)
|
|
&& index(u.urooms, *in_rooms(ox, oy, SHOPBASE))),
|
|
FALSE);
|
|
}
|
|
/* set otmp->no_charge to 0 */
|
|
if (container)
|
|
picked_container(otmp); /* happens to do the right thing */
|
|
if (otmp->oclass != COIN_CLASS)
|
|
otmp->no_charge = 0;
|
|
}
|
|
|
|
if (otmp->owornmask)
|
|
remove_worn_item(otmp, TRUE);
|
|
|
|
/* some things break rather than ship */
|
|
if (breaktest(otmp)) {
|
|
const char *result;
|
|
|
|
if (objects[otmp->otyp].oc_material == GLASS
|
|
|| otmp->otyp == EXPENSIVE_CAMERA) {
|
|
if (otmp->otyp == MIRROR)
|
|
change_luck(-2);
|
|
result = "crash";
|
|
} else {
|
|
/* penalty for breaking eggs laid by you */
|
|
if (otmp->otyp == EGG && otmp->spe && otmp->corpsenm >= LOW_PM)
|
|
change_luck((schar) -min(otmp->quan, 5L));
|
|
result = "splat";
|
|
}
|
|
You_hear("a muffled %s.", result);
|
|
obj_extract_self(otmp);
|
|
obfree(otmp, (struct obj *) 0);
|
|
return TRUE;
|
|
}
|
|
|
|
add_to_migration(otmp);
|
|
otmp->ox = cc.x;
|
|
otmp->oy = cc.y;
|
|
otmp->owornmask = (long) toloc;
|
|
|
|
/* boulder from rolling boulder trap, no longer part of the trap */
|
|
if (otmp->otyp == BOULDER)
|
|
otmp->otrapped = 0;
|
|
|
|
if (impact) {
|
|
/* the objs impacted may be in a shop other than
|
|
* the one in which the hero is located. another
|
|
* check for a shk is made in impact_drop. it is, e.g.,
|
|
* possible to kick/throw an object belonging to one
|
|
* shop into another shop through a gap in the wall,
|
|
* and cause objects belonging to the other shop to
|
|
* fall down a trap door--thereby getting two shopkeepers
|
|
* angry at the hero in one shot.
|
|
*/
|
|
impact_drop(otmp, x, y, 0);
|
|
newsym(x, y);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
obj_delivery(near_hero)
|
|
boolean near_hero;
|
|
{
|
|
register struct obj *otmp, *otmp2;
|
|
int nx = 0, ny = 0;
|
|
int where;
|
|
boolean nobreak, noscatter;
|
|
stairway *stway;
|
|
d_level fromdlev;
|
|
boolean isladder;
|
|
|
|
for (otmp = g.migrating_objs; otmp; otmp = otmp2) {
|
|
otmp2 = otmp->nobj;
|
|
if (otmp->ox != u.uz.dnum || otmp->oy != u.uz.dlevel)
|
|
continue;
|
|
|
|
where = (int) (otmp->owornmask & 0x7fffL); /* destination code */
|
|
if ((where & MIGR_TO_SPECIES) != 0)
|
|
continue;
|
|
|
|
nobreak = (where & MIGR_NOBREAK) != 0;
|
|
noscatter = (where & MIGR_WITH_HERO) != 0;
|
|
where &= ~(MIGR_NOBREAK | MIGR_NOSCATTER);
|
|
|
|
if (!near_hero ^ (where == MIGR_WITH_HERO))
|
|
continue;
|
|
|
|
obj_extract_self(otmp);
|
|
otmp->owornmask = 0L;
|
|
fromdlev.dnum = otmp->omigr_from_dnum;
|
|
fromdlev.dlevel = otmp->omigr_from_dlevel;
|
|
|
|
isladder = FALSE;
|
|
|
|
switch (where) {
|
|
case MIGR_LADDER_UP:
|
|
isladder = TRUE;
|
|
/*FALLTHRU*/
|
|
case MIGR_STAIRS_UP:
|
|
case MIGR_SSTAIRS:
|
|
if ((stway = stairway_find_from(&fromdlev, isladder)) != 0) {
|
|
nx = stway->sx;
|
|
ny = stway->sy;
|
|
}
|
|
break;
|
|
case MIGR_WITH_HERO:
|
|
nx = u.ux, ny = u.uy;
|
|
break;
|
|
default:
|
|
case MIGR_RANDOM:
|
|
nx = ny = 0;
|
|
break;
|
|
}
|
|
otmp->omigr_from_dnum = 0;
|
|
otmp->omigr_from_dlevel = 0;
|
|
if (nx > 0) {
|
|
place_object(otmp, nx, ny);
|
|
if (!nobreak && !IS_SOFT(levl[nx][ny].typ)) {
|
|
if (where == MIGR_WITH_HERO) {
|
|
if (breaks(otmp, nx, ny))
|
|
continue;
|
|
} else if (breaktest(otmp)) {
|
|
/* assume it broke before player arrived, no messages */
|
|
delobj(otmp);
|
|
continue;
|
|
}
|
|
}
|
|
stackobj(otmp);
|
|
if (!noscatter)
|
|
(void) scatter(nx, ny, rnd(2), 0, otmp);
|
|
else
|
|
newsym(nx, ny);
|
|
} else { /* random location */
|
|
/* set dummy coordinates because there's no
|
|
current position for rloco() to update */
|
|
otmp->ox = otmp->oy = 0;
|
|
if (rloco(otmp) && !nobreak && breaktest(otmp)) {
|
|
/* assume it broke before player arrived, no messages */
|
|
delobj(otmp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
deliver_obj_to_mon(mtmp, cnt, deliverflags)
|
|
int cnt;
|
|
struct monst *mtmp;
|
|
unsigned long deliverflags;
|
|
{
|
|
struct obj *otmp, *otmp2;
|
|
int where, maxobj = 1;
|
|
boolean at_crime_scene = In_mines(&u.uz);
|
|
|
|
if ((deliverflags & DF_RANDOM) && cnt > 1)
|
|
maxobj = rnd(cnt);
|
|
else if (deliverflags & DF_ALL)
|
|
maxobj = 0;
|
|
else
|
|
maxobj = 1;
|
|
|
|
#define DELIVER_PM (M2_UNDEAD | M2_WERE | M2_HUMAN | M2_ELF | M2_DWARF \
|
|
| M2_GNOME | M2_ORC | M2_DEMON | M2_GIANT)
|
|
|
|
cnt = 0;
|
|
for (otmp = g.migrating_objs; otmp; otmp = otmp2) {
|
|
otmp2 = otmp->nobj;
|
|
where = (int) (otmp->owornmask & 0x7fffL); /* destination code */
|
|
if ((where & MIGR_TO_SPECIES) == 0)
|
|
continue;
|
|
|
|
if (otmp->migr_species != NON_PM
|
|
&& ((mtmp->data->mflags2 & DELIVER_PM)
|
|
== (unsigned) otmp->migr_species)) {
|
|
obj_extract_self(otmp);
|
|
otmp->owornmask = 0L;
|
|
otmp->ox = otmp->oy = 0;
|
|
|
|
/* special treatment for orcs and their kind */
|
|
if ((otmp->corpsenm & M2_ORC) != 0 && has_oname(otmp)) {
|
|
if (!has_mgivenname(mtmp)) {
|
|
if (at_crime_scene || !rn2(2))
|
|
mtmp = christen_orc(mtmp,
|
|
at_crime_scene ? ONAME(otmp)
|
|
: (char *) 0,
|
|
/* bought the stolen goods */
|
|
" the Fence");
|
|
}
|
|
free_oname(otmp);
|
|
}
|
|
otmp->migr_species = NON_PM;
|
|
otmp->omigr_from_dnum = 0;
|
|
otmp->omigr_from_dlevel = 0;
|
|
(void) add_to_minv(mtmp, otmp);
|
|
cnt++;
|
|
if (maxobj && cnt >= maxobj)
|
|
break;
|
|
/* getting here implies DF_ALL */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
otransit_msg(otmp, nodrop, chainthere, num)
|
|
register struct obj *otmp;
|
|
boolean nodrop, chainthere;
|
|
long num;
|
|
{
|
|
char *optr = 0, obuf[BUFSZ], xbuf[BUFSZ];
|
|
|
|
if (otmp->otyp == CORPSE) {
|
|
/* Tobjnam() calls xname() and would yield "The corpse";
|
|
we want more specific "The newt corpse" or "Medusa's corpse" */
|
|
optr = upstart(corpse_xname(otmp, (char *) 0, CXN_PFX_THE));
|
|
} else {
|
|
optr = Tobjnam(otmp, (char *) 0);
|
|
}
|
|
Strcpy(obuf, optr);
|
|
|
|
if (num || chainthere) {
|
|
/* As of 3.6.2: use a separate buffer for the suffix to avoid risk of
|
|
overrunning obuf[] (let pline() handle truncation if necessary) */
|
|
if (num) { /* means: other objects are impacted */
|
|
Sprintf(xbuf, " %s %s object%s", otense(otmp, "hit"),
|
|
(num == 1L) ? "another" : "other", (num > 1L) ? "s" : "");
|
|
} else { /* chain-only msg */
|
|
Sprintf(xbuf, " %s your chain", otense(otmp, "rattle"));
|
|
}
|
|
if (nodrop)
|
|
Sprintf(eos(xbuf), ".");
|
|
else
|
|
Sprintf(eos(xbuf), " and %s %s.",
|
|
otense(otmp, "fall"), g.gate_str);
|
|
pline("%s%s", obuf, xbuf);
|
|
} else if (!nodrop)
|
|
pline("%s %s %s.", obuf, otense(otmp, "fall"), g.gate_str);
|
|
}
|
|
|
|
/* migration destination for objects which fall down to next level */
|
|
schar
|
|
down_gate(x, y)
|
|
xchar x, y;
|
|
{
|
|
struct trap *ttmp;
|
|
stairway *stway = stairway_at(x, y);
|
|
|
|
g.gate_str = 0;
|
|
/* this matches the player restriction in goto_level() */
|
|
if (on_level(&u.uz, &qstart_level) && !ok_to_quest()) {
|
|
return MIGR_NOWHERE;
|
|
}
|
|
if (stway && !stway->up && !stway->isladder) {
|
|
g.gate_str = "down the stairs";
|
|
return (stway->tolev.dnum == u.uz.dnum) ? MIGR_STAIRS_UP
|
|
: MIGR_SSTAIRS;
|
|
}
|
|
if (stway && !stway->up && stway->isladder) {
|
|
g.gate_str = "down the ladder";
|
|
return MIGR_LADDER_UP;
|
|
}
|
|
/* hole will always be flagged as seen; trap drop might or might not */
|
|
if ((ttmp = t_at(x, y)) != 0 && ttmp->tseen && is_hole(ttmp->ttyp)) {
|
|
g.gate_str = (ttmp->ttyp == TRAPDOOR) ? "through the trap door"
|
|
: "through the hole";
|
|
return MIGR_RANDOM;
|
|
}
|
|
return MIGR_NOWHERE;
|
|
}
|
|
|
|
/*dokick.c*/
|