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