make rank() static again
[NetHack.git] / src / ball.c
blobebacb7e83ed92c53e3766643faefc975318a5b1d
1 /* NetHack 3.7 ball.c $NHDT-Date: 1596498150 2020/08/03 23:42:30 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.51 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) David Cohrs, 2006. */
4 /* NetHack may be freely redistributed. See license for details. */
6 /* Ball & Chain
7 * =============================================================*/
9 #include "hack.h"
11 staticfn int bc_order(void);
12 staticfn void litter(void);
13 staticfn void placebc_core(void);
14 staticfn void unplacebc_core(void);
15 staticfn boolean check_restriction(int);
17 static int bcrestriction = 0;
18 #ifdef BREADCRUMBS
19 static struct breadcrumbs bcpbreadcrumbs = {0}, bcubreadcrumbs = {0};
20 #endif
22 void
23 ballrelease(boolean showmsg)
25 if (carried(uball) && !welded(uball)) {
26 if (showmsg)
27 pline("Startled, you drop the iron ball.");
28 if (uwep == uball)
29 setuwep((struct obj *) 0);
30 if (uswapwep == uball)
31 setuswapwep((struct obj *) 0);
32 if (uquiver == uball)
33 setuqwep((struct obj *) 0);
34 /* [this used to test 'if (uwep != uball)' but that always passes
35 after the setuwep() above] */
36 freeinv(uball); /* remove from inventory but don't place on floor */
37 (void) encumber_msg();
41 /* ball&chain might hit hero when falling through a trap door */
42 void
43 ballfall(void)
45 boolean gets_hit;
47 if (!uball || (uball && carried(uball) && welded(uball)))
48 return;
50 gets_hit = (((uball->ox != u.ux) || (uball->oy != u.uy))
51 && ((uwep == uball) ? FALSE : (boolean) rn2(5)));
52 ballrelease(TRUE);
53 if (gets_hit) {
54 int dmg = rn1(7, 25);
56 pline_The("iron ball falls on your %s.", body_part(HEAD));
57 if (uarmh) {
58 if (hard_helmet(uarmh)) {
59 pline("Fortunately, you are wearing a hard helmet.");
60 dmg = 3;
61 } else if (flags.verbose)
62 pline("%s does not protect you.", Yname2(uarmh));
64 losehp(Maybe_Half_Phys(dmg), "crunched in the head by an iron ball",
65 NO_KILLER_PREFIX);
70 * To make this work, we have to mess with the hero's mind. The rules for
71 * ball&chain are:
73 * 1. If the hero can see them, fine.
74 * 2. If the hero can't see either, it isn't seen.
75 * 3. If either is felt it is seen.
76 * 4. If either is felt and moved, it disappears.
78 * If the hero can see, then when a move is done, the ball and chain are
79 * first picked up, the positions under them are corrected, then they
80 * are moved after the hero moves. Not too bad.
82 * If the hero is blind, then she can "feel" the ball and/or chain at any
83 * time. However, when the hero moves, the felt ball and/or chain become
84 * unfelt and whatever was felt "under" the ball&chain appears. Pretty
85 * nifty, but it requires that the ball&chain "remember" what was under
86 * them --- i.e. they pick-up glyphs when they are felt and drop them when
87 * moved (and felt). When swallowed, the ball&chain are pulled completely
88 * off of the dungeon, but are still on the object chain. They are placed
89 * under the hero when she is expelled.
93 * from you.h
94 * int u.bglyph glyph under the ball
95 * int u.cglyph glyph under the chain
96 * int u.bc_felt mask for ball/chain being felt
97 * #define BC_BALL 0x01 bit mask in u.bc_felt for ball
98 * #define BC_CHAIN 0x02 bit mask in u.bc_felt for chain
99 * int u.bc_order ball & chain order
101 * u.bc_felt is also manipulated in display.c and read.c, the others only
102 * in this file. None of these variables are valid unless the player is
103 * Blind.
106 /* values for u.bc_order */
107 #define BCPOS_DIFFER 0 /* ball & chain at different positions */
108 #define BCPOS_CHAIN 1 /* chain on top of ball */
109 #define BCPOS_BALL 2 /* ball on top of chain */
112 * Place the ball & chain under the hero. Make sure that the ball & chain
113 * variables are set (actually only needed when blind, but what the heck).
114 * It is assumed that when this is called, the ball and chain are NOT
115 * attached to the object list.
117 * Should not be called while swallowed except on waterlevel.
119 staticfn void
120 placebc_core(void)
122 if (!uchain || !uball) {
123 impossible("Where are your ball and chain?");
124 return;
127 (void) flooreffects(uchain, u.ux, u.uy, ""); /* chain might rust */
129 if (carried(uball)) { /* the ball is carried */
130 u.bc_order = BCPOS_DIFFER;
131 } else {
132 /* ball might rust -- already checked when carried */
133 (void) flooreffects(uball, u.ux, u.uy, "");
134 place_object(uball, u.ux, u.uy);
135 u.bc_order = BCPOS_CHAIN;
138 place_object(uchain, u.ux, u.uy);
140 u.bglyph = u.cglyph = levl[u.ux][u.uy].glyph; /* pick up glyph */
142 newsym(u.ux, u.uy);
143 bcrestriction = 0;
146 staticfn void
147 unplacebc_core(void)
149 if (u.uswallow) {
150 if (Is_waterlevel(&u.uz)) {
151 /* we need to proceed with the removal from the floor
152 * so that movebubbles() processing will disregard it as
153 * intended. Ignore all the vision stuff.
155 if (!carried(uball))
156 obj_extract_self(uball);
157 obj_extract_self(uchain);
159 /* ball&chain not unplaced while swallowed */
160 return;
163 if (!carried(uball)) {
164 obj_extract_self(uball);
165 if (Blind && (u.bc_felt & BC_BALL)) /* drop glyph */
166 levl[uball->ox][uball->oy].glyph = u.bglyph;
167 maybe_unhide_at(uball->ox, uball->oy);
168 newsym(uball->ox, uball->oy);
170 obj_extract_self(uchain);
171 if (Blind && (u.bc_felt & BC_CHAIN)) /* drop glyph */
172 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
173 maybe_unhide_at(uchain->ox, uchain->oy);
175 newsym(uchain->ox, uchain->oy);
176 u.bc_felt = 0; /* feel nothing */
179 staticfn boolean
180 check_restriction(int restriction)
182 boolean ret = FALSE;
184 if (!bcrestriction || (restriction == override_restriction))
185 ret = TRUE;
186 else
187 ret = (bcrestriction == restriction) ? TRUE : FALSE;
188 return ret;
191 #ifndef BREADCRUMBS
192 void
193 placebc(void)
195 if (!check_restriction(0)) {
196 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
197 char panicbuf[BUFSZ];
199 Sprintf(panicbuf, "placebc denied, restriction in effect");
200 paniclog("placebc", panicbuf);
201 #endif
202 return;
204 if (uchain && uchain->where != OBJ_FREE) {
205 impossible("bc already placed?");
206 return;
208 placebc_core();
211 void
212 unplacebc(void)
214 if (bcrestriction) {
215 impossible("unplacebc denied, restriction in place");
216 return;
218 unplacebc_core();
222 unplacebc_and_covet_placebc(void)
224 int restriction = 0;
226 if (bcrestriction) {
227 impossible("unplacebc_and_covet_placebc denied, already restricted");
228 } else {
229 restriction = bcrestriction = rnd(400);
230 unplacebc_core();
232 return restriction;
235 void
236 lift_covet_and_placebc(int pin)
238 if (!check_restriction(pin)) {
239 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
240 char panicbuf[BUFSZ];
242 Sprintf(panicbuf, "lift_covet_and_placebc denied, %s",
243 (pin != bcrestriction) ? "pin mismatch"
244 : "restriction in effect");
245 paniclog("placebc", panicbuf);
246 #endif
247 return;
249 if (uchain && uchain->where != OBJ_FREE) {
250 impossible("bc already placed?");
251 return;
253 placebc_core();
256 #else /* BREADCRUMBS */
258 void
259 Placebc(const char *funcnm, int linenum)
261 if (!check_restriction(0)) {
262 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
263 char panicbuf[BUFSZ];
265 Sprintf(panicbuf, "Placebc denied to %s:%d, restricted by %s:%d",
266 funcnm, linenum,
267 bcpbreadcrumbs.funcnm, bcpbreadcrumbs.linenum);
268 paniclog("Placebc", panicbuf);
269 #endif
270 return;
272 if ((uchain && uchain->where != OBJ_FREE)
273 && bcpbreadcrumbs.in_effect) {
274 impossible("Placebc collision at %s:%d, already placed by %s:%d",
275 funcnm, linenum,
276 bcpbreadcrumbs.funcnm, bcpbreadcrumbs.linenum);
277 return;
279 bcpbreadcrumbs.in_effect = TRUE;
280 bcubreadcrumbs.in_effect = FALSE;
281 bcpbreadcrumbs.funcnm = funcnm;
282 bcpbreadcrumbs.linenum = linenum;
283 placebc_core();
286 void
287 Unplacebc(const char *funcnm, int linenum)
290 if (bcrestriction) {
291 char panicbuf[BUFSZ];
293 Sprintf(panicbuf, "Unplacebc from %s:%d, when restricted to %s:%d",
294 funcnm, linenum,
295 bcubreadcrumbs.funcnm, bcubreadcrumbs.linenum);
296 paniclog("Unplacebc", panicbuf);
298 bcpbreadcrumbs.in_effect = FALSE;
299 bcubreadcrumbs.in_effect = TRUE;
300 bcubreadcrumbs.funcnm = funcnm;
301 bcubreadcrumbs.linenum = linenum;
302 unplacebc_core();
306 Unplacebc_and_covet_placebc(const char *funcnm, int linenum)
308 int restriction = 0;
310 if (bcrestriction) {
311 impossible(
312 "Unplacebc_and_covet_placebc denied to %s:%d, restricted by %s:%d",
313 funcnm, linenum,
314 bcubreadcrumbs.funcnm, bcubreadcrumbs.linenum);
315 } else {
316 restriction = bcrestriction = rnd(400);
317 bcpbreadcrumbs.in_effect = FALSE;
318 bcubreadcrumbs.in_effect = TRUE;
319 bcubreadcrumbs.funcnm = funcnm;
320 bcubreadcrumbs.linenum = linenum;
321 unplacebc_core();
323 return restriction;
326 void
327 Lift_covet_and_placebc(int pin, char *funcnm, int linenum)
329 if (!check_restriction(pin)) {
330 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
331 char panicbuf[BUFSZ];
333 Sprintf(panicbuf,
334 "Lift_covet_and_placebc denied to %s:%d, restricted by %s:%d",
335 funcnm, linenum,
336 bcpbreadcrumbs.funcnm, bcpbreadcrumbs.linenum);
337 paniclog("Lift_covet_and_placebc", panicbuf);
338 #endif
339 return;
341 if (uchain && uchain->where != OBJ_FREE) {
342 impossible("bc already placed?");
343 return;
345 placebc_core();
347 #endif /* BREADCRUMBS */
350 * Return the stacking of the hero's ball & chain. This assumes that the
351 * hero is being punished.
353 staticfn int
354 bc_order(void)
356 struct obj *obj;
358 if (uchain->ox != uball->ox || uchain->oy != uball->oy || carried(uball)
359 || u.uswallow)
360 return BCPOS_DIFFER;
362 for (obj = svl.level.objects[uball->ox][uball->oy]; obj;
363 obj = obj->nexthere) {
364 if (obj == uchain)
365 return BCPOS_CHAIN;
366 if (obj == uball)
367 return BCPOS_BALL;
369 impossible("bc_order: ball&chain not in same location!");
370 return BCPOS_DIFFER;
374 * set_bc()
376 * The hero is either about to go blind or already blind and just punished.
377 * Set up the ball and chain variables so that the ball and chain are "felt".
379 void
380 set_bc(int already_blind)
382 int ball_on_floor = !carried(uball);
384 u.bc_order = bc_order(); /* get the order */
385 u.bc_felt = ball_on_floor ? BC_BALL | BC_CHAIN : BC_CHAIN; /* felt */
387 if (already_blind || u.uswallow) {
388 u.cglyph = u.bglyph = levl[u.ux][u.uy].glyph;
389 return;
393 * Since we can still see, remove the ball&chain and get the glyph that
394 * would be beneath them. Then put the ball&chain back. This is pretty
395 * disgusting, but it will work.
397 remove_object(uchain);
398 if (ball_on_floor)
399 remove_object(uball);
401 newsym(uchain->ox, uchain->oy);
402 u.cglyph = levl[uchain->ox][uchain->oy].glyph;
404 if (u.bc_order == BCPOS_DIFFER) { /* different locations */
405 place_object(uchain, uchain->ox, uchain->oy);
406 newsym(uchain->ox, uchain->oy);
407 if (ball_on_floor) {
408 newsym(uball->ox, uball->oy); /* see under ball */
409 u.bglyph = levl[uball->ox][uball->oy].glyph;
410 place_object(uball, uball->ox, uball->oy);
411 newsym(uball->ox, uball->oy); /* restore ball */
413 } else {
414 u.bglyph = u.cglyph;
415 if (u.bc_order == BCPOS_CHAIN) {
416 place_object(uball, uball->ox, uball->oy);
417 place_object(uchain, uchain->ox, uchain->oy);
418 } else {
419 place_object(uchain, uchain->ox, uchain->oy);
420 place_object(uball, uball->ox, uball->oy);
422 newsym(uball->ox, uball->oy);
427 * move_bc()
429 * Move the ball and chain. This is called twice for every move. The first
430 * time to pick up the ball and chain before the move, the second time to
431 * place the ball and chain after the move. If the ball is carried, this
432 * function should never have BC_BALL as part of its control.
434 * Should not be called while swallowed.
436 void
437 move_bc(int before, int control, coordxy ballx, coordxy bally,
438 coordxy chainx, coordxy chainy)
440 if (Blind) {
442 * The hero is blind. Time to work hard. The ball and chain that
443 * are attached to the hero are very special. The hero knows that
444 * they are attached, so when they move, the hero knows that they
445 * aren't at the last position remembered. This is complicated
446 * by the fact that the hero can "feel" the surrounding locations
447 * at any time, hence, making one or both of them show up again.
448 * So, we have to keep track of which is felt at any one time and
449 * act accordingly.
451 if (!before) {
452 if ((control & BC_CHAIN) && (control & BC_BALL)) {
454 * Both ball and chain moved. If felt, drop glyph.
456 if (u.bc_felt & BC_BALL)
457 levl[uball->ox][uball->oy].glyph = u.bglyph;
458 if (u.bc_felt & BC_CHAIN)
459 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
460 u.bc_felt = 0;
462 /* Pick up glyph at new location. */
463 u.bglyph = levl[ballx][bally].glyph;
464 u.cglyph = levl[chainx][chainy].glyph;
466 movobj(uball, ballx, bally);
467 movobj(uchain, chainx, chainy);
468 } else if (control & BC_BALL) {
469 if (u.bc_felt & BC_BALL) {
470 if (u.bc_order == BCPOS_DIFFER) { /* ball by itself */
471 levl[uball->ox][uball->oy].glyph = u.bglyph;
472 } else if (u.bc_order == BCPOS_BALL) {
473 if (u.bc_felt & BC_CHAIN) { /* know chain is there */
474 map_object(uchain, 0);
475 } else {
476 levl[uball->ox][uball->oy].glyph = u.bglyph;
479 u.bc_felt &= ~BC_BALL; /* no longer feel the ball */
482 /* Pick up glyph at new position. */
483 u.bglyph = (ballx != chainx || bally != chainy)
484 ? levl[ballx][bally].glyph
485 : u.cglyph;
487 movobj(uball, ballx, bally);
488 } else if (control & BC_CHAIN) {
489 if (u.bc_felt & BC_CHAIN) {
490 if (u.bc_order == BCPOS_DIFFER) {
491 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
492 } else if (u.bc_order == BCPOS_CHAIN) {
493 if (u.bc_felt & BC_BALL) {
494 map_object(uball, 0);
495 } else {
496 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
499 u.bc_felt &= ~BC_CHAIN;
501 /* Pick up glyph at new position. */
502 u.cglyph = (ballx != chainx || bally != chainy)
503 ? levl[chainx][chainy].glyph
504 : u.bglyph;
506 movobj(uchain, chainx, chainy);
509 u.bc_order = bc_order(); /* reset the order */
512 } else {
514 * The hero is not blind. To make this work correctly, we need to
515 * pick up the ball and chain before the hero moves, then put them
516 * in their new positions after the hero moves.
518 if (before) {
519 if (!control) {
521 * Neither ball nor chain is moving, so remember which was
522 * on top until !before. Use the variable u.bc_order
523 * since it is only valid when blind.
525 u.bc_order = bc_order();
528 remove_object(uchain);
529 maybe_unhide_at(uchain->ox, uchain->oy);
530 newsym(uchain->ox, uchain->oy);
531 if (!carried(uball)) {
532 remove_object(uball);
533 maybe_unhide_at(uball->ox, uball->oy);
534 newsym(uball->ox, uball->oy);
536 } else {
537 int on_floor = !carried(uball);
539 if ((control & BC_CHAIN)
540 || (!control && u.bc_order == BCPOS_CHAIN)) {
541 /* If the chain moved or nothing moved & chain on top. */
542 if (on_floor)
543 place_object(uball, ballx, bally);
544 place_object(uchain, chainx, chainy); /* chain on top */
545 } else {
546 place_object(uchain, chainx, chainy);
547 if (on_floor)
548 place_object(uball, ballx, bally);
549 /* ball on top */
551 newsym(chainx, chainy);
552 if (on_floor)
553 newsym(ballx, bally);
558 /* return TRUE if the caller needs to place the ball and chain down again */
559 boolean
560 drag_ball(coordxy x, coordxy y, int *bc_control,
561 coordxy *ballx, coordxy *bally, coordxy *chainx, coordxy *chainy,
562 boolean *cause_delay, boolean allow_drag)
564 struct trap *t = (struct trap *) 0;
565 boolean already_in_rock;
568 * Should not be called while swallowed. Should be called before
569 * movement, because we might want to move the ball or chain to the
570 * hero's old position.
572 * It is called if we are moving. It is also called if we are
573 * teleporting *if* the ball doesn't move and we thus must drag the
574 * chain. It is not called for ordinary teleportation.
576 * 'allow_drag' is only used in the ugly special case where teleporting
577 * must drag the chain, while an identical-looking movement must drag
578 * both the ball and chain.
581 *ballx = uball->ox;
582 *bally = uball->oy;
583 *chainx = uchain->ox;
584 *chainy = uchain->oy;
585 *bc_control = 0;
586 *cause_delay = FALSE;
588 if (dist2(x, y, uchain->ox, uchain->oy) <= 2) { /* nothing moved */
589 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
590 return TRUE;
593 /* only need to move the chain? */
594 if (carried(uball) || distmin(x, y, uball->ox, uball->oy) <= 2) {
595 coordxy oldchainx = uchain->ox, oldchainy = uchain->oy;
597 *bc_control = BC_CHAIN;
598 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
599 if (carried(uball)) {
600 /* move chain only if necessary */
601 if (distmin(x, y, uchain->ox, uchain->oy) > 1) {
602 *chainx = u.ux;
603 *chainy = u.uy;
605 return TRUE;
608 #define CHAIN_IN_MIDDLE(chx, chy) \
609 (distmin(x, y, chx, chy) <= 1 \
610 && distmin(chx, chy, uball->ox, uball->oy) <= 1)
611 #define IS_CHAIN_ROCK(x, y) \
612 (IS_OBSTRUCTED(levl[x][y].typ) \
613 || (IS_DOOR(levl[x][y].typ) \
614 && (levl[x][y].doormask & (D_CLOSED | D_LOCKED))))
616 * Don't ever move the chain into solid rock. If we have to, then
617 * instead undo the move_bc() and jump to the drag ball code. Note
618 * that this also means the "cannot carry and drag" message will not
619 * appear, since unless we moved at least two squares there is no
620 * possibility of the chain position being in solid rock.
622 #define SKIP_TO_DRAG \
623 do { \
624 *chainx = oldchainx; \
625 *chainy = oldchainy; \
626 move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy); \
627 goto drag; \
628 } while (0)
630 if (IS_CHAIN_ROCK(u.ux, u.uy) || IS_CHAIN_ROCK(*chainx, *chainy)
631 || IS_CHAIN_ROCK(uball->ox, uball->oy))
632 already_in_rock = TRUE;
633 else
634 already_in_rock = FALSE;
636 switch (dist2(x, y, uball->ox, uball->oy)) {
637 /* two spaces diagonal from ball, move chain in-between */
638 case 8:
639 *chainx = (uball->ox + x) / 2;
640 *chainy = (uball->oy + y) / 2;
641 if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
642 SKIP_TO_DRAG;
643 break;
645 /* player is distance 2/1 from ball; move chain to one of the
646 * two spaces between
648 * __
651 case 5: {
652 coordxy tempx, tempy, tempx2, tempy2;
654 /* find position closest to current position of chain;
655 no effect if current position is already OK */
656 if (abs(x - uball->ox) == 1) {
657 tempx = x;
658 tempx2 = uball->ox;
659 tempy = tempy2 = (uball->oy + y) / 2;
660 } else {
661 tempx = tempx2 = (uball->ox + x) / 2;
662 tempy = y;
663 tempy2 = uball->oy;
665 if (IS_CHAIN_ROCK(tempx, tempy) && !IS_CHAIN_ROCK(tempx2, tempy2)
666 && !already_in_rock) {
667 if (allow_drag) {
668 /* Avoid pathological case *if* not teleporting:
669 * 0 0_
670 * _X move northeast -----> X@
673 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5
674 && dist2(x, y, tempx, tempy) == 1)
675 SKIP_TO_DRAG;
676 /* Avoid pathological case *if* not teleporting:
677 * 0 0
678 * _X move east -----> X_
679 * @ @
681 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4
682 && dist2(x, y, tempx, tempy) == 2)
683 SKIP_TO_DRAG;
685 *chainx = tempx2;
686 *chainy = tempy2;
687 } else if (!IS_CHAIN_ROCK(tempx, tempy)
688 && IS_CHAIN_ROCK(tempx2, tempy2) && !already_in_rock) {
689 if (allow_drag) {
690 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5
691 && dist2(x, y, tempx2, tempy2) == 1)
692 SKIP_TO_DRAG;
693 if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4
694 && dist2(x, y, tempx2, tempy2) == 2)
695 SKIP_TO_DRAG;
697 *chainx = tempx;
698 *chainy = tempy;
699 } else if (IS_CHAIN_ROCK(tempx, tempy)
700 && IS_CHAIN_ROCK(tempx2, tempy2) && !already_in_rock) {
701 SKIP_TO_DRAG;
702 } else if (dist2(tempx, tempy, uchain->ox, uchain->oy)
703 < dist2(tempx2, tempy2, uchain->ox, uchain->oy)
704 || ((dist2(tempx, tempy, uchain->ox, uchain->oy)
705 == dist2(tempx2, tempy2, uchain->ox, uchain->oy))
706 && rn2(2))) {
707 *chainx = tempx;
708 *chainy = tempy;
709 } else {
710 *chainx = tempx2;
711 *chainy = tempy2;
713 break;
716 /* ball is two spaces horizontal or vertical from player; move*/
717 /* chain in-between *unless* current chain position is OK */
718 case 4:
719 if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
720 break;
721 *chainx = (x + uball->ox) / 2;
722 *chainy = (y + uball->oy) / 2;
723 if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
724 SKIP_TO_DRAG;
725 break;
727 /* ball is one space diagonal from player. Check for the
728 * following special case:
730 * _ moving southwest becomes @_
731 * 0 0
732 * (This will also catch teleporting that happens to resemble
733 * this case, but oh well.) Otherwise fall through.
735 case 2:
736 if (dist2(x, y, uball->ox, uball->oy) == 2
737 && dist2(x, y, uchain->ox, uchain->oy) == 4) {
738 if (uchain->oy == y)
739 *chainx = uball->ox;
740 else
741 *chainy = uball->oy;
742 if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
743 SKIP_TO_DRAG;
744 break;
746 FALLTHROUGH;
747 /* FALLTHRU */
748 case 1:
749 case 0:
750 /* do nothing if possible */
751 if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
752 break;
753 /* otherwise try to drag chain to player's old position */
754 if (CHAIN_IN_MIDDLE(u.ux, u.uy)) {
755 *chainx = u.ux;
756 *chainy = u.uy;
757 break;
759 /* otherwise use player's new position (they must have
760 teleported, for this to happen) */
761 *chainx = x;
762 *chainy = y;
763 break;
765 default:
766 impossible("bad chain movement");
767 break;
769 #undef SKIP_TO_DRAG
770 #undef CHAIN_IN_MIDDLE
771 return TRUE;
774 drag:
776 if (near_capacity() > SLT_ENCUMBER && dist2(x, y, u.ux, u.uy) <= 2) {
777 You("cannot %sdrag the heavy iron ball.",
778 gi.invent ? "carry all that and also " : "");
779 nomul(0);
780 return FALSE;
783 if ((is_pool(uchain->ox, uchain->oy)
784 /* water not mere continuation of previous water */
785 && (levl[uchain->ox][uchain->oy].typ == POOL
786 || !is_pool(uball->ox, uball->oy)
787 || levl[uball->ox][uball->oy].typ == POOL))
788 || ((t = t_at(uchain->ox, uchain->oy))
789 && (is_pit(t->ttyp) || is_hole(t->ttyp)))) {
790 if (Levitation) {
791 You_feel("a tug from the iron ball.");
792 if (t)
793 t->tseen = 1;
794 } else {
795 struct monst *victim;
797 You("are jerked back by the iron ball!");
798 if ((victim = m_at(uchain->ox, uchain->oy)) != 0) {
799 int tmp;
800 int dieroll = rnd(20);
802 tmp = -2 + Luck + find_mac(victim);
803 tmp += omon_adj(victim, uball, TRUE);
805 if (tmp >= dieroll)
806 (void) hmon(victim, uball, HMON_DRAGGED, dieroll);
807 else
808 miss(xname(uball), victim);
810 } /* now check again in case mon died */
811 if (!m_at(uchain->ox, uchain->oy)) {
812 u.ux = uchain->ox;
813 u.uy = uchain->oy;
814 newsym(u.ux0, u.uy0);
816 nomul(0);
818 *bc_control = BC_BALL;
819 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
820 *ballx = uchain->ox;
821 *bally = uchain->oy;
822 move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy);
823 spoteffects(TRUE);
824 return FALSE;
828 *bc_control = BC_BALL | BC_CHAIN;
830 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
831 if (dist2(x, y, u.ux, u.uy) > 2) {
832 /* Awful case: we're still in range of the ball, so we thought we
833 * could only move the chain, but it turned out that the target
834 * square for the chain was rock, so we had to drag it instead.
835 * But we can't drag it either, because we teleported and are more
836 * than one square from our old position. Revert to the teleport
837 * behavior.
839 *ballx = *chainx = x;
840 *bally = *chainy = y;
841 } else {
842 coordxy newchainx = u.ux, newchainy = u.uy;
845 * Generally, chain moves to hero's previous location and ball
846 * moves to chain's previous location, except that we try to
847 * keep the chain directly between the hero and the ball. But,
848 * take the simple approach if the hero's previous location or
849 * the potential between location is inaccessible.
851 if (dist2(x, y, uchain->ox, uchain->oy) == 4
852 && !IS_CHAIN_ROCK(newchainx, newchainy)) {
853 newchainx = (x + uchain->ox) / 2;
854 newchainy = (y + uchain->oy) / 2;
855 if (IS_CHAIN_ROCK(newchainx, newchainy)) {
856 /* don't let chain move to inaccessible location */
857 newchainx = u.ux;
858 newchainy = u.uy;
862 *ballx = uchain->ox;
863 *bally = uchain->oy;
864 *chainx = newchainx;
865 *chainy = newchainy;
867 #undef IS_CHAIN_ROCK
868 *cause_delay = TRUE;
869 return TRUE;
873 * drop_ball()
875 * The punished hero drops or throws her iron ball. If the hero is
876 * blind, we must reset the order and glyph. Check for side effects.
877 * This routine expects the ball to be already placed.
879 * Should not be called while swallowed.
881 void
882 drop_ball(coordxy x, coordxy y)
884 if (Blind) {
885 /* get the order */
886 u.bc_order = bc_order();
887 /* pick up glyph */
888 u.bglyph = (u.bc_order) ? u.cglyph : levl[x][y].glyph;
891 if (x != u.ux || y != u.uy) {
892 static const char pullmsg[] = "The ball pulls you out of the ";
893 struct trap *t;
894 long side;
896 if (u.utrap
897 && u.utraptype != TT_INFLOOR && u.utraptype != TT_BURIEDBALL) {
898 switch (u.utraptype) {
899 case TT_PIT:
900 pline("%s%s!", pullmsg, "pit");
901 break;
902 case TT_WEB:
903 pline("%s%s!", pullmsg, "web");
904 Soundeffect(se_destroy_web, 30);
905 pline_The("web is destroyed!");
906 deltrap(t_at(u.ux, u.uy));
907 break;
908 case TT_LAVA:
909 pline("%s%s!", pullmsg, hliquid("lava"));
910 break;
911 case TT_BEARTRAP:
912 side = rn2(3) ? LEFT_SIDE : RIGHT_SIDE;
913 pline("%s%s!", pullmsg, "bear trap");
914 set_wounded_legs(side, rn1(1000, 500));
915 if (!u.usteed) {
916 Your("%s %s is severely damaged.",
917 (side == LEFT_SIDE) ? "left" : "right",
918 body_part(LEG));
919 losehp(Maybe_Half_Phys(2),
920 "leg damage from being pulled out of a bear trap",
921 KILLED_BY);
923 break;
925 reset_utrap(TRUE);
926 fill_pit(u.ux, u.uy);
929 u.ux0 = u.ux;
930 u.uy0 = u.uy;
931 if (!Levitation && !MON_AT(x, y) && !u.utrap
932 && (is_pool(x, y)
933 || ((t = t_at(x, y))
934 && (is_pit(t->ttyp)
935 || is_hole(t->ttyp))))) {
936 u.ux = x;
937 u.uy = y;
938 } else {
939 u.ux = x - u.dx;
940 u.uy = y - u.dy;
942 gv.vision_full_recalc = 1; /* hero has moved, recalc vision later */
944 if (Blind) {
945 /* drop glyph under the chain */
946 if (u.bc_felt & BC_CHAIN)
947 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
948 u.bc_felt = 0; /* feel nothing */
949 /* pick up new glyph */
950 u.cglyph = (u.bc_order) ? u.bglyph : levl[u.ux][u.uy].glyph;
952 movobj(uchain, u.ux, u.uy); /* has a newsym */
953 if (Blind) {
954 u.bc_order = bc_order();
956 newsym(u.ux0, u.uy0); /* clean up old position */
957 if (u.ux0 != u.ux || u.uy0 != u.uy) {
958 spoteffects(TRUE);
963 /* ball&chain cause hero to randomly lose stuff from inventory */
964 staticfn void
965 litter(void)
967 struct obj *otmp, *nextobj = 0;
968 int capacity = weight_cap();
970 for (otmp = gi.invent; otmp; otmp = nextobj) {
971 nextobj = otmp->nobj;
972 if (otmp != uball && rnd(capacity) <= (int) otmp->owt) {
973 if (canletgo(otmp, "")) {
974 You("drop %s and %s %s down the stairs with you.",
975 yname(otmp), (otmp->quan == 1L) ? "it" : "they",
976 otense(otmp, "fall"));
977 setnotworn(otmp);
978 freeinv(otmp);
979 hitfloor(otmp, FALSE);
985 void
986 drag_down(void)
988 boolean forward;
989 uchar dragchance = 3;
992 * Assume that the ball falls forward if:
994 * a) the character is wielding it, or
995 * b) the character has both hands available to hold it (i.e. is
996 * not wielding any weapon), or
997 * c) (perhaps) it falls forward out of his non-weapon hand
999 forward = carried(uball) && (uwep == uball || !uwep || !rn2(3));
1001 if (carried(uball) && !welded(uball))
1002 You("lose your grip on the iron ball.");
1004 cls(); /* previous level is still displayed although you
1005 went down the stairs. Avoids bug C343-20 */
1007 if (forward) {
1008 if (rn2(6)) {
1009 pline_The("iron ball drags you downstairs!");
1010 losehp(Maybe_Half_Phys(rnd(6)),
1011 "dragged downstairs by an iron ball", NO_KILLER_PREFIX);
1012 litter();
1014 } else {
1015 if (rn2(2)) {
1016 Soundeffect(se_iron_ball_hits_you, 25);
1017 pline_The("iron ball smacks into you!");
1018 losehp(Maybe_Half_Phys(rnd(20)), "iron ball collision",
1019 KILLED_BY_AN);
1020 exercise(A_STR, FALSE);
1021 dragchance -= 2;
1023 if ((int) dragchance >= rnd(6)) {
1024 pline_The("iron ball drags you downstairs!");
1025 losehp(Maybe_Half_Phys(rnd(3)),
1026 "dragged downstairs by an iron ball", NO_KILLER_PREFIX);
1027 exercise(A_STR, FALSE);
1028 litter();
1033 void
1034 bc_sanity_check(void)
1036 int otyp, freeball, freechain;
1037 const char *onam;
1039 if (Punished && (!uball || !uchain)) {
1040 impossible("Punished without %s%s%s?",
1041 !uball ? "iron ball" : "",
1042 (!uball && !uchain) ? " and " : "",
1043 !uchain ? "attached chain" : "");
1044 } else if (!Punished && (uball || uchain)) {
1045 impossible("Attached %s%s%s without being Punished?",
1046 uchain ? "chain" : "",
1047 (uchain && uball) ? " and " : "",
1048 uball ? "iron ball" : "");
1050 /* ball is free when swallowed, when changing levels or during air bubble
1051 management on Plane of Water (both of which start and end in between
1052 sanity checking cycles, so shouldn't be relevant), other times? */
1053 freechain = (!uchain || uchain->where == OBJ_FREE);
1054 freeball = (!uball || uball->where == OBJ_FREE
1055 /* lie to simplify the testing logic */
1056 || (freechain && uball->where == OBJ_INVENT));
1057 if (uball && (uball->otyp != HEAVY_IRON_BALL
1058 || (uball->where != OBJ_FLOOR
1059 && uball->where != OBJ_INVENT
1060 && uball->where != OBJ_FREE)
1061 || (freeball ^ freechain)
1062 || (uball->owornmask & W_BALL) == 0L
1063 || (uball->owornmask & ~(W_BALL | W_WEAPONS)) != 0L)) {
1064 otyp = uball->otyp;
1065 onam = safe_typename(otyp);
1066 impossible("uball: type %d (%s), where %d, wornmask=0x%08lx",
1067 otyp, onam, uball->where, uball->owornmask);
1069 /* similar check to ball except can't be in inventory */
1070 if (uchain && (uchain->otyp != IRON_CHAIN
1071 || (uchain->where != OBJ_FLOOR
1072 && uchain->where != OBJ_FREE)
1073 || (freechain ^ freeball)
1074 /* [could simplify this to owornmask != W_CHAIN] */
1075 || (uchain->owornmask & W_CHAIN) == 0L
1076 || (uchain->owornmask & ~W_CHAIN) != 0L)) {
1077 otyp = uchain->otyp;
1078 onam = safe_typename(otyp);
1079 impossible("uchain: type %d (%s), where %d, wornmask=0x%08lx",
1080 otyp, onam, uchain->where, uchain->owornmask);
1082 if (uball && uchain && !(freeball && freechain)) {
1083 int bx, by, cx, cy, bdx, bdy, cdx, cdy;
1085 /* non-free chain should be under or next to the hero;
1086 non-free ball should be on or next to the chain or else carried */
1087 cx = uchain->ox, cy = uchain->oy;
1088 cdx = cx - u.ux, cdy = cy - u.uy;
1089 cdx = abs(cdx), cdy = abs(cdy);
1090 if (uball->where == OBJ_INVENT) /* carried(uball) */
1091 bx = u.ux, by = u.uy; /* get_obj_location() */
1092 else
1093 bx = uball->ox, by = uball->oy;
1094 bdx = bx - cx, bdy = by - cy;
1095 bdx = abs(bdx), bdy = abs(bdy);
1096 if (cdx > 1 || cdy > 1 || bdx > 1 || bdy > 1)
1097 impossible(
1098 "b&c distance: you@<%d,%d>, chain@<%d,%d>, ball@<%d,%d>",
1099 u.ux, u.uy, cx, cy, bx, by);
1101 /* [check bc_order too?] */
1104 /*ball.c*/