1 /* SCCS Id: @(#)ball.c 3.4 2003/02/03 */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* Ball & Chain =============================================================*/
9 STATIC_DCL int NDECL(bc_order);
10 STATIC_DCL void NDECL(litter);
17 gets_hit = (((uball->ox != u.ux) || (uball->oy != u.uy)) &&
18 ((uwep == uball)? FALSE : (boolean)rn2(5)));
20 pline("Startled, you drop the iron ball.");
22 setuwep((struct obj *)0);
23 if (uswapwep == uball)
24 setuswapwep((struct obj *)0);
26 setuqwep((struct obj *)0);;
32 pline_The("iron ball falls on your %s.",
35 if(is_metallic(uarmh)) {
36 pline("Fortunately, you are wearing a hard helmet.");
38 } else if (flags.verbose)
39 Your("%s does not protect you.", xname(uarmh));
41 losehp(dmg, "crunched in the head by an iron ball",
47 * To make this work, we have to mess with the hero's mind. The rules for
50 * 1. If the hero can see them, fine.
51 * 2. If the hero can't see either, it isn't seen.
52 * 3. If either is felt it is seen.
53 * 4. If either is felt and moved, it disappears.
55 * If the hero can see, then when a move is done, the ball and chain are
56 * first picked up, the positions under them are corrected, then they
57 * are moved after the hero moves. Not too bad.
59 * If the hero is blind, then she can "feel" the ball and/or chain at any
60 * time. However, when the hero moves, the felt ball and/or chain become
61 * unfelt and whatever was felt "under" the ball&chain appears. Pretty
62 * nifty, but it requires that the ball&chain "remember" what was under
63 * them --- i.e. they pick-up glyphs when they are felt and drop them when
64 * moved (and felt). When swallowed, the ball&chain are pulled completely
65 * off of the dungeon, but are still on the object chain. They are placed
66 * under the hero when she is expelled.
71 * int u.bglyph glyph under the ball
72 * int u.cglyph glyph under the chain
73 * int u.bc_felt mask for ball/chain being felt
74 * #define BC_BALL 0x01 bit mask in u.bc_felt for ball
75 * #define BC_CHAIN 0x02 bit mask in u.bc_felt for chain
76 * int u.bc_order ball & chain order
78 * u.bc_felt is also manipulated in display.c and read.c, the others only
79 * in this file. None of these variables are valid unless the player is
83 /* values for u.bc_order */
84 #define BCPOS_DIFFER 0 /* ball & chain at different positions */
85 #define BCPOS_CHAIN 1 /* chain on top of ball */
86 #define BCPOS_BALL 2 /* ball on top of chain */
91 * Place the ball & chain under the hero. Make sure that the ball & chain
92 * variables are set (actually only needed when blind, but what the heck).
93 * It is assumed that when this is called, the ball and chain are NOT
94 * attached to the object list.
96 * Should not be called while swallowed.
101 if (!uchain || !uball) {
102 impossible("Where are your ball and chain?");
106 (void) flooreffects(uchain, u.ux, u.uy, ""); /* chain might rust */
108 if (carried(uball)) /* the ball is carried */
109 u.bc_order = BCPOS_DIFFER;
111 /* ball might rust -- already checked when carried */
112 (void) flooreffects(uball, u.ux, u.uy, "");
113 place_object(uball, u.ux, u.uy);
114 u.bc_order = BCPOS_CHAIN;
117 place_object(uchain, u.ux, u.uy);
119 u.bglyph = u.cglyph = levl[u.ux][u.uy].glyph; /* pick up glyph */
127 if (u.uswallow) return; /* ball&chain not placed while swallowed */
129 if (!carried(uball)) {
130 obj_extract_self(uball);
131 if (Blind && (u.bc_felt & BC_BALL)) /* drop glyph */
132 levl[uball->ox][uball->oy].glyph = u.bglyph;
134 newsym(uball->ox,uball->oy);
136 obj_extract_self(uchain);
137 if (Blind && (u.bc_felt & BC_CHAIN)) /* drop glyph */
138 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
140 newsym(uchain->ox,uchain->oy);
141 u.bc_felt = 0; /* feel nothing */
146 * Return the stacking of the hero's ball & chain. This assumes that the
147 * hero is being punished.
154 if (uchain->ox != uball->ox || uchain->oy != uball->oy || carried(uball)
158 for (obj = level.objects[uball->ox][uball->oy]; obj; obj = obj->nexthere) {
159 if (obj == uchain) return BCPOS_CHAIN;
160 if (obj == uball) return BCPOS_BALL;
162 impossible("bc_order: ball&chain not in same location!");
169 * The hero is either about to go blind or already blind and just punished.
170 * Set up the ball and chain variables so that the ball and chain are "felt".
173 set_bc(already_blind)
176 int ball_on_floor = !carried(uball);
178 u.bc_order = bc_order(); /* get the order */
179 u.bc_felt = ball_on_floor ? BC_BALL|BC_CHAIN : BC_CHAIN; /* felt */
181 if (already_blind || u.uswallow) {
182 u.cglyph = u.bglyph = levl[u.ux][u.uy].glyph;
187 * Since we can still see, remove the ball&chain and get the glyph that
188 * would be beneath them. Then put the ball&chain back. This is pretty
189 * disgusting, but it will work.
191 remove_object(uchain);
192 if (ball_on_floor) remove_object(uball);
194 newsym(uchain->ox, uchain->oy);
195 u.cglyph = levl[uchain->ox][uchain->oy].glyph;
197 if (u.bc_order == BCPOS_DIFFER) { /* different locations */
198 place_object(uchain, uchain->ox, uchain->oy);
199 newsym(uchain->ox, uchain->oy);
201 newsym(uball->ox, uball->oy); /* see under ball */
202 u.bglyph = levl[uball->ox][uball->oy].glyph;
203 place_object(uball, uball->ox, uball->oy);
204 newsym(uball->ox, uball->oy); /* restore ball */
208 if (u.bc_order == BCPOS_CHAIN) {
209 place_object(uball, uball->ox, uball->oy);
210 place_object(uchain, uchain->ox, uchain->oy);
212 place_object(uchain, uchain->ox, uchain->oy);
213 place_object(uball, uball->ox, uball->oy);
215 newsym(uball->ox, uball->oy);
223 * Move the ball and chain. This is called twice for every move. The first
224 * time to pick up the ball and chain before the move, the second time to
225 * place the ball and chain after the move. If the ball is carried, this
226 * function should never have BC_BALL as part of its control.
228 * Should not be called while swallowed.
231 move_bc(before, control, ballx, bally, chainx, chainy)
233 xchar ballx, bally, chainx, chainy; /* only matter !before */
237 * The hero is blind. Time to work hard. The ball and chain that
238 * are attached to the hero are very special. The hero knows that
239 * they are attached, so when they move, the hero knows that they
240 * aren't at the last position remembered. This is complicated
241 * by the fact that the hero can "feel" the surrounding locations
242 * at any time, hence, making one or both of them show up again.
243 * So, we have to keep track of which is felt at any one time and
247 if ((control & BC_CHAIN) && (control & BC_BALL)) {
249 * Both ball and chain moved. If felt, drop glyph.
251 if (u.bc_felt & BC_BALL)
252 levl[uball->ox][uball->oy].glyph = u.bglyph;
253 if (u.bc_felt & BC_CHAIN)
254 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
257 /* Pick up glyph at new location. */
258 u.bglyph = levl[ballx][bally].glyph;
259 u.cglyph = levl[chainx][chainy].glyph;
261 movobj(uball,ballx,bally);
262 movobj(uchain,chainx,chainy);
263 } else if (control & BC_BALL) {
264 if (u.bc_felt & BC_BALL) {
265 if (u.bc_order == BCPOS_DIFFER) { /* ball by itself */
266 levl[uball->ox][uball->oy].glyph = u.bglyph;
267 } else if (u.bc_order == BCPOS_BALL) {
268 if (u.bc_felt & BC_CHAIN) { /* know chain is there */
269 map_object(uchain, 0);
271 levl[uball->ox][uball->oy].glyph = u.bglyph;
274 u.bc_felt &= ~BC_BALL; /* no longer feel the ball */
277 /* Pick up glyph at new position. */
278 u.bglyph = (ballx != chainx || bally != chainy) ?
279 levl[ballx][bally].glyph : u.cglyph;
281 movobj(uball,ballx,bally);
282 } else if (control & BC_CHAIN) {
283 if (u.bc_felt & BC_CHAIN) {
284 if (u.bc_order == BCPOS_DIFFER) {
285 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
286 } else if (u.bc_order == BCPOS_CHAIN) {
287 if (u.bc_felt & BC_BALL) {
288 map_object(uball, 0);
290 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
293 u.bc_felt &= ~BC_CHAIN;
295 /* Pick up glyph at new position. */
296 u.cglyph = (ballx != chainx || bally != chainy) ?
297 levl[chainx][chainy].glyph : u.bglyph;
299 movobj(uchain,chainx,chainy);
302 u.bc_order = bc_order(); /* reset the order */
307 * The hero is not blind. To make this work correctly, we need to
308 * pick up the ball and chain before the hero moves, then put them
309 * in their new positions after the hero moves.
314 * Neither ball nor chain is moving, so remember which was
315 * on top until !before. Use the variable u.bc_order
316 * since it is only valid when blind.
318 u.bc_order = bc_order();
321 remove_object(uchain);
322 newsym(uchain->ox, uchain->oy);
323 if (!carried(uball)) {
324 remove_object(uball);
325 newsym(uball->ox, uball->oy);
328 int on_floor = !carried(uball);
330 if ((control & BC_CHAIN) ||
331 (!control && u.bc_order == BCPOS_CHAIN)) {
332 /* If the chain moved or nothing moved & chain on top. */
333 if (on_floor) place_object(uball, ballx, bally);
334 place_object(uchain, chainx, chainy); /* chain on top */
336 place_object(uchain, chainx, chainy);
337 if (on_floor) place_object(uball, ballx, bally);
340 newsym(chainx, chainy);
341 if (on_floor) newsym(ballx, bally);
346 /* return TRUE if the caller needs to place the ball and chain down again
348 * Should not be called while swallowed. Should be called before movement,
349 * because we might want to move the ball or chain to the hero's old position.
351 * It is called if we are moving. It is also called if we are teleporting
352 * *if* the ball doesn't move and we thus must drag the chain. It is not
353 * called for ordinary teleportation.
355 * allow_drag is only used in the ugly special case where teleporting must
356 * drag the chain, while an identical-looking movement must drag both the ball
360 drag_ball(x, y, bc_control, ballx, bally, chainx, chainy, cause_delay,
364 xchar *ballx, *bally, *chainx, *chainy;
365 boolean *cause_delay;
368 struct trap *t = (struct trap *)0;
369 boolean already_in_rock;
373 *chainx = uchain->ox;
374 *chainy = uchain->oy;
376 *cause_delay = FALSE;
378 if (dist2(x, y, uchain->ox, uchain->oy) <= 2) { /* nothing moved */
379 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
383 /* only need to move the chain? */
384 if (carried(uball) || distmin(x, y, uball->ox, uball->oy) <= 2) {
385 xchar oldchainx = uchain->ox, oldchainy = uchain->oy;
386 *bc_control = BC_CHAIN;
387 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
388 if (carried(uball)) {
389 /* move chain only if necessary */
390 if (distmin(x, y, uchain->ox, uchain->oy) > 1) {
396 #define CHAIN_IN_MIDDLE(chx, chy) \
397 (distmin(x, y, chx, chy) <= 1 && distmin(chx, chy, uball->ox, uball->oy) <= 1)
398 #define IS_CHAIN_ROCK(x,y) \
399 (IS_ROCK(levl[x][y].typ) || (IS_DOOR(levl[x][y].typ) && \
400 (levl[x][y].doormask & (D_CLOSED|D_LOCKED))))
401 /* Don't ever move the chain into solid rock. If we have to, then instead
402 * undo the move_bc() and jump to the drag ball code. Note that this also
403 * means the "cannot carry and drag" message will not appear, since unless we
404 * moved at least two squares there is no possibility of the chain position
405 * being in solid rock.
407 #define SKIP_TO_DRAG { *chainx = oldchainx; *chainy = oldchainy; \
408 move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy); \
410 if (IS_CHAIN_ROCK(u.ux, u.uy) || IS_CHAIN_ROCK(*chainx, *chainy)
411 || IS_CHAIN_ROCK(uball->ox, uball->oy))
412 already_in_rock = TRUE;
414 already_in_rock = FALSE;
416 switch(dist2(x, y, uball->ox, uball->oy)) {
417 /* two spaces diagonal from ball, move chain inbetween */
419 *chainx = (uball->ox + x)/2;
420 *chainy = (uball->oy + y)/2;
421 if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
425 /* player is distance 2/1 from ball; move chain to one of the
432 xchar tempx, tempy, tempx2, tempy2;
434 /* find position closest to current position of chain */
435 /* no effect if current position is already OK */
436 if (abs(x - uball->ox) == 1) {
439 tempy = tempy2 = (uball->oy + y)/2;
441 tempx = tempx2 = (uball->ox + x)/2;
445 if (IS_CHAIN_ROCK(tempx, tempy) &&
446 !IS_CHAIN_ROCK(tempx2, tempy2) &&
449 /* Avoid pathological case *if* not teleporting:
451 * _X move northeast -----> X@
454 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 &&
455 dist2(x, y, tempx, tempy) == 1)
457 /* Avoid pathological case *if* not teleporting:
459 * _X move east -----> X_
462 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 &&
463 dist2(x, y, tempx, tempy) == 2)
468 } else if (!IS_CHAIN_ROCK(tempx, tempy) &&
469 IS_CHAIN_ROCK(tempx2, tempy2) &&
472 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 &&
473 dist2(x, y, tempx2, tempy2) == 1)
475 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 &&
476 dist2(x, y, tempx2, tempy2) == 2)
481 } else if (IS_CHAIN_ROCK(tempx, tempy) &&
482 IS_CHAIN_ROCK(tempx2, tempy2) &&
485 } else if (dist2(tempx, tempy, uchain->ox, uchain->oy) <
486 dist2(tempx2, tempy2, uchain->ox, uchain->oy) ||
487 ((dist2(tempx, tempy, uchain->ox, uchain->oy) ==
488 dist2(tempx2, tempy2, uchain->ox, uchain->oy)) && rn2(2))) {
498 /* ball is two spaces horizontal or vertical from player; move*/
499 /* chain inbetween *unless* current chain position is OK */
501 if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
503 *chainx = (x + uball->ox)/2;
504 *chainy = (y + uball->oy)/2;
505 if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
509 /* ball is one space diagonal from player. Check for the
510 * following special case:
512 * _ moving southwest becomes @_
514 * (This will also catch teleporting that happens to resemble
515 * this case, but oh well.) Otherwise fall through.
518 if (dist2(x, y, uball->ox, uball->oy) == 2 &&
519 dist2(x, y, uchain->ox, uchain->oy) == 4) {
524 if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
531 /* do nothing if possible */
532 if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
534 /* otherwise try to drag chain to player's old position */
535 if (CHAIN_IN_MIDDLE(u.ux, u.uy)) {
540 /* otherwise use player's new position (they must have
541 teleported, for this to happen) */
546 default: impossible("bad chain movement");
551 #undef CHAIN_IN_MIDDLE
557 if (near_capacity() > SLT_ENCUMBER && dist2(x, y, u.ux, u.uy) <= 2) {
558 You("cannot %sdrag the heavy iron ball.",
559 invent ? "carry all that and also " : "");
564 if ((is_pool(uchain->ox, uchain->oy) &&
565 /* water not mere continuation of previous water */
566 (levl[uchain->ox][uchain->oy].typ == POOL ||
567 !is_pool(uball->ox, uball->oy) ||
568 levl[uball->ox][uball->oy].typ == POOL))
569 || ((t = t_at(uchain->ox, uchain->oy)) &&
571 t->ttyp == SPIKED_PIT ||
573 t->ttyp == TRAPDOOR)) ) {
576 You_feel("a tug from the iron ball.");
579 struct monst *victim;
581 You("are jerked back by the iron ball!");
582 if ((victim = m_at(uchain->ox, uchain->oy)) != 0) {
585 tmp = -2 + Luck + find_mac(victim);
586 tmp += omon_adj(victim, uball, TRUE);
588 (void) hmon(victim,uball,1);
590 miss(xname(uball), victim);
592 } /* now check again in case mon died */
593 if (!m_at(uchain->ox, uchain->oy)) {
596 newsym(u.ux0, u.uy0);
600 *bc_control = BC_BALL;
601 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
604 move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy);
610 *bc_control = BC_BALL|BC_CHAIN;
612 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
613 if (dist2(x, y, u.ux, u.uy) > 2) {
614 /* Awful case: we're still in range of the ball, so we thought we
615 * could only move the chain, but it turned out that the target
616 * square for the chain was rock, so we had to drag it instead.
617 * But we can't drag it either, because we teleported and are more
618 * than one square from our old position. Revert to the teleport
621 *ballx = *chainx = x;
622 *bally = *chainy = y;
636 * The punished hero drops or throws her iron ball. If the hero is
637 * blind, we must reset the order and glyph. Check for side effects.
638 * This routine expects the ball to be already placed.
640 * Should not be called while swallowed.
647 u.bc_order = bc_order(); /* get the order */
649 u.bglyph = (u.bc_order) ? u.cglyph : levl[x][y].glyph;
652 if (x != u.ux || y != u.uy) {
654 const char *pullmsg = "The ball pulls you out of the %s!";
656 if (u.utrap && u.utraptype != TT_INFLOOR) {
657 switch(u.utraptype) {
659 pline(pullmsg, "pit");
662 pline(pullmsg, "web");
663 pline_The("web is destroyed!");
664 deltrap(t_at(u.ux,u.uy));
667 pline(pullmsg, "lava");
670 register long side = rn2(3) ? LEFT_SIDE : RIGHT_SIDE;
671 pline(pullmsg, "bear trap");
672 set_wounded_legs(side, rn1(1000, 500));
677 Your("%s %s is severely damaged.",
678 (side == LEFT_SIDE) ? "left" : "right",
680 losehp(2, "leg damage from being pulled out of a bear trap",
687 fill_pit(u.ux, u.uy);
692 if (!Levitation && !MON_AT(x, y) && !u.utrap &&
695 (t->ttyp == PIT || t->ttyp == SPIKED_PIT ||
696 t->ttyp == TRAPDOOR || t->ttyp == HOLE)))) {
703 vision_full_recalc = 1; /* hero has moved, recalculate vision later */
706 /* drop glyph under the chain */
707 if (u.bc_felt & BC_CHAIN)
708 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
709 u.bc_felt = 0; /* feel nothing */
710 /* pick up new glyph */
711 u.cglyph = (u.bc_order) ? u.bglyph : levl[u.ux][u.uy].glyph;
713 movobj(uchain,u.ux,u.uy); /* has a newsym */
715 u.bc_order = bc_order();
717 newsym(u.ux0,u.uy0); /* clean up old position */
718 if (u.ux0 != u.ux || u.uy0 != u.uy) {
720 if (In_sokoban(&u.uz))
721 change_luck(-1); /* Sokoban guilt */
730 struct obj *otmp = invent, *nextobj;
731 int capacity = weight_cap();
734 nextobj = otmp->nobj;
735 if ((otmp != uball) && (rnd(capacity) <= (int)otmp->owt)) {
736 if (canletgo(otmp, "")) {
737 Your("%s you down the stairs.",
738 aobjnam(otmp, "follow"));
750 uchar dragchance = 3;
753 * Assume that the ball falls forward if:
755 * a) the character is wielding it, or
756 * b) the character has both hands available to hold it (i.e. is
757 * not wielding any weapon), or
758 * c) (perhaps) it falls forward out of his non-weapon hand
761 forward = carried(uball) && (uwep == uball || !uwep || !rn2(3));
764 You("lose your grip on the iron ball.");
768 pline_The("iron ball drags you downstairs!");
769 losehp(rnd(6), "dragged downstairs by an iron ball",
775 pline_The("iron ball smacks into you!");
776 losehp(rnd(20), "iron ball collision", KILLED_BY_AN);
777 exercise(A_STR, FALSE);
780 if( (int) dragchance >= rnd(6)) {
781 pline_The("iron ball drags you downstairs!");
782 losehp(rnd(3), "dragged downstairs by an iron ball",
784 exercise(A_STR, FALSE);