]> granicus.if.org Git - nethack/commitdiff
fix #K3455 - rocks vs xorns
authorPatR <rankin@nethack.org>
Wed, 2 Feb 2022 13:26:03 +0000 (05:26 -0800)
committerPatR <rankin@nethack.org>
Wed, 2 Feb 2022 13:26:03 +0000 (05:26 -0800)
Implement the suggestion that falling rock traps and rolling boulder
traps be harmless to xorns.  I've extended that to all missiles made
of stone (rocks, gems, boulders, a handful of other things that will
only matter if poly'd hero throws in '<' direction or is hit by stuff
scattered by an explosion).

I excluded ghosts because they would become even harder to kill and
the missile handling would need extra checks to test for blessed objs.

include/mondata.h
include/obj.h
src/do_name.c
src/dothrow.c
src/mthrowu.c
src/trap.c
src/uhitm.c
src/zap.c

index a50183f3ebf93cc73c05bcdb8f80f44df4e3509d..e7b84721dc8b52328f79b830222487057234123d 100644 (file)
 #define touch_petrifies(ptr) \
     ((ptr) == &mons[PM_COCKATRICE] || (ptr) == &mons[PM_CHICKATRICE])
 
+/* missiles made of rocks don't harm these: xorns and earth elementals
+   (but not ghosts and shades because that would impact all missile use
+   and also require an exception for blessed rocks/gems/boulders) */
+#define passes_rocks(ptr) (passes_walls(ptr) && !unsolid(ptr))
+
 #define is_mind_flayer(ptr) \
     ((ptr) == &mons[PM_MIND_FLAYER] || (ptr) == &mons[PM_MASTER_MIND_FLAYER])
 
index 685c9165f5bd112f659aad7d85f60adf77a75e95..6ef303f480490f6187866d02b809c50f8b80fad9 100644 (file)
@@ -226,6 +226,12 @@ struct obj {
 #define uslinging() (uwep && objects[uwep->otyp].oc_skill == P_SLING)
 /* 'is_quest_artifact()' only applies to the current role's artifact */
 #define any_quest_artifact(o) ((o)->oartifact >= ART_ORB_OF_DETECTION)
+/* 'missile' aspect is up to the caller and does not imply is_missile();
+   rings might be launched as missiles when being scattered by an explosion */
+#define stone_missile(o) \
+    ((o) && (objects[(o)->otyp].oc_material == GEMSTONE             \
+             || (objects[(o)->otyp].oc_material == MINERAL))        \
+         && (o)->oclass != RING_CLASS)
 
 /* Armor */
 #define is_shield(otmp)          \
index cde27e4430e13092e15667e398094f4f488f8394..c2103b289b746c8cd0ef62c43f8b9af2f08ed0aa 100644 (file)
@@ -1728,6 +1728,9 @@ x_monnam(
             falseCap = (*pm_name != lowc(*pm_name));
     char *bp;
 
+    if (mtmp == &g.youmonst)
+        return strcpy(buf, "you"); /* ignore article, "invisible", &c */
+
     if (g.program_state.gameover)
         suppress |= SUPPRESS_HALLUCINATION;
     if (article == ARTICLE_YOUR && !mtmp->mtame)
index bb89aaf900df8f8f2c683d6a4d27373dd6403007..c9d07789b721c954825acc5e7701d35aa99a2343 100644 (file)
@@ -535,9 +535,9 @@ endmultishot(boolean verbose)
 /* Object hits floor at hero's feet.
    Called from drop(), throwit(), hold_another_object(), litter(). */
 void
-hitfloor(struct obj *obj,
-         boolean verbosely) /* usually True; False if caller has given
-                               drop message */
+hitfloor(
+    struct obj *obj,
+    boolean verbosely) /* usually True; False if caller has given drop mesg */
 {
     if (IS_SOFT(levl[u.ux][u.uy].typ) || u.uinwater || u.uswallow) {
         dropy(obj);
@@ -1056,7 +1056,8 @@ check_shop_obj(struct obj *obj, xchar x, xchar y, boolean broken)
 }
 
 /* Will 'obj' cause damage if it falls on hero's head when thrown upward?
-   Not used to handle things which break when they hit. */
+   Not used to handle things which break when they hit.
+   Stone missile hitting hero w/ Passes_walls attribute handled separately. */
 static boolean
 harmless_missile(struct obj *obj)
 {
@@ -1167,13 +1168,16 @@ toss_up(struct obj *obj, boolean hitsroof)
         hitfloor(obj, FALSE);
         g.thrownobj = 0;
     } else { /* neither potion nor other breaking object */
-        boolean is_silver = (objects[otyp].oc_material == SILVER),
+        int material = objects[otyp].oc_material;
+        boolean is_silver = (material == SILVER),
                 less_damage = (uarmh && is_metallic(uarmh)
                                && (!is_silver || !Hate_silver)),
+                harmless = (stone_missile(obj)
+                            && passes_rocks(g.youmonst.data)),
                 artimsg = FALSE;
         int dmg = dmgval(obj, &g.youmonst);
 
-        if (obj->oartifact)
+        if (obj->oartifact && !harmless)
             /* need a fake die roll here; rn1(18,2) avoids 1 and 20 */
             artimsg = artifact_hit((struct monst *) 0, &g.youmonst, obj, &dmg,
                                    rn1(18, 2));
@@ -1205,15 +1209,24 @@ toss_up(struct obj *obj, boolean hitsroof)
         dmg = Maybe_Half_Phys(dmg);
 
         if (uarmh) {
-            if (less_damage && dmg < (Upolyd ? u.mh : u.uhp)) {
-                if (!artimsg)
-                    pline("Fortunately, you are wearing a hard helmet.");
+            /* note: 'harmless' and 'petrifier' are mutually exclusive */
+            if ((less_damage && dmg < (Upolyd ? u.mh : u.uhp)) || harmless) {
+                if (!artimsg) {
+                    if (!harmless) /* !harmless => less_damage here */
+                        pline("Fortunately, you are wearing a hard helmet.");
+                    else
+                        pline("Unfortunately, you are wearing %s.",
+                              an(helm_simple_name(uarmh))); /* helm or hat */
+                }
 
             /* helmet definitely protects you when it blocks petrification */
             } else if (!petrifier) {
                 if (flags.verbose)
                     Your("%s does not protect you.", helm_simple_name(uarmh));
             }
+            /* stone missile against hero in xorn form would have been
+               harmless, but hitting a worn helmet negates that */
+            harmless = FALSE;
         } else if (petrifier && !Stone_resistance
                    && !(poly_when_stoned(g.youmonst.data)
                         && polymon(PM_STONE_GOLEM))) {
@@ -1229,9 +1242,13 @@ toss_up(struct obj *obj, boolean hitsroof)
         }
         if (is_silver && Hate_silver)
             pline_The("silver sears you!");
+        if (harmless)
+            hit(thesimpleoname(obj), &g.youmonst, " but doesn't hurt.");
+
         hitfloor(obj, TRUE);
         g.thrownobj = 0;
-        losehp(dmg, "falling object", KILLED_BY_AN);
+        if (!harmless)
+            losehp(dmg, "falling object", KILLED_BY_AN);
     }
     return TRUE;
 }
@@ -1692,8 +1709,9 @@ tmiss(struct obj *obj, struct monst *mon, boolean maybe_wakeup)
  * Also used for kicked objects and for polearms/grapnel applied at range.
  */
 int
-thitmonst(register struct monst *mon,
-          register struct obj *obj) /* g.thrownobj or g.kickedobj or uwep */
+thitmonst(
+    struct monst *mon,
+    struct obj *obj) /* g.thrownobj or g.kickedobj or uwep */
 {
     register int tmp;     /* Base chance to hit */
     register int disttmp; /* distance modifier */
index 82838183f37dca7e58e87473c34d16386b89be93..cf775ec7da5d464aabae196579fd18355ad531bf 100644 (file)
@@ -37,17 +37,18 @@ m_has_launcher_and_ammo(struct monst* mtmp)
     return FALSE;
 }
 
-/* hero is hit by something other than a monster */
+/* hero is hit by something other than a monster (though it could be a
+   missile thrown or shot by a monster) */
 int
 thitu(
-    int tlev,
+    int tlev, /* pseudo-level used when deciding whether to hit hero's AC */
     int dam,
     struct obj **objp,
     const char *name) /* if null, then format `*objp' */
 {
     struct obj *obj = objp ? *objp : 0;
     const char *onm, *knm;
-    boolean is_acid;
+    boolean is_acid, named = (name != 0);
     int kprefix = KILLED_BY_AN, dieroll;
     char onmbuf[BUFSZ], knmbuf[BUFSZ];
 
@@ -90,6 +91,13 @@ thitu(
         if (is_acid && Acid_resistance) {
             pline("It doesn't seem to hurt you.");
             monstseesu(M_SEEN_ACID);
+        } else if (stone_missile(obj) && passes_rocks(g.youmonst.data)) {
+            /* use 'named' as an approximation for "hitting from above";
+               we avoid "passes through you" for horizontal flight path
+               because missile stops and that wording would suggest that
+               it should keep going */
+            pline("It %s you.",
+                  named ? "passes harmlessly through" : "doesn't harm");
         } else if (obj && obj->oclass == POTION_CLASS) {
             /* an explosion which scatters objects might hit hero with one
                (potions deliberately thrown at hero are handled by m_throw) */
@@ -280,8 +288,8 @@ ohitmon(
     struct monst *mtmp, /* accidental target, located at <g.bhitpos.x,.y> */
     struct obj *otmp,   /* missile; might be destroyed by drop_throw */
     int range,          /* how much farther will object travel if it misses;
-                           use -1 to signify to keep going even after hit,
-                           unless it's gone (used for rolling_boulder_traps) */
+                         * use -1 to signify to keep going even after hit,
+                         * unless it's gone (used for rolling_boulder_traps) */
     boolean verbose)/* give message(s) even when you can't see what happened */
 {
     int damage, tmp;
@@ -325,6 +333,9 @@ ohitmon(
         potionhit(mtmp, otmp, POTHIT_OTHER_THROW);
         return 1;
     } else {
+        int material = objects[otmp->otyp].oc_material;
+        boolean harmless = (stone_missile(otmp) && passes_rocks(mtmp->data));
+
         damage = dmgval(otmp, mtmp);
         if (otmp->otyp == ACID_VENOM && resists_acid(mtmp))
             damage = 0;
@@ -336,12 +347,20 @@ ohitmon(
             seemimic(mtmp);
         mtmp->msleeping = 0;
         if (vis) {
-            if (otmp->otyp == EGG)
+            if (otmp->otyp == EGG) {
                 pline("Splat!  %s is hit with %s egg!", Monnam(mtmp),
                       otmp->known ? an(mons[otmp->corpsenm].pmnames[NEUTRAL])
                                   : "an");
-            else
-                hit(distant_name(otmp, mshot_xname), mtmp, exclam(damage));
+            } else {
+                char how[BUFSZ];
+
+                if (!harmless)
+                    Strcpy(how, exclam(damage)); /* "!" or "." */
+                else
+                    Sprintf(how, " but passes harmlessly through %.9s.",
+                            mhim(mtmp));
+                hit(distant_name(otmp, mshot_xname), mtmp, how);
+            }
         } else if (verbose && !g.mtarget)
             pline("%s%s is hit%s", (otmp->otyp == EGG) ? "Splat!  " : "",
                   Monnam(mtmp), exclam(damage));
@@ -361,8 +380,7 @@ ohitmon(
                 }
             }
         }
-        if (objects[otmp->otyp].oc_material == SILVER
-            && mon_hates_silver(mtmp)) {
+        if (material == SILVER && mon_hates_silver(mtmp)) {
             boolean flesh = (!noncorporeal(mtmp->data)
                              && !amorphous(mtmp->data));
 
@@ -395,7 +413,8 @@ ohitmon(
                 damage = 0;
         }
 
-        if (!DEADMONSTER(mtmp)) { /* might already be dead (if petrified) */
+        /* might already be dead (if petrified) */
+        if (!harmless && !DEADMONSTER(mtmp)) {
             mtmp->mhp -= damage;
             if (DEADMONSTER(mtmp)) {
                 if (vis || (verbose && !g.mtarget))
index 5ff0168d47b684a752bf1e5d62d86edd2dda515f..e342c5e14c1311aa5748a7a16243d2223a5c4ff2 100644 (file)
@@ -9,8 +9,7 @@ extern const char *const destroy_strings[][3]; /* from zap.c */
 
 static boolean keep_saddle_with_steedcorpse(unsigned, struct obj *,
                                             struct obj *);
-static boolean mu_maybe_destroy_web(struct monst *, boolean,
-                                    struct trap *);
+static boolean mu_maybe_destroy_web(struct monst *, boolean, struct trap *);
 static struct obj *t_missile(int, struct trap *);
 static int trapeffect_arrow_trap(struct monst *, struct trap *, unsigned);
 static int trapeffect_dart_trap(struct monst *, struct trap *, unsigned);
@@ -1060,6 +1059,7 @@ trapeffect_rocktrap(
     unsigned trflags UNUSED)
 {
     struct obj *otmp;
+    boolean harmless = FALSE;
 
     if (mtmp == &g.youmonst) {
         if (trap->once && trap->tseen && !rn2(15)) {
@@ -1078,20 +1078,31 @@ trapeffect_rocktrap(
             pline("A trap door in %s opens and %s falls on your %s!",
                   the(ceiling(u.ux, u.uy)), an(xname(otmp)), body_part(HEAD));
             if (uarmh) {
-                if (is_metallic(uarmh)) {
+                /* normally passes_rocks() would protect againt a falling
+                   rock, but not when wearing a helmet */
+                if (passes_rocks(g.youmonst.data)) {
+                    pline("Unfortunately, you are wearing %s.",
+                          an(helm_simple_name(uarmh))); /* helm or hat */
+                    dmg = 2;
+                } else if (is_metallic(uarmh)) {
                     pline("Fortunately, you are wearing a hard helmet.");
                     dmg = 2;
                 } else if (flags.verbose) {
                     pline("%s does not protect you.", Yname2(uarmh));
                 }
+            } else if (passes_rocks(g.youmonst.data)) {
+                pline("It passes harmlessly through you.");
+                harmless = TRUE;
             }
             if (!Blind)
                 otmp->dknown = 1;
             stackobj(otmp);
             newsym(u.ux, u.uy); /* map the rock */
 
-            losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN);
-            exercise(A_STR, FALSE);
+            if (!harmless) {
+                losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN);
+                exercise(A_STR, FALSE);
+            }
         }
     } else {
         boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed);
@@ -2655,15 +2666,13 @@ force_launch_placement(void)
 int
 launch_obj(
     short otyp,
-    register int x1,
-    register int y1,
-    register int x2,
-    register int y2,
+    int x1, int y1,
+    int x2, int y2,
     int style)
 {
-    register struct monst *mtmp;
-    register struct obj *otmp, *otmp2;
-    register int dx, dy;
+    struct monst *mtmp;
+    struct obj *otmp, *otmp2;
+    int dx, dy;
     struct obj *singleobj;
     boolean used_up = FALSE;
     boolean otherside = FALSE;
@@ -2733,8 +2742,8 @@ launch_obj(
         singleobj->otrapped = 1;
         style &= ~LAUNCH_KNOWN;
     /*FALLTHRU*/
- roll:
     case ROLL:
+ roll:
         delaycnt = 2;
     /*FALLTHRU*/
     default:
@@ -5637,14 +5646,13 @@ b_trapped(const char* item, int bodypart)
 }
 
 /* Monster is hit by trap. */
-/* Note: doesn't work if both obj and d_override are null */
 static boolean
 thitm(
-    int tlev,
-    struct monst *mon,
-    struct obj *obj,
-    int d_override,
-    boolean nocorpse)
+    int tlev,          /* missile's attack level */
+    struct monst *mon, /* target */
+    struct obj *obj,   /* missile; might be Null */
+    int d_override,    /* non-zero: force hit for this amount of damage */
+    boolean nocorpse)  /* True: a trap is completely burning up the target */
 {
     int strike;
     boolean trapkilled = FALSE;
@@ -5664,9 +5672,11 @@ thitm(
             pline("%s is almost hit by %s!", Monnam(mon), doname(obj));
     } else {
         int dam = 1;
+        boolean harmless = (stone_missile(obj) && passes_rocks(mon->data));
 
         if (obj && cansee(mon->mx, mon->my))
-            pline("%s is hit by %s!", Monnam(mon), doname(obj));
+            pline("%s is hit by %s%s", Monnam(mon), doname(obj),
+                  harmless ? " but is not harmed." : "!");
         if (d_override) {
             dam = d_override;
         } else if (obj) {
@@ -5674,15 +5684,19 @@ thitm(
             if (dam < 1)
                 dam = 1;
         }
-        mon->mhp -= dam;
-        if (mon->mhp <= 0) {
-            int xx = mon->mx, yy = mon->my;
+        if (!harmless) {
+            mon->mhp -= dam;
+            if (mon->mhp <= 0) {
+                int xx = mon->mx, yy = mon->my;
 
-            monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS);
-            if (DEADMONSTER(mon)) {
-                newsym(xx, yy);
-                trapkilled = TRUE;
+                monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS);
+                if (DEADMONSTER(mon)) {
+                    newsym(xx, yy);
+                    trapkilled = TRUE;
+                }
             }
+        } else {
+            strike = 0; /* harmless; don't use up the missile */
         }
     }
     if (obj && (!strike || d_override)) {
index 103a274afe2bb680fa82b55ad37061d1a6302c88..bf03a6dfca259884df9970d298d8942a395b3a50 100644 (file)
@@ -295,11 +295,12 @@ mon_maybe_wakeup_on_hit(struct monst *mtmp)
    using attack type aatyp and/or weapon.
    larger value == easier to hit */
 int
-find_roll_to_hit(struct monst *mtmp,
-                 uchar aatyp,        /* usually AT_WEAP or AT_KICK */
-                 struct obj *weapon, /* uwep or uswapwep or NULL */
-                 int *attk_count,
-                 int *role_roll_penalty)
+find_roll_to_hit(
+    struct monst *mtmp,
+    uchar aatyp,        /* usually AT_WEAP or AT_KICK */
+    struct obj *weapon, /* uwep or uswapwep or NULL */
+    int *attk_count,
+    int *role_roll_penalty)
 {
     int tmp, tmp2;
 
@@ -574,10 +575,9 @@ known_hitum(
 /* hit the monster next to you and the monsters to the left and right of it;
    return False if the primary target is killed, True otherwise */
 static boolean
-hitum_cleave(struct monst *target, /* non-Null; forcefight at nothing doesn't
-                                      cleave... */
-             struct attack *uattk) /* ... but we don't enforce that here; Null
-                                      works ok */
+hitum_cleave(
+    struct monst *target, /* non-Null; forcefight at nothing doesn't cleave +*/
+    struct attack *uattk) /*+ but we don't enforce that here; Null works ok */
 {
     /* swings will be delivered in alternate directions; with consecutive
        attacks it will simulate normal swing and backswing; when swings
@@ -723,12 +723,13 @@ hmon(struct monst *mon,
 
 DISABLE_WARNING_FORMAT_NONLITERAL
 
-/* guts of hmon() */
+/* guts of hmon(); returns True if 'mon' survives */
 static boolean
-hmon_hitmon(struct monst *mon,
-           struct obj *obj,
-           int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */
-           int dieroll)
+hmon_hitmon(
+    struct monst *mon,
+    struct obj *obj,
+    int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */
+    int dieroll)
 {
     int tmp;
     struct permonst *mdat = mon->data;
@@ -751,7 +752,7 @@ hmon_hitmon(struct monst *mon,
                             /* not grapnels; applied implies uwep */
                             || (thrown == HMON_APPLIED && is_pole(uwep)));
     int jousting = 0;
-    long silverhit = 0L;
+    int material = obj ? objects[obj->otyp].oc_material : 0;
     int wtype;
     struct obj *monwep;
     char saved_oname[BUFSZ];
@@ -760,6 +761,8 @@ hmon_hitmon(struct monst *mon,
 
     wakeup(mon, TRUE);
     if (!obj) { /* attack with bare hands */
+        long silverhit = 0L; /* armor mask */
+
         if (mdat == &mons[PM_SHADE]) {
             tmp = 0;
         } else {
@@ -781,10 +784,20 @@ hmon_hitmon(struct monst *mon,
             silvermsg = TRUE;
 
     } else {
+        /* stone missile does not hurt xorn or earth elemental, but doesn't
+           pass all the way through and continue on to some further target */
+        if ((thrown == HMON_THROWN || thrown == HMON_KICKED) /* not Applied */
+            && stone_missile(obj) && passes_rocks(mdat)) {
+            hit(mshot_xname(obj), mon, " but does no harm.");
+            return TRUE;
+        }
+        /* remember obj's name since it might end up being destroyed and
+           we'll want to use it after that */
         if (!(artifact_light(obj) && obj->lamplit))
             Strcpy(saved_oname, cxname(obj));
         else
             Strcpy(saved_oname, bare_artifactname(obj));
+
         if (obj->oclass == WEAPON_CLASS || is_weptool(obj)
             || obj->oclass == GEM_CLASS) {
             /* is it not a melee weapon? */
@@ -803,10 +816,8 @@ hmon_hitmon(struct monst *mon,
                     tmp = 0;
                 else
                     tmp = rnd(2);
-                if (objects[obj->otyp].oc_material == SILVER
-                    && mon_hates_silver(mon)) {
-                    silvermsg = TRUE;
-                    silverobj = TRUE;
+                if (material == SILVER && mon_hates_silver(mon)) {
+                    silvermsg = silverobj = TRUE;
                     /* if it will already inflict dmg, make it worse */
                     tmp += rnd((tmp) ? 20 : 10);
                 }
@@ -891,10 +902,8 @@ hmon_hitmon(struct monst *mon,
                         return TRUE;
                     hittxt = TRUE;
                 }
-                if (objects[obj->otyp].oc_material == SILVER
-                    && mon_hates_silver(mon)) {
-                    silvermsg = TRUE;
-                    silverobj = TRUE;
+                if (material == SILVER && mon_hates_silver(mon)) {
+                    silvermsg = silverobj = TRUE;
                 }
                 if (artifact_light(obj) && obj->lamplit
                     && mon_hates_light(mon))
@@ -1168,8 +1177,7 @@ hmon_hitmon(struct monst *mon,
                     }
                     /* things like silver wands can arrive here so we
                        need another silver check; blessed check too */
-                    if (objects[obj->otyp].oc_material == SILVER
-                        && mon_hates_silver(mon)) {
+                    if (material == SILVER && mon_hates_silver(mon)) {
                         tmp += rnd(20);
                         silvermsg = silverobj = TRUE;
                     }
@@ -1336,9 +1344,9 @@ hmon_hitmon(struct monst *mon,
         /* iron weapon using melee or polearm hit [3.6.1: metal weapon too;
            also allow either or both weapons to cause split when twoweap] */
         && obj && (obj == uwep || (u.twoweap && obj == uswapwep))
-        && ((objects[obj->otyp].oc_material == IRON
+        && ((material == IRON
              /* allow scalpel and tsurugi to split puddings */
-             || objects[obj->otyp].oc_material == METAL)
+             || material == METAL)
             /* but not bashing with darts, arrows or ya */
             && !(is_ammo(obj) || is_missile(obj)))
         && hand_to_hand) {
index dcecbe39c33fd42099d4f250e393f81bf8d8d9ff..e4497f38203673f1392da379c28fb25da749e4eb 100644 (file)
--- a/src/zap.c
+++ b/src/zap.c
@@ -3309,11 +3309,16 @@ exclam(int force)
 }
 
 void
-hit(const char *str, struct monst *mtmp,
-    const char *force) /* usually either "." or "!" */
+hit(const char *str,    /* zap text or missile name */
+    struct monst *mtmp, /* target; for missile, might be hero */
+    const char *force)  /* usually either "." or "!" via exclam() */
 {
-    if ((!cansee(g.bhitpos.x, g.bhitpos.y) && !canspotmon(mtmp)
-         && !engulfing_u(mtmp)) || !flags.verbose)
+    boolean verbosely = (mtmp == &g.youmonst
+                         || (flags.verbose
+                             && (cansee(g.bhitpos.x, g.bhitpos.y)
+                                 || canspotmon(mtmp) || engulfing_u(mtmp))));
+
+    if (!verbosely)
         pline("%s %s it.", The(str), vtense(str, "hit"));
     else
         pline("%s %s %s%s", The(str), vtense(str, "hit"),