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