From: PatR Date: Thu, 27 Oct 2022 22:33:49 +0000 (-0700) Subject: knockback knocking riding hero out of saddle X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=da32b572a60feb561a5c24b123b58270fc085ab4;p=nethack knockback knocking riding hero out of saddle 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.) --- diff --git a/src/cmd.c b/src/cmd.c index 65c960f63..76314e065 100644 --- 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]; diff --git a/src/steed.c b/src/steed.c index 5f850270d..72114dc18 100644 --- a/src/steed.c +++ b/src/steed.c @@ -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)) { diff --git a/src/uhitm.c b/src/uhitm.c index f5db387c2..bd8e6c2a3 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -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))