-/* NetHack 3.6 dothrow.c $NHDT-Date: 1522967321 2018/04/05 22:28:41 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.135 $ */
+/* NetHack 3.6 dothrow.c $NHDT-Date: 1556201496 2019/04/25 14:11:36 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.160 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2013. */
/* NetHack may be freely redistributed. See license for details. */
You("\94\8eË\82·\82é\82à\82Ì\82ª\82È\82¢\81D");
}
/* if autoquiver is disabled or has failed, prompt for missile;
- fill quiver with it if it's not wielded */
+ fill quiver with it if it's not wielded or worn */
if (!obj) {
+ /* in case we're using ^A to repeat prior 'f' command, don't
+ use direction of previous throw as getobj()'s choice here */
+ in_doagain = 0;
+ /* choose something from inventory, then usually quiver it */
obj = getobj(uslinging() ? bullets : toss_objs, "throw");
/* Q command doesn't allow gold in quiver */
if (obj && !obj->owornmask && obj->oclass != COIN_CLASS)
}
}
-/*
- * Object hits floor at hero's feet. Called from drop() and throwit().
- */
+/* Object hits floor at hero's feet.
+ Called from drop(), throwit(), hold_another_object(). */
void
-hitfloor(obj)
-register struct obj *obj;
+hitfloor(obj, verbosely)
+struct obj *obj;
+boolean verbosely; /* usually True; False if caller has given drop message */
{
- if (IS_SOFT(levl[u.ux][u.uy].typ) || u.uinwater) {
+ if (IS_SOFT(levl[u.ux][u.uy].typ) || u.uinwater || u.uswallow) {
dropy(obj);
return;
}
if (IS_ALTAR(levl[u.ux][u.uy].typ))
doaltarobj(obj);
- else
+ else if (verbosely)
#if 0 /*JP*/
- pline("%s hit%s the %s.", Doname2(obj), (obj->quan == 1L) ? "s" : "",
+ pline("%s %s the %s.", Doname2(obj), otense(obj, "hit"),
surface(u.ux, u.uy));
#else
pline("%s\82Í%s\82É\96½\92\86\82µ\82½\81D", Doname2(obj),
- surface(u.ux,u.uy));
+ surface(u.ux, u.uy));
#endif
if (hero_breaks(obj, u.ux, u.uy, TRUE))
int ox, oy, *range = (int *) arg;
struct obj *obj;
struct monst *mon;
- boolean may_pass = TRUE;
+ boolean may_pass = TRUE, via_jumping, stopping_short;
struct trap *ttmp;
int dmg = 0;
} else if (*range == 0) {
return FALSE; /* previous step wants to stop now */
}
+ via_jumping = (EWwalking & I_SPECIAL) != 0L;
+ stopping_short = (via_jumping && *range < 2);
if (!Passes_walls || !(may_pass = may_passwall(x, y))) {
boolean odoor_diag = (IS_DOOR(levl[x][y].typ)
mon->mundetected = 0; /* wakeup() will handle mimic */
mnam = a_monnam(mon); /* after unhiding */
- pronoun = mhim(mon);
+ pronoun = noit_mhim(mon);
#if 0 /*JP*/
if (!strcmp(mnam, "it")) {
#else
if (!strcmp(mnam, "\89½\8eÒ\82©")) {
#endif
- /* mhim() uses pronoun_gender() which forces neuter if monster
- can't be seen; we want him/her for humanoid sensed by touch */
-#if 0 /*JP*/
- if (!strcmp(pronoun, "it") && humanoid(mon->data))
-#else
- if (!strcmp(pronoun, "\89½\8eÒ\82©") && humanoid(mon->data))
-#endif
- pronoun = genders[mon->female].him;
#if 0 /*JP*/
mnam = !strcmp(pronoun, "it") ? "something" : "someone";
#else
newsym(ox, oy); /* update old position */
vision_recalc(1); /* update for new position */
flush_screen(1);
-
- if (is_pool(x, y) && !u.uinwater
- && ((Is_waterlevel(&u.uz) && levl[x][y].typ == WATER)
- || !(Levitation || Flying || Wwalking))) {
- multi = 0; /* can move, so drown() allows crawling out of water */
- (void) drown();
- return FALSE;
+ /* if terrain type changes, levitation or flying might become blocked
+ or unblocked; might issue message, so do this after map+vision has
+ been updated for new location instead of right after u_on_newpos() */
+ if (levl[u.ux][u.uy].typ != levl[ox][oy].typ)
+ switch_terrain();
+
+ if (is_pool(x, y) && !u.uinwater) {
+ if ((Is_waterlevel(&u.uz) && levl[x][y].typ == WATER)
+ || !(Levitation || Flying || Wwalking)) {
+ multi = 0; /* can move, so drown() allows crawling out of water */
+ (void) drown();
+ return FALSE;
+ } else if (!Is_waterlevel(&u.uz) && !stopping_short) {
+ Norep("You move over %s.", an(is_moat(x, y) ? "moat" : "pool"));
+ }
+ } else if (is_lava(x, y) && !stopping_short) {
+ Norep("You move over some lava.");
}
/* FIXME:
- * Each trap should really trigger on the recoil if
- * it would trigger during normal movement. However,
- * not all the possible side-effects of this are
- * tested [as of 3.4.0] so we trigger those that
- * we have tested, and offer a message for the
- * ones that we have not yet tested.
+ * Each trap should really trigger on the recoil if it would
+ * trigger during normal movement. However, not all the possible
+ * side-effects of this are tested [as of 3.4.0] so we trigger
+ * those that we have tested, and offer a message for the ones
+ * that we have not yet tested.
*/
if ((ttmp = t_at(x, y)) != 0) {
- if (ttmp->ttyp == MAGIC_PORTAL) {
+ if (stopping_short) {
+ ; /* see the comment above hurtle_jump() */
+ } else if (ttmp->ttyp == MAGIC_PORTAL) {
dotrap(ttmp, 0);
return FALSE;
} else if (ttmp->ttyp == VIBRATING_SQUARE) {
dotrap(ttmp, 0); /* doesn't print messages */
} else if (ttmp->ttyp == FIRE_TRAP) {
dotrap(ttmp, 0);
- } else if ((ttmp->ttyp == PIT || ttmp->ttyp == SPIKED_PIT
- || ttmp->ttyp == HOLE || ttmp->ttyp == TRAPDOOR)
+ } else if ((is_pit(ttmp->ttyp) || is_hole(ttmp->ttyp))
&& Sokoban) {
- /* Air currents overcome the recoil */
- dotrap(ttmp, 0);
+ /* air currents overcome the recoil in Sokoban;
+ when jumping, caller performs last step and enters trap */
+ if (!via_jumping)
+ dotrap(ttmp, 0);
*range = 0;
return TRUE;
} else {
xchar x, y;
boolean broken;
{
+ boolean costly_xy;
struct monst *shkp = shop_keeper(*u.ushops);
if (!shkp)
return;
- if (broken || !costly_spot(x, y)
- || *in_rooms(x, y, SHOPBASE) != *u.ushops) {
+ costly_xy = costly_spot(x, y);
+ if (broken || !costly_xy || *in_rooms(x, y, SHOPBASE) != *u.ushops) {
/* thrown out of a shop or into a different shop */
if (is_unpaid(obj))
(void) stolen_value(obj, u.ux, u.uy, (boolean) shkp->mpeaceful,
FALSE);
if (broken)
obj->no_charge = 1;
- } else {
- if (costly_spot(u.ux, u.uy) && costly_spot(x, y)) {
+ } else if (costly_xy) {
+ char *oshops = in_rooms(x, y, SHOPBASE);
+
+ /* ushops0: in case we threw while levitating and recoiled
+ out of shop (most likely to the shk's spot in front of door) */
+ if (*oshops == *u.ushops || *oshops == *u.ushops0) {
if (is_unpaid(obj))
subfrombill(obj, shkp);
else if (x != shkp->mx || y != shkp->my)
} else if (petrifier && !Stone_resistance
&& !(poly_when_stoned(youmonst.data)
&& polymon(PM_STONE_GOLEM))) {
- petrify:
+ petrify:
killer.format = KILLED_BY;
#if 0 /*JP*/
Strcpy(killer.name, "elementary physics"); /* "what goes up..." */
done(STONING);
return obj ? TRUE : FALSE;
}
- hitfloor(obj);
+ hitfloor(obj, TRUE);
thrownobj = 0;
#if 0 /*JP*/
losehp(Maybe_Half_Phys(dmg), "falling object", KILLED_BY_AN);
if ((u.dx || u.dy) && (bhitpos.x != u.ux || bhitpos.y != u.uy)) {
int x = bhitpos.x - u.dx, y = bhitpos.y - u.dy;
- tmp_at(DISP_FLASH, obj_to_glyph(obj));
+ tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng));
while (isok(x,y) && (x != u.ux || y != u.uy)) {
tmp_at(x, y);
delay_output();
register int range, urange;
boolean crossbowing, impaired = (Confusion || Stunned || Blind
|| Hallucination || Fumbling);
+ boolean tethered_weapon = (obj->otyp == AKLYS && (wep_mask & W_WEP) != 0);
notonhead = FALSE; /* reset potentially stale value */
if ((obj->cursed || obj->greased) && (u.dx || u.dy) && !rn2(7)) {
boolean slipok = TRUE;
- if (ammo_and_launcher(obj, uwep))
+ if (ammo_and_launcher(obj, uwep)) {
/*JP
pline("%s!", Tobjnam(obj, "misfire"));
*/
pline("%s\82Í\82Í\82¸\82ê\82½\81I", xname(obj));
- else {
+ } else {
/* only slip if it's greased or meant to be thrown */
if (obj->greased || throwing_weapon(obj))
/* BUG: this message is grammatically incorrect if obj has
mon = u.ustuck;
bhitpos.x = mon->mx;
bhitpos.y = mon->my;
+ if (tethered_weapon)
+ tmp_at(DISP_TETHER, obj_to_glyph(obj, rn2_on_display_rng));
} else if (u.dz) {
if (u.dz < 0
/* Mjollnir must we wielded to be thrown--caller verifies this;
aklys must we wielded as primary to return when thrown */
&& ((Role_if(PM_VALKYRIE) && obj->oartifact == ART_MJOLLNIR)
- || (obj->otyp == AKLYS && (wep_mask & W_WEP) != 0))
+ || tethered_weapon)
&& !impaired) {
#if 0 /*JP*/
pline("%s the %s and returns to your hand!", Tobjnam(obj, "hit"),
for dealing with cursed saddle: throw holy water > */
potionhit(u.usteed, obj, POTHIT_HERO_THROW);
} else {
- hitfloor(obj);
+ hitfloor(obj, TRUE);
}
thrownobj = (struct obj *) 0;
return;
}
} else {
/* crossbow range is independent of strength */
- crossbowing =
- (ammo_and_launcher(obj, uwep) && weapon_type(uwep) == P_CROSSBOW);
+ crossbowing = (ammo_and_launcher(obj, uwep)
+ && weapon_type(uwep) == P_CROSSBOW);
urange = (crossbowing ? 18 : (int) ACURRSTR) / 2;
/* balls are easy to throw or at least roll;
* also, this insures the maximum range of a ball is greater
range = 20; /* you must be giant */
else if (obj->oartifact == ART_MJOLLNIR)
range = (range + 1) / 2; /* it's heavy */
- else if (obj->otyp == AKLYS && (wep_mask & W_WEP) != 0)
+ else if (tethered_weapon) /* primary weapon is aklys */
/* if an aklys is going to return, range is limited by the
length of the attached cord [implicit aspect of item] */
range = min(range, BOLT_LIM / 2);
if (Underwater)
range = 1;
- mon = bhit(u.dx, u.dy, range, THROWN_WEAPON,
+ mon = bhit(u.dx, u.dy, range,
+ tethered_weapon ? THROWN_TETHERED_WEAPON : THROWN_WEAPON,
(int FDECL((*), (MONST_P, OBJ_P))) 0,
(int FDECL((*), (OBJ_P, OBJ_P))) 0, &obj);
thrownobj = obj; /* obj may be null now */
if (Is_airlevel(&u.uz) || Levitation)
hurtle(-u.dx, -u.dy, urange, TRUE);
- if (!obj)
+ if (!obj) {
+ /* bhit display cleanup was left with this caller
+ for tethered_weapon, but clean it up now since
+ we're about to return */
+ if (tethered_weapon)
+ tmp_at(DISP_END, 0);
return;
+ }
}
if (mon) {
}
if (!thrownobj) {
- ; /* missile has already been handled */
+ /* missile has already been handled */
+ if (tethered_weapon) tmp_at(DISP_END, 0);
} else if (u.uswallow) {
- /* ball is not picked up by monster */
- if (obj != uball)
- (void) mpickobj(u.ustuck, obj);
- thrownobj = (struct obj *) 0;
+ if (tethered_weapon) {
+ tmp_at(DISP_END, 0);
+ pline("%s returns to your hand!", The(xname(thrownobj)));
+ thrownobj = addinv(thrownobj);
+ (void) encumber_msg();
+ /* in case addinv() autoquivered */
+ if (thrownobj->owornmask & W_QUIVER)
+ setuqwep((struct obj *) 0);
+ setuwep(thrownobj);
+ } else {
+ /* ball is not picked up by monster */
+ if (obj != uball)
+ (void) mpickobj(u.ustuck, obj);
+ thrownobj = (struct obj *) 0;
+ }
} else {
/* Mjollnir must we wielded to be thrown--caller verifies this;
aklys must we wielded as primary to return when thrown */
if ((obj->oartifact == ART_MJOLLNIR && Role_if(PM_VALKYRIE))
- || (obj->otyp == AKLYS && (wep_mask & W_WEP) != 0)) {
+ || tethered_weapon) {
if (rn2(100)) {
- sho_obj_return_to_u(obj); /* display its flight */
+ if (tethered_weapon)
+ tmp_at(DISP_END, BACKTRACK);
+ else
+ sho_obj_return_to_u(obj); /* display its flight */
if (!impaired && rn2(100)) {
/*JP
thrownobj = (struct obj *) 0;
return;
} else {
+ if (tethered_weapon)
+ tmp_at(DISP_END, 0);
/* when this location is stepped on, the weapon will be
auto-picked up due to 'obj->was_thrown' of 1;
addinv() prevents thrown Mjollnir from being placed
}
}
- if (!IS_SOFT(levl[bhitpos.x][bhitpos.y].typ) && breaktest(obj)) {
- tmp_at(DISP_FLASH, obj_to_glyph(obj));
+ if ((!IS_SOFT(levl[bhitpos.x][bhitpos.y].typ) && breaktest(obj))
+ /* venom [via #monster to spit while poly'd] fails breaktest()
+ but we want to force breakage even when location IS_SOFT() */
+ || obj->oclass == VENOM_CLASS) {
+ tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng));
tmp_at(bhitpos.x, bhitpos.y);
delay_output();
tmp_at(DISP_END, 0);
}
}
-/* an object may hit a monster; various factors adjust the chance of hitting
- */
+/* an object may hit a monster; various factors adjust chance of hitting */
int
omon_adj(mon, obj, mon_notices)
struct monst *mon;
An attentive player will still notice that this is different from
an arrow just landing short of any target (no message in that case),
so will realize that there is a valid target here anyway. */
- if (!canseemon(mon) || (mon->m_ap_type && mon->m_ap_type != M_AP_MONSTER))
+ if (!canseemon(mon) || (M_AP_TYPE(mon) && M_AP_TYPE(mon) != M_AP_MONSTER))
/*JP
pline("%s %s.", The(missile), otense(obj, "miss"));
*/
* No bonuses for fleeing or stunned targets (they don't dodge
* melee blows as readily, but dodging arrows is hard anyway).
* Not affected by traps, etc.
- * Certain items which don't in themselves do damage ignore tmp.
+ * Certain items which don't in themselves do damage ignore 'tmp'.
* Distance and monster size affect chance to hit.
*/
tmp = -1 + Luck + find_mac(mon) + u.uhitinc
}
if (tmp >= dieroll) {
- boolean wasthrown = (thrownobj != 0);
+ boolean wasthrown = (thrownobj != 0),
+ /* remember weapon attribute; hmon() might destroy obj */
+ chopper = is_axe(obj);
/* attack hits mon */
if (hmode == HMON_APPLIED)
u.uconduct.weaphit++;
if (hmon(mon, obj, hmode, dieroll)) { /* mon still alive */
- cutworm(mon, bhitpos.x, bhitpos.y, obj);
+ if (mon->wormno)
+ cutworm(mon, bhitpos.x, bhitpos.y, chopper);
}
exercise(A_DEX, TRUE);
/* if hero was swallowed and projectile killed the engulfer,
if (wasthrown && !thrownobj)
return 1;
- /* projectiles other than magic stones
- sometimes disappear when thrown */
+ /* projectiles other than magic stones sometimes disappear
+ when thrown; projectiles aren't among the types of weapon
+ that hmon() might have destroyed so obj is intact */
if (objects[otyp].oc_skill < P_NONE
&& objects[otyp].oc_skill > -P_BOOMERANG
&& !objects[otyp].oc_magic) {
(void) mpickobj(mon, obj); /* may merge and free obj */
ret = 1;
-nopick:
+ nopick:
if (!Blind)
pline1(buf);
if (!tele_restrict(mon))
boolean from_invent; /* thrown or dropped by player; maybe on shop bill */
{
boolean in_view = Blind ? FALSE : (from_invent || cansee(x, y));
+
if (!breaktest(obj))
return 0;
breakmsg(obj, in_view);