diff --git a/include/extern.h b/include/extern.h index 572980753..3dba1c021 100644 --- a/include/extern.h +++ b/include/extern.h @@ -109,7 +109,7 @@ E void NDECL(unplacebc); E void FDECL(set_bc, (int)); E void FDECL(move_bc, (int,int,XCHAR_P,XCHAR_P,XCHAR_P,XCHAR_P)); E boolean FDECL(drag_ball, (XCHAR_P,XCHAR_P, - int *,xchar *,xchar *,xchar *,xchar *, boolean *)); + int *,xchar *,xchar *,xchar *,xchar *, boolean *,BOOLEAN_P)); E void FDECL(drop_ball, (XCHAR_P,XCHAR_P)); E void NDECL(drag_down); @@ -1870,8 +1870,8 @@ E void FDECL(place_monster, (struct monst *,int,int)); E boolean FDECL(goodpos, (int,int,struct monst *)); E boolean FDECL(enexto, (coord *,XCHAR_P,XCHAR_P,struct permonst *)); -E void FDECL(teleds, (int,int)); -E boolean NDECL(safe_teleds); +E void FDECL(teleds, (int,int,BOOLEAN_P)); +E boolean FDECL(safe_teleds, (BOOLEAN_P)); E boolean FDECL(teleport_pet, (struct monst *,BOOLEAN_P)); E void NDECL(tele); E int NDECL(dotele); diff --git a/src/apply.c b/src/apply.c index f1d2bef1b..f95cd1f4a 100644 --- a/src/apply.c +++ b/src/apply.c @@ -1361,7 +1361,7 @@ int magic; /* 0=Physical, otherwise skill level */ if (In_sokoban(&u.uz)) change_luck(-1); - teleds(cc.x, cc.y); + teleds(cc.x, cc.y, TRUE); nomul(-1); nomovemsg = ""; morehungry(rnd(25)); @@ -2180,7 +2180,7 @@ struct obj *obj; if (proficient && rn2(proficient + 2)) { if (!mtmp || enexto(&cc, rx, ry, youmonst.data)) { You("yank yourself out of the pit!"); - teleds(cc.x, cc.y); + teleds(cc.x, cc.y, TRUE); u.utrap = 0; vision_full_recalc = 1; } diff --git a/src/ball.c b/src/ball.c index 4c0250f17..c33285880 100644 --- a/src/ball.c +++ b/src/ball.c @@ -347,15 +347,26 @@ xchar ballx, bally, chainx, chainy; /* only matter !before */ * * Should not be called while swallowed. Should be called before movement, * because we might want to move the ball or chain to the hero's old position. + * + * It is called if we are moving. It is also called if we are teleporting + * *if* the ball doesn't move and we thus must drag the chain. It is not + * called for ordinary teleportation. + * + * allow_drag is only used in the ugly special case where teleporting must + * drag the chain, while an identical-looking movement must drag both the ball + * and chain. */ boolean -drag_ball(x, y, bc_control, ballx, bally, chainx, chainy, cause_delay) +drag_ball(x, y, bc_control, ballx, bally, chainx, chainy, cause_delay, + allow_drag) xchar x, y; int *bc_control; xchar *ballx, *bally, *chainx, *chainy; boolean *cause_delay; +boolean allow_drag; { struct trap *t = (struct trap *)0; + boolean already_in_rock; *ballx = uball->ox; *bally = uball->oy; @@ -375,7 +386,7 @@ boolean *cause_delay; *bc_control = BC_CHAIN; move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy); if (carried(uball)) { - /* move chain only if necessary; assume they didn't teleport */ + /* move chain only if necessary */ if (distmin(x, y, uchain->ox, uchain->oy) > 1) { *chainx = u.ux; *chainy = u.uy; @@ -388,17 +399,26 @@ boolean *cause_delay; (IS_ROCK(levl[x][y].typ) || (IS_DOOR(levl[x][y].typ) && \ (levl[x][y].doormask & (D_CLOSED|D_LOCKED)))) /* Don't ever move the chain into solid rock. If we have to, then instead - * undo the move_bc() and jump to the drag ball code. + * undo the move_bc() and jump to the drag ball code. Note that this also + * means the "cannot carry and drag" message will not appear, since unless we + * moved at least two squares there is no possibility of the chain position + * being in solid rock. */ #define SKIP_TO_DRAG { *chainx = oldchainx; *chainy = oldchainy; \ move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy); \ goto drag; } + if (IS_CHAIN_ROCK(u.ux, u.uy) || IS_CHAIN_ROCK(*chainx, *chainy) + || IS_CHAIN_ROCK(uball->ox, uball->oy)) + already_in_rock = TRUE; + else + already_in_rock = FALSE; + switch(dist2(x, y, uball->ox, uball->oy)) { /* two spaces diagonal from ball, move chain inbetween */ case 8: *chainx = (uball->ox + x)/2; *chainy = (uball->oy + y)/2; - if (IS_CHAIN_ROCK(*chainx, *chainy)) + if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock) SKIP_TO_DRAG; break; @@ -423,37 +443,44 @@ boolean *cause_delay; tempy2 = uball->oy; } if (IS_CHAIN_ROCK(tempx, tempy) && - !IS_CHAIN_ROCK(tempx2, tempy2)) { - /* Avoid pathological case: - * 0 0_ - * _X move northeast -----> X@ - * @ - */ - if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 && - dist2(x, y, tempx, tempy) == 1) - SKIP_TO_DRAG; - /* Avoid pathological case: - * 0 0 - * _X move east -----> X_ - * @ @ - */ - if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 && - dist2(x, y, tempx, tempy) == 2) - SKIP_TO_DRAG; + !IS_CHAIN_ROCK(tempx2, tempy2) && + !already_in_rock) { + if (allow_drag) { + /* Avoid pathological case *if* not teleporting: + * 0 0_ + * _X move northeast -----> X@ + * @ + */ + if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 && + dist2(x, y, tempx, tempy) == 1) + SKIP_TO_DRAG; + /* Avoid pathological case *if* not teleporting: + * 0 0 + * _X move east -----> X_ + * @ @ + */ + if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 && + dist2(x, y, tempx, tempy) == 2) + SKIP_TO_DRAG; + } *chainx = tempx2; *chainy = tempy2; } else if (!IS_CHAIN_ROCK(tempx, tempy) && - IS_CHAIN_ROCK(tempx2, tempy2)) { - if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 && - dist2(x, y, tempx2, tempy2) == 1) - SKIP_TO_DRAG; - if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 && - dist2(x, y, tempx2, tempy2) == 2) - SKIP_TO_DRAG; + IS_CHAIN_ROCK(tempx2, tempy2) && + !already_in_rock) { + if (allow_drag) { + if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 && + dist2(x, y, tempx2, tempy2) == 1) + SKIP_TO_DRAG; + if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 && + dist2(x, y, tempx2, tempy2) == 2) + SKIP_TO_DRAG; + } *chainx = tempx; *chainy = tempy; } else if (IS_CHAIN_ROCK(tempx, tempy) && - IS_CHAIN_ROCK(tempx2, tempy2)) { + IS_CHAIN_ROCK(tempx2, tempy2) && + !already_in_rock) { SKIP_TO_DRAG; } else if (dist2(tempx, tempy, uchain->ox, uchain->oy) < dist2(tempx2, tempy2, uchain->ox, uchain->oy) || @@ -475,7 +502,7 @@ boolean *cause_delay; break; *chainx = (x + uball->ox)/2; *chainy = (y + uball->oy)/2; - if (IS_CHAIN_ROCK(*chainx, *chainy)) + if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock) SKIP_TO_DRAG; break; @@ -494,7 +521,7 @@ boolean *cause_delay; *chainx = uball->ox; else *chainy = uball->oy; - if (IS_CHAIN_ROCK(*chainx, *chainy)) + if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock) SKIP_TO_DRAG; break; } diff --git a/src/dbridge.c b/src/dbridge.c index 589d56963..e49a1aae5 100644 --- a/src/dbridge.c +++ b/src/dbridge.c @@ -400,7 +400,7 @@ int dest, how; if (enexto(&xy, etmp->ex, etmp->ey, etmp->edata)) { pline("A %s force teleports you away...", Hallucination ? "normal" : "strange"); - teleds(xy.x, xy.y); + teleds(xy.x, xy.y, FALSE); } /* otherwise on top of the drawbridge is the * only viable spot in the dungeon, so stay there diff --git a/src/do.c b/src/do.c index 040ae98e0..63f4a1b90 100644 --- a/src/do.c +++ b/src/do.c @@ -953,7 +953,7 @@ boolean at_stairs, falling, portal; pline("A mysterious force momentarily surrounds you..."); if (on_level(newlevel, &u.uz)) { - (void) safe_teleds(); + (void) safe_teleds(FALSE); (void) next_to_u(); return; } else diff --git a/src/hack.c b/src/hack.c index af7e5df45..a7d25e41b 100644 --- a/src/hack.c +++ b/src/hack.c @@ -1126,7 +1126,7 @@ domove() /* Move ball and chain. */ if (Punished) if (!drag_ball(x,y, &bc_control, &ballx, &bally, &chainx, &chainy, - &cause_delay)) + &cause_delay, TRUE)) return; /* Check regions entering/leaving */ diff --git a/src/mhitu.c b/src/mhitu.c index b563f2c5a..3f9dc3508 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -363,7 +363,7 @@ mattacku(mtmp) newsym(mtmp->mx,mtmp->my); place_monster(mtmp, u.ux, u.uy); if(mtmp->wormno) worm_move(mtmp); - teleds(cc.x, cc.y); + teleds(cc.x, cc.y, TRUE); set_apparxy(mtmp); newsym(u.ux,u.uy); } else { diff --git a/src/pray.c b/src/pray.c index cc845683f..5fff68487 100644 --- a/src/pray.c +++ b/src/pray.c @@ -219,7 +219,7 @@ register int trouble; /* teleport should always succeed, but if not, * just untrap them. */ - if(!safe_teleds()) + if(!safe_teleds(FALSE)) u.utrap = 0; break; case TROUBLE_STARVING: @@ -256,7 +256,7 @@ register int trouble; case TROUBLE_STUCK_IN_WALL: Your("surroundings change."); /* no control, but works on no-teleport levels */ - (void) safe_teleds(); + (void) safe_teleds(FALSE); break; case TROUBLE_CURSED_LEVITATION: if (uarmf && uarmf->otyp==LEVITATION_BOOTS diff --git a/src/steed.c b/src/steed.c index deeeb4580..ad7d69ecf 100644 --- a/src/steed.c +++ b/src/steed.c @@ -332,7 +332,7 @@ mount_steed(mtmp, force) } u.usteed = mtmp; remove_monster(mtmp->mx, mtmp->my); - teleds(mtmp->mx, mtmp->my); + teleds(mtmp->mx, mtmp->my, TRUE); return (TRUE); } @@ -568,7 +568,7 @@ dismount_steed(reason) * teleds() clears u.utrap */ in_steed_dismounting = TRUE; - teleds(cc.x, cc.y); + teleds(cc.x, cc.y, TRUE); in_steed_dismounting = FALSE; /* Put your steed in your trap */ diff --git a/src/teleport.c b/src/teleport.c index 3cb68e04a..49730f279 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -201,34 +201,50 @@ boolean trapok; } void -teleds(nux, nuy) +teleds(nux, nuy, allow_drag) register int nux,nuy; +boolean allow_drag; { - boolean dont_teleport_ball = FALSE; + boolean ball_still_in_range = FALSE; + /* If they have to move the ball, then drag if allow_drag is true; + * otherwise they are teleporting, so unplacebc(). + * If they don't have to move the ball, then always "drag" whether or + * not allow_drag is true, because we are calling that function, not + * to drag, but to move the chain. *However* there are some dumb + * special cases: + * 0 0 + * _X move east -----> X_ + * @ @ + * These are permissible if teleporting, but not if dragging. As a + * result, drag_ball() needs to know about allow_drag and might end + * up dragging the ball anyway. Also, drag_ball() might find that + * dragging the ball is completely impossible (ball in range but there's + * rock in the way), in which case it teleports the ball on its own. + */ if (Punished) { - /* If they're teleporting to a position where the ball doesn't need - * to be moved, don't place the ball. Especially useful when this - * function is being called for crawling out of water instead of - * real teleportation. - */ if (!carried(uball) && distmin(nux, nuy, uball->ox, uball->oy) <= 2) - dont_teleport_ball = TRUE; - else - unplacebc(); + ball_still_in_range = TRUE; /* don't have to move the ball */ + else { + /* have to move the ball */ + if (!allow_drag || distmin(u.ux, u.uy, nux, nuy) > 1) { + /* we should not have dist > 1 and allow_drag at the same + * time, but just in case, we must then revert to teleport. + */ + allow_drag = FALSE; + unplacebc(); + } + } } u.utrap = 0; u.ustuck = 0; u.ux0 = u.ux; u.uy0 = u.uy; - u.ux = nux; - u.uy = nuy; - fill_pit(u.ux0, u.uy0); /* do this now so that cansee() is correct */ if (hides_under(youmonst.data)) u.uundetected = OBJ_AT(nux, nuy); else if (youmonst.data->mlet == S_EEL) - u.uundetected = is_pool(u.ux, u.uy); + u.uundetected = is_pool(nux, nuy); else { u.uundetected = 0; /* mimics stop being unnoticed */ @@ -241,25 +257,24 @@ register int nux,nuy; docrt(); } if (Punished) { - if (dont_teleport_ball) { + if (ball_still_in_range || allow_drag) { int bc_control; xchar ballx, bally, chainx, chainy; boolean cause_delay; - /* We really should only be dragging the chain here, since we - * checked the ball distance. However, some pathological - * cases will drag the ball anyway. drag_ball() tries to - * handle those by ignoring near_capacity() and teleporting the - * ball and chain along with you. Bug: if you only teleported - * one square, drag_ball() has no way to distinguish between - * teleporting and moving, and treats it like a move. (Note - * that teleds() doesn't imply teleporting.) - */ - if (drag_ball(u.ux, u.uy, &bc_control, &ballx, &bally, - &chainx, &chainy, &cause_delay)) + if (drag_ball(nux, nuy, &bc_control, &ballx, &bally, + &chainx, &chainy, &cause_delay, allow_drag)) move_bc(0, bc_control, ballx, bally, chainx, chainy); - } else - placebc(); + } + } + /* must set u.ux, u.uy after drag_ball(), which may need to know + the old position if allow_drag is true... */ + u.ux = nux; + u.uy = nuy; + fill_pit(u.ux0, u.uy0); + if (Punished) { + if (!ball_still_in_range && !allow_drag) + placebc(); } initrack(); /* teleports mess up tracking monsters without this */ update_player_regions(); @@ -286,7 +301,8 @@ register int nux,nuy; } boolean -safe_teleds() +safe_teleds(allow_drag) +boolean allow_drag; { register int nux, nuy, tcnt = 0; @@ -296,7 +312,7 @@ safe_teleds() } while (!teleok(nux, nuy, (boolean)(tcnt > 200)) && ++tcnt <= 400); if (tcnt <= 400) { - teleds(nux, nuy); + teleds(nux, nuy, allow_drag); return TRUE; } else return FALSE; @@ -309,7 +325,7 @@ vault_tele() coord c; if (croom && somexy(croom, &c) && teleok(c.x,c.y,FALSE)) { - teleds(c.x,c.y); + teleds(c.x,c.y,FALSE); return; } tele(); @@ -394,14 +410,14 @@ tele() /* possible extensions: introduce a small error if magic power is low; allow transfer to solid rock */ if (teleok(cc.x, cc.y, FALSE)) { - teleds(cc.x, cc.y); + teleds(cc.x, cc.y, FALSE); return; } pline("Sorry..."); } } - (void) safe_teleds(); + (void) safe_teleds(FALSE); } int diff --git a/src/trap.c b/src/trap.c index f412ac330..cd3aadba4 100644 --- a/src/trap.c +++ b/src/trap.c @@ -2642,7 +2642,7 @@ crawl:; You("dump some of your gear to lose weight..."); if (succ) { pline("Pheew! That was close."); - teleds(x,y); + teleds(x,y,TRUE); return(TRUE); } /* still too much weight */ @@ -2655,7 +2655,7 @@ crawl:; "pool of water" : "moat"; done(DROWNING); /* oops, we're still alive. better get out of the water. */ - while (!safe_teleds()) { + while (!safe_teleds(TRUE)) { pline("You're still drowning."); done(DROWNING); } @@ -2758,7 +2758,8 @@ struct trap *ttmp; boolean unused; /* we know there's no monster in the way, and we're not trapped */ - if (!Punished || drag_ball(x, y, &bc, &bx, &by, &cx, &cy, &unused)) { + if (!Punished || drag_ball(x, y, &bc, &bx, &by, &cx, &cy, &unused, + TRUE)) { u.ux0 = u.ux, u.uy0 = u.uy; u.ux = x, u.uy = y; u.umoved = TRUE; @@ -3618,7 +3619,7 @@ lava_effects() killer = lava_killer; You("burn to a crisp..."); done(BURNING); - while (!safe_teleds()) { + while (!safe_teleds(TRUE)) { pline("You're still burning."); done(BURNING); }