]> granicus.if.org Git - nethack/commitdiff
knockback knocking riding hero out of saddle
authorPatR <rankin@nethack.org>
Thu, 27 Oct 2022 22:33:49 +0000 (15:33 -0700)
committerPatR <rankin@nethack.org>
Thu, 27 Oct 2022 22:33:49 +0000 (15:33 -0700)
I've implemented targetted dismount such that being knocked out of
the saddle will place the hero opposite the attacker in preference
to a random spot adjacent to the steed.  If that opposite spot
isn't appropriate, the two spots next to it get tried.

In these map fragments, H is knocking mounted hero off of u.  The
digits indicate priority of potential destinations.

|.....   |..21.
|...2.   |..u2.
|.Hu1.   |.H...
|...2.   |.....

If spot 1 isn't acceptable, both of spots 2 (in random order) will
be tried next.  If those aren't acceptable either, it will try the
other 5 spots adjacent to the steed (the one of those with the
attacker will always be unacceptable).  And as before, it none of
those work, it uses enexto() to pick a random spot as close to the
steed as feasible.

Not knockback:  when dismounting due to polymorph, avoid diagonal
adjacent spots if hero's new form can't move diagonally.  (The hero
can't already be in no-diagonal form because riding requires that
the rider be humanoid.  I keep thinking the restriction is "can't
be polymorphed" but that isn't correct.)

src/cmd.c
src/steed.c
src/uhitm.c

index 65c960f63319e42ec312204f5876074d47759a32..76314e065db80523877e3d685baab3fcd79a533b 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -5433,7 +5433,7 @@ void
 confdir(boolean force_impairment)
 {
     if (force_impairment || u_maybe_impaired()) {
-        register coordxy x = NODIAG(u.umonnum) ? dirs_ord[rn2(4)] : rn2(N_DIRS);
+        int x = NODIAG(u.umonnum) ? (int) dirs_ord[rn2(4)] : rn2(N_DIRS);
 
         u.dx = xdir[x];
         u.dy = ydir[x];
index 5f850270d797cebf1b2480e03737286ad08734c4..72114dc18e5d16ca05a702101de85c56903d0558 100644 (file)
@@ -440,49 +440,111 @@ landing_spot(
     int reason,
     int forceit)
 {
-    int i = 0, distance, min_distance = -1;
+    coord cc, try[8]; /* 8: the 8 spots adjacent to the hero's spot */
+    int i, j, best_j, clockwise_j, counterclk_j,
+        n, viable, distance, min_distance = -1;
     coordxy x, y;
-    boolean found = FALSE;
+    boolean found, impaird, kn_trap, boulder;
     struct trap *t;
 
+    (void) memset((genericptr_t) try, 0, sizeof try);
+    n = 0;
+    j = xytod(u.dx, u.dy);
+    if (reason == DISMOUNT_KNOCKED && j != DIR_ERR) {
+        /* we'll check preferred location first; if viable it'll be picked */
+        best_j = j;
+        try[0].x = u.dx, try[0].y = u.dy;
+        /* the two next best locations are checked second and third */
+        i = rn2(2);
+        clockwise_j = (j + 1) % N_DIRS;
+        dtoxy(&cc, clockwise_j);
+        try[1 + i].x = cc.x, try[1 + i].y = cc.y; /* [1] or [2] */
+        counterclk_j = (j + N_DIRS - 1) % N_DIRS;
+        dtoxy(&cc, counterclk_j);
+        try[2 - i].x = cc.x, try[2 - i].y = cc.y; /* [2] or [1] */
+        n = 3;
+        debugpline3("knock from saddle: best %s, next %s or %s",
+                    directionname(best_j),
+                    directionname(clockwise_j), directionname(counterclk_j));
+    } else {
+        best_j = clockwise_j = counterclk_j = -1;
+    }
+    for (j = 0; j < N_DIRS; ++j) {
+        /* fortunately NODIAG() handling isn't needed for DISMOUNT_KNOCKED
+           because hero can only ride when humanoid */
+        if (j == best_j || j == clockwise_j || j == counterclk_j)
+            continue;
+        /* j==0 is W, j==1 NW, j==2 N, j==3 NE, ..., around to j==7 SW;
+           so odd j values are diagonal directions here */
+        if (reason == DISMOUNT_POLY && NODIAG(u.umonnum) && (j % 1) != 0)
+            continue;
+        dtoxy(&cc, j);
+        try[n++] = cc;
+    }
+
     /*
-     * TODO:
-     *  for reason==DISMOUNT_KNOCKED, prefer the spot directly behind
-     *  current position relative to the attacker; first need to figure
-     *  how to obtain attacker information...
+     * Up to three passes;
+     * i==0: voluntary dismount without impairment avoids known traps and
+     *       boulders;
+     * i==1: voluntary dismount with impairment or knocked out of saddle
+     *       avoids boulders but allows known traps;
+     * i==2: other, allow traps and boulders.
+     *
+     * Fallback to i==1 if nothing appropriate was found for i==0 and
+     * to i==2 as last resort.
      */
-
-    /* avoid known traps (i == 0) and boulders, but allow them as a backup */
-    if (reason != DISMOUNT_BYCHOICE || Stunned || Confusion || Fumbling)
-        i = 1;
-    for (; !found && i < 2; ++i) {
-        for (x = u.ux - 1; x <= u.ux + 1; x++)
-            for (y = u.uy - 1; y <= u.uy + 1; y++) {
-                if (!isok(x, y) || u_at(x, y))
-                    continue;
-
-                if (accessible(x, y) && !MON_AT(x, y)
-                    && test_move(u.ux, u.uy, x - u.ux, y - u.uy, TEST_MOVE)) {
-                    distance = distu(x, y);
-                    if (min_distance < 0 || distance < min_distance
-                        || (distance == min_distance && rn2(2))) {
-                        if (i > 0 || (((t = t_at(x, y)) == 0 || !t->tseen)
-                                      && (!sobj_at(BOULDER, x, y)
-                                          || throws_rocks(g.youmonst.data)))) {
-                            spot->x = x;
-                            spot->y = y;
-                            min_distance = distance;
-                            found = TRUE;
-                        }
+    impaird = (Stunned || Confusion || Fumbling);
+    viable = 0;
+    found = FALSE;
+    for (i = (reason == DISMOUNT_BYCHOICE && !impaird) ? 0
+             : ((reason == DISMOUNT_BYCHOICE && impaird)
+                || reason == DISMOUNT_KNOCKED) ? 1
+               : 2;
+         i <= 2 && !found; ++i) {
+        for (j = 0; j < n; ++j) {
+            x = u.ux + try[j].x;
+            y = u.uy + try[j].y;
+            if (!isok(x, y) || u_at(x, y)) /* [note: u_at() can't happen] */
+                continue;
+
+            if (accessible(x, y) && !MON_AT(x, y)
+                && test_move(u.ux, u.uy, x - u.ux, y - u.uy, TEST_MOVE)) {
+                ++viable;
+                distance = distu(x, y);
+                if (min_distance < 0 /* no viable candidate yet */
+                    /* or better than pending candidate (note: distance
+                       is never less than min_distance because we're
+                       limiting search to radius 1; j==0 won't get here
+                       because 'min_distance < 0' will always pass for it) */
+                    || (distance < min_distance || (best_j != -1 && j == 0))
+                    /* or equally good, maybe substitute this one */
+                    || (distance == min_distance && !rn2(viable))) {
+                    /* traps avoided on pass 0; boulders avoided on 0 and 1 */
+                    kn_trap = i == 0 && ((t = t_at(x, y)) != 0 && t->tseen
+                                         && t->ttyp != VIBRATING_SQUARE);
+                    boulder = i <= 1 && (sobj_at(BOULDER, x, y)
+                                         && !throws_rocks(g.youmonst.data));
+                    if (!kn_trap && !boulder) {
+                        spot->x = x;
+                        spot->y = y;
+                        min_distance = distance;
+                        found = TRUE;
+                        if (best_j != -1 && j < 3)
+                            /* since best_j is first candidate (j==0), j==1
+                               and j==2 can only get here when best_j was
+                               not viable; 50:50 chance for clockwise_j to
+                               come before counterclk_j so each has same
+                               chance to be next after best_j */
+                            break;
                     }
                 }
             }
+        }
     }
 
     /* If we didn't find a good spot and forceit is on, try enexto(). */
-    if (forceit && min_distance < 0
-        && !enexto(spot, u.ux, u.uy, g.youmonst.data))
-        return FALSE;
+    if (forceit && !found)
+        found = enexto(spot, u.ux, u.uy, g.youmonst.data);
 
     return found;
 }
@@ -618,7 +680,7 @@ dismount_steed(
             if (enexto(&cc, u.ux, u.uy, mtmp->data))
                 rloc_to(mtmp, cc.x, cc.y);
             else /* evidently no room nearby; move steed elsewhere */
-                (void) rloc(mtmp, RLOC_ERR|RLOC_NOMSG);
+                (void) rloc(mtmp, RLOC_ERR | RLOC_NOMSG);
             return;
         }
 
@@ -658,12 +720,11 @@ dismount_steed(
              *
              * Clearly this is not the best way to do it.  A full fix would
              * involve having these functions not call pickup() at all,
-             * instead
-             * calling them first and calling pickup() afterwards.  But it
-             * would take a lot of work to keep this change from having any
-             * unforeseen side effects (for instance, you would no longer be
-             * able to walk onto a square with a hole, and autopickup before
-             * falling into the hole).
+             * instead calling them first and calling pickup() afterwards.
+             * But it would take a lot of work to keep this change from
+             * having any unforeseen side effects (for instance, you would
+             * no longer be able to walk onto a square with a hole, and
+             * autopickup before falling into the hole).
              */
             /* [ALI] No need to move the player if the steed died. */
             if (!DEADMONSTER(mtmp)) {
index f5db387c25e0177f31581b36b35d938f3fbdcf01..bd8e6c2a35819525d6eefbd3a62479a664e5d7eb 100644 (file)
@@ -4732,10 +4732,15 @@ mhitm_knockback(
 
     /* do the actual knockback effect */
     if (u_def) {
+        /* normally dx,dy indicates direction hero is throwing, zapping, &c
+           but here it is used to pass the preferred direction for dismount
+           to dismount_steed (used for DISMOUNT_KNOCKED only) */
+        u.dx = sgn(u.ux - magr->mx); /* [sgn() is superfluous here] */
+        u.dy = sgn(u.uy - magr->my); /* [ditto] */
         if (u.usteed)
             dismount_steed(DISMOUNT_KNOCKED);
         else
-            hurtle(u.ux - magr->mx, u.uy - magr->my, rnd(2), FALSE);
+            hurtle(u.dx, u.dy, rnd(2), FALSE);
 
         set_apparxy(magr); /* update magr's idea of where you are */
         if (!rn2(4))