}
}
-/* return TRUE if ball could be dragged
+/* return TRUE if the caller needs to place the ball and chain down again
*
* 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.
/* only need to move the chain? */
if (carried(uball) || distmin(x, y, uball->ox, uball->oy) <= 2) {
+ xchar oldchainx = uchain->ox, oldchainy = uchain->oy;
*bc_control = BC_CHAIN;
move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
if (carried(uball)) {
}
#define CHAIN_IN_MIDDLE(chx, chy) \
(distmin(x, y, chx, chy) <= 1 && distmin(chx, chy, uball->ox, uball->oy) <= 1)
+#define IS_CHAIN_ROCK(x,y) \
+(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.
+ */
+#define SKIP_TO_DRAG do { *chainx = oldchainx; *chainy = oldchainy; \
+ move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy); \
+ goto drag; } while(0)
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))
+ SKIP_TO_DRAG;
break;
/* player is distance 2/1 from ball; move chain to one of the
tempy = y;
tempy2 = uball->oy;
}
- if (dist2(tempx, tempy, uchain->ox, uchain->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;
+ *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;
+ *chainx = tempx;
+ *chainy = tempy;
+ } else if (IS_CHAIN_ROCK(tempx, tempy) &&
+ IS_CHAIN_ROCK(tempx2, tempy2)) {
+ SKIP_TO_DRAG;
+ } else if (dist2(tempx, tempy, uchain->ox, uchain->oy) <
dist2(tempx2, tempy2, uchain->ox, uchain->oy) ||
((dist2(tempx, tempy, uchain->ox, uchain->oy) ==
dist2(tempx2, tempy2, uchain->ox, uchain->oy)) && rn2(2))) {
case 4:
if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
break;
- *chainx = (x + uchain->ox)/2;
- *chainy = (y + uchain->oy)/2;
+ *chainx = (x + uball->ox)/2;
+ *chainy = (y + uball->oy)/2;
+ if (IS_CHAIN_ROCK(*chainx, *chainy))
+ SKIP_TO_DRAG;
break;
/* ball is one space diagonal from player. Check for the
*chainx = uball->ox;
else
*chainy = uball->oy;
+ if (IS_CHAIN_ROCK(*chainx, *chainy))
+ SKIP_TO_DRAG;
break;
}
/* fall through */
default: impossible("bad chain movement");
break;
}
+#undef SKIP_TO_DRAG
+#undef IS_CHAIN_ROCK
#undef CHAIN_IN_MIDDLE
return TRUE;
}
- if (near_capacity() > SLT_ENCUMBER) {
+drag:
+
+ if (near_capacity() > SLT_ENCUMBER && dist2(x, y, u.ux, u.uy) <= 2) {
You("cannot %sdrag the heavy iron ball.",
invent ? "carry all that and also " : "");
nomul(0);
}
}
- *bc_control = BC_BALL|BC_CHAIN;;
+ *bc_control = BC_BALL|BC_CHAIN;
move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
- *ballx = uchain->ox;
- *bally = uchain->oy;
- *chainx = u.ux;
- *chainy = u.uy;
+ if (dist2(x, y, u.ux, u.uy) > 2) {
+ /* Awful case: we're still in range of the ball, so we thought we
+ * could only move the chain, but it turned out that the target
+ * square for the chain was rock, so we had to drag it instead.
+ * But we can't drag it either, because we teleported and are more
+ * than one square from our old position. Revert to the teleport
+ * behavior.
+ */
+ *ballx = *chainx = x;
+ *bally = *chainy = y;
+ } else {
+ *ballx = uchain->ox;
+ *bally = uchain->oy;
+ *chainx = u.ux;
+ *chainy = u.uy;
+ }
*cause_delay = TRUE;
return TRUE;
}
xchar ballx, bally, chainx, chainy;
boolean cause_delay;
- /* this should only drag the chain (and never give a near-
- capacity message) since we already checked ball distance */
- (void) drag_ball(u.ux, u.uy, &bc_control, &ballx, &bally,
- &chainx, &chainy, &cause_delay);
- move_bc(0, bc_control, ballx, bally, chainx, chainy);
+ /* 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))
+ move_bc(0, bc_control, ballx, bally, chainx, chainy);
} else
placebc();
}