OSDN Git Service

upgrade to 3.6.2
[jnethack/source.git] / src / dothrow.c
index 31bb59f..4ca9dbb 100644 (file)
@@ -1,4 +1,4 @@
-/* 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. */
@@ -428,8 +428,12 @@ dofire()
                 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)
@@ -470,26 +474,26 @@ boolean verbose;
     }
 }
 
-/*
- * 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))
@@ -631,7 +635,7 @@ int x, y;
     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;
 
@@ -646,6 +650,8 @@ int x, y;
     } 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)
@@ -772,20 +778,12 @@ int x, y;
 
         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
@@ -841,25 +839,36 @@ int x, y;
     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) {
@@ -870,11 +879,12 @@ int x, y;
             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 {
@@ -1043,21 +1053,26 @@ struct obj *obj;
 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)
@@ -1215,7 +1230,7 @@ boolean hitsroof;
         } 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..." */
@@ -1232,7 +1247,7 @@ boolean hitsroof;
             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);
@@ -1265,7 +1280,7 @@ struct obj *obj;
     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();
@@ -1287,17 +1302,18 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
     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
@@ -1341,12 +1357,14 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
         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"),
@@ -1369,7 +1387,7 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
                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;
@@ -1391,8 +1409,8 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
         }
     } 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
@@ -1436,7 +1454,7 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
             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);
@@ -1446,7 +1464,8 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
         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 */
@@ -1455,8 +1474,14 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
         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) {
@@ -1483,19 +1508,34 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
     }
 
     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
@@ -1565,6 +1605,8 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
                 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
@@ -1580,8 +1622,11 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
             }
         }
 
-        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);
@@ -1638,8 +1683,7 @@ boolean twoweap; /* used to restore twoweapon mode if wielded weapon returns */
     }
 }
 
-/* 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;
@@ -1696,7 +1740,7 @@ boolean maybe_wakeup;
        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"));
 */
@@ -1741,7 +1785,7 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */
      * 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
@@ -1892,13 +1936,16 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */
         }
 
         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,
@@ -1908,8 +1955,9 @@ register struct obj *obj; /* thrownobj or kickedobj or uwep */
             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) {
@@ -2098,7 +2146,7 @@ register struct obj *obj;
     (void) mpickobj(mon, obj); /* may merge and free obj */
     ret = 1;
 
-nopick:
+ nopick:
     if (!Blind)
         pline1(buf);
     if (!tele_restrict(mon))
@@ -2145,6 +2193,7 @@ xchar x, y;          /* object location (ox, oy may not be right) */
 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);