1 /* SCCS Id: @(#)ball.c 3.3 97/04/23 */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
4
5 /* Ball & Chain =============================================================*/
6
7 #include "hack.h"
8
9 STATIC_DCL int NDECL(bc_order);
10 STATIC_DCL void NDECL(litter);
11
12 void
ballfall()13 ballfall()
14 {
15 boolean gets_hit;
16
17 gets_hit = (((uball->ox != u.ux) || (uball->oy != u.uy)) &&
18 ((uwep == uball)? FALSE : (boolean)rn2(5)));
19 if (carried(uball)) {
20 pline("Startled, you drop the iron ball.");
21 if (uwep == uball)
22 setuwep((struct obj *)0);
23 if (uswapwep == uball)
24 setuswapwep((struct obj *)0);
25 if (uquiver == uball)
26 setuqwep((struct obj *)0);;
27 if (uwep != uball)
28 freeinv(uball);
29 }
30 if(gets_hit){
31 int dmg = rn1(7,25);
32 pline_The("iron ball falls on your %s.",
33 body_part(HEAD));
34 if (uarmh) {
35 if(is_metallic(uarmh)) {
36 pline("Fortunately, you are wearing a hard helmet.");
37 dmg = 3;
38 } else if (flags.verbose)
39 Your("%s does not protect you.", xname(uarmh));
40 }
41 losehp(dmg, "Crunched in the head by an iron ball",
42 NO_KILLER_PREFIX);
43 }
44 }
45
46 /*
47 * To make this work, we have to mess with the hero's mind. The rules for
48 * ball&chain are:
49 *
50 * 1. If the hero can see them, fine.
51 * 2. If the hero can't see either, it isn't seen.
52 * 3. If either is felt it is seen.
53 * 4. If either is felt and moved, it disappears.
54 *
55 * If the hero can see, then when a move is done, the ball and chain are
56 * first picked up, the positions under them are corrected, then they
57 * are moved after the hero moves. Not too bad.
58 *
59 * If the hero is blind, then she can "feel" the ball and/or chain at any
60 * time. However, when the hero moves, the felt ball and/or chain become
61 * unfelt and whatever was felt "under" the ball&chain appears. Pretty
62 * nifty, but it requires that the ball&chain "remember" what was under
63 * them --- i.e. they pick-up glyphs when they are felt and drop them when
64 * moved (and felt). When swallowed, the ball&chain are pulled completely
65 * off of the dungeon, but are still on the object chain. They are placed
66 * under the hero when she is expelled.
67 */
68
69 /*
70 * from you.h
71 * int u.bglyph glyph under the ball
72 * int u.cglyph glyph under the chain
73 * int u.bc_felt mask for ball/chain being felt
74 * #define BC_BALL 0x01 bit mask in u.bc_felt for ball
75 * #define BC_CHAIN 0x02 bit mask in u.bc_felt for chain
76 * int u.bc_order ball & chain order
77 *
78 * u.bc_felt is also manipulated in display.c and read.c, the others only
79 * in this file. None of these variables are valid unless the player is
80 * Blind.
81 */
82
83 /* values for u.bc_order */
84 #define BCPOS_DIFFER 0 /* ball & chain at different positions */
85 #define BCPOS_CHAIN 1 /* chain on top of ball */
86 #define BCPOS_BALL 2 /* ball on top of chain */
87
88
89
90 /*
91 * Place the ball & chain under the hero. Make sure that the ball & chain
92 * variables are set (actually only needed when blind, but what the heck).
93 * It is assumed that when this is called, the ball and chain are NOT
94 * attached to the object list.
95 *
96 * Should not be called while swallowed.
97 */
98 void
placebc()99 placebc()
100 {
101 if (!uchain || !uball) {
102 impossible("Where are your ball and chain?");
103 return;
104 }
105
106 (void) flooreffects(uchain, u.ux, u.uy, ""); /* chain might rust */
107
108 if (carried(uball)) /* the ball is carried */
109 u.bc_order = BCPOS_DIFFER;
110 else {
111 /* ball might rust -- already checked when carried */
112 (void) flooreffects(uball, u.ux, u.uy, "");
113 place_object(uball, u.ux, u.uy);
114 u.bc_order = BCPOS_CHAIN;
115 }
116
117 place_object(uchain, u.ux, u.uy);
118
119 u.bglyph = u.cglyph = levl[u.ux][u.uy].glyph; /* pick up glyph */
120
121 newsym(u.ux,u.uy);
122 }
123
124 void
unplacebc()125 unplacebc()
126 {
127 if (u.uswallow) return; /* ball&chain not placed while swallowed */
128
129 if (!carried(uball)) {
130 obj_extract_self(uball);
131 if (Blind && (u.bc_felt & BC_BALL)) /* drop glyph */
132 levl[uball->ox][uball->oy].glyph = u.bglyph;
133
134 newsym(uball->ox,uball->oy);
135 }
136 obj_extract_self(uchain);
137 if (Blind && (u.bc_felt & BC_CHAIN)) /* drop glyph */
138 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
139
140 newsym(uchain->ox,uchain->oy);
141 u.bc_felt = 0; /* feel nothing */
142 }
143
144
145 /*
146 * Return the stacking of the hero's ball & chain. This assumes that the
147 * hero is being punished.
148 */
149 STATIC_OVL int
bc_order()150 bc_order()
151 {
152 struct obj *obj;
153
154 if (uchain->ox != uball->ox || uchain->oy != uball->oy || carried(uball)
155 || u.uswallow)
156 return BCPOS_DIFFER;
157
158 for (obj = level.objects[uball->ox][uball->oy]; obj; obj = obj->nexthere) {
159 if (obj == uchain) return BCPOS_CHAIN;
160 if (obj == uball) return BCPOS_BALL;
161 }
162 impossible("bc_order: ball&chain not in same location!");
163 return BCPOS_DIFFER;
164 }
165
166 /*
167 * set_bc()
168 *
169 * The hero is either about to go blind or already blind and just punished.
170 * Set up the ball and chain variables so that the ball and chain are "felt".
171 */
172 void
set_bc(already_blind)173 set_bc(already_blind)
174 int already_blind;
175 {
176 int ball_on_floor = !carried(uball);
177
178 u.bc_order = bc_order(); /* get the order */
179 u.bc_felt = ball_on_floor ? BC_BALL|BC_CHAIN : BC_CHAIN; /* felt */
180
181 if (already_blind || u.uswallow) {
182 u.cglyph = u.bglyph = levl[u.ux][u.uy].glyph;
183 return;
184 }
185
186 /*
187 * Since we can still see, remove the ball&chain and get the glyph that
188 * would be beneath them. Then put the ball&chain back. This is pretty
189 * disgusting, but it will work.
190 */
191 remove_object(uchain);
192 if (ball_on_floor) remove_object(uball);
193
194 newsym(uchain->ox, uchain->oy);
195 u.cglyph = levl[uchain->ox][uchain->oy].glyph;
196
197 if (u.bc_order == BCPOS_DIFFER) { /* different locations */
198 place_object(uchain, uchain->ox, uchain->oy);
199 newsym(uchain->ox, uchain->oy);
200 if (ball_on_floor) {
201 newsym(uball->ox, uball->oy); /* see under ball */
202 u.bglyph = levl[uball->ox][uball->oy].glyph;
203 place_object(uball, uball->ox, uball->oy);
204 newsym(uball->ox, uball->oy); /* restore ball */
205 }
206 } else {
207 u.bglyph = u.cglyph;
208 if (u.bc_order == BCPOS_CHAIN) {
209 place_object(uball, uball->ox, uball->oy);
210 place_object(uchain, uchain->ox, uchain->oy);
211 } else {
212 place_object(uchain, uchain->ox, uchain->oy);
213 place_object(uball, uball->ox, uball->oy);
214 }
215 newsym(uball->ox, uball->oy);
216 }
217 }
218
219
220 /*
221 * move_bc()
222 *
223 * Move the ball and chain. This is called twice for every move. The first
224 * time to pick up the ball and chain before the move, the second time to
225 * place the ball and chain after the move. If the ball is carried, this
226 * function should never have BC_BALL as part of its control.
227 *
228 * Should not be called while swallowed.
229 */
230 void
move_bc(before,control,ballx,bally,chainx,chainy)231 move_bc(before, control, ballx, bally, chainx, chainy)
232 int before, control;
233 xchar ballx, bally, chainx, chainy; /* only matter !before */
234 {
235 if (Blind) {
236 /*
237 * The hero is blind. Time to work hard. The ball and chain that
238 * are attached to the hero are very special. The hero knows that
239 * they are attached, so when they move, the hero knows that they
240 * aren't at the last position remembered. This is complicated
241 * by the fact that the hero can "feel" the surrounding locations
242 * at any time, hence, making one or both of them show up again.
243 * So, we have to keep track of which is felt at any one time and
244 * act accordingly.
245 */
246 if (!before) {
247 if ((control & BC_CHAIN) && (control & BC_BALL)) {
248 /*
249 * Both ball and chain moved. If felt, drop glyph.
250 */
251 if (u.bc_felt & BC_BALL)
252 levl[uball->ox][uball->oy].glyph = u.bglyph;
253 if (u.bc_felt & BC_CHAIN)
254 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
255 u.bc_felt = 0;
256
257 /* Pick up glyph at new location. */
258 u.bglyph = levl[ballx][bally].glyph;
259 u.cglyph = levl[chainx][chainy].glyph;
260
261 movobj(uball,ballx,bally);
262 movobj(uchain,chainx,chainy);
263 } else if (control & BC_BALL) {
264 if (u.bc_felt & BC_BALL) {
265 if (u.bc_order == BCPOS_DIFFER) { /* ball by itself */
266 levl[uball->ox][uball->oy].glyph = u.bglyph;
267 } else if (u.bc_order == BCPOS_BALL) {
268 if (u.bc_felt & BC_CHAIN) { /* know chain is there */
269 map_object(uchain, 0);
270 } else {
271 levl[uball->ox][uball->oy].glyph = u.bglyph;
272 }
273 }
274 u.bc_felt &= ~BC_BALL; /* no longer feel the ball */
275 }
276
277 /* Pick up glyph at new position. */
278 u.bglyph = (ballx != chainx || bally != chainy) ?
279 levl[ballx][bally].glyph : u.cglyph;
280
281 movobj(uball,ballx,bally);
282 } else if (control & BC_CHAIN) {
283 if (u.bc_felt & BC_CHAIN) {
284 if (u.bc_order == BCPOS_DIFFER) {
285 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
286 } else if (u.bc_order == BCPOS_CHAIN) {
287 if (u.bc_felt & BC_BALL) {
288 map_object(uball, 0);
289 } else {
290 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
291 }
292 }
293 u.bc_felt &= ~BC_CHAIN;
294 }
295 /* Pick up glyph at new position. */
296 u.cglyph = (ballx != chainx || bally != chainy) ?
297 levl[chainx][chainy].glyph : u.bglyph;
298
299 movobj(uchain,chainx,chainy);
300 }
301
302 u.bc_order = bc_order(); /* reset the order */
303 }
304
305 } else {
306 /*
307 * The hero is not blind. To make this work correctly, we need to
308 * pick up the ball and chain before the hero moves, then put them
309 * in their new positions after the hero moves.
310 */
311 if (before) {
312 if (!control) {
313 /*
314 * Neither ball nor chain is moving, so remember which was
315 * on top until !before. Use the variable u.bc_order
316 * since it is only valid when blind.
317 */
318 u.bc_order = bc_order();
319 }
320
321 remove_object(uchain);
322 newsym(uchain->ox, uchain->oy);
323 if (!carried(uball)) {
324 remove_object(uball);
325 newsym(uball->ox, uball->oy);
326 }
327 } else {
328 int on_floor = !carried(uball);
329
330 if ((control & BC_CHAIN) ||
331 (!control && u.bc_order == BCPOS_CHAIN)) {
332 /* If the chain moved or nothing moved & chain on top. */
333 if (on_floor) place_object(uball, ballx, bally);
334 place_object(uchain, chainx, chainy); /* chain on top */
335 } else {
336 place_object(uchain, chainx, chainy);
337 if (on_floor) place_object(uball, ballx, bally);
338 /* ball on top */
339 }
340 newsym(chainx, chainy);
341 if (on_floor) newsym(ballx, bally);
342 }
343 }
344 }
345
346 /* return TRUE if ball could be dragged
347 *
348 * Should not be called while swallowed.
349 */
350 boolean
drag_ball(x,y,bc_control,ballx,bally,chainx,chainy,cause_delay)351 drag_ball(x, y, bc_control, ballx, bally, chainx, chainy, cause_delay)
352 xchar x, y;
353 int *bc_control;
354 xchar *ballx, *bally, *chainx, *chainy;
355 boolean *cause_delay;
356 {
357 struct trap *t = (struct trap *)0;
358
359 *ballx = uball->ox;
360 *bally = uball->oy;
361 *chainx = uchain->ox;
362 *chainy = uchain->oy;
363 *bc_control = 0;
364 *cause_delay = FALSE;
365
366 if (dist2(x, y, uchain->ox, uchain->oy) <= 2) { /* nothing moved */
367 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
368 return TRUE;
369 }
370
371 if (carried(uball) || dist2(x, y, uball->ox, uball->oy) < 3 ||
372 (uball->ox == uchain->ox && uball->oy == uchain->oy)) {
373 /*
374 * Case where the ball doesn't move but the chain can't just move
375 * to the player's position:
376 * @ _
377 * _ moving southwest becomes @_ and not @
378 * 0 0 0
379 */
380 *bc_control = BC_CHAIN;
381 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
382 if (dist2(x, y, uball->ox, uball->oy) == 2 &&
383 dist2(x, y, uchain->ox, uchain->oy) == 4) {
384 if (uchain->oy == y)
385 *chainx = uball->ox;
386 else
387 *chainy = uball->oy;
388 } else {
389 *chainx = u.ux;
390 *chainy = u.uy;
391 }
392 return TRUE;
393 }
394
395 if (near_capacity() > SLT_ENCUMBER) {
396 You("cannot %sdrag the heavy iron ball.",
397 invent ? "carry all that and also " : "");
398 nomul(0);
399 return FALSE;
400 }
401
402 if ((is_pool(uchain->ox, uchain->oy) &&
403 /* water not mere continuation of previous water */
404 (levl[uchain->ox][uchain->oy].typ == POOL ||
405 !is_pool(uball->ox, uball->oy) ||
406 levl[uball->ox][uball->oy].typ == POOL))
407 || ((t = t_at(uchain->ox, uchain->oy)) &&
408 (t->ttyp == PIT ||
409 t->ttyp == SPIKED_PIT ||
410 t->ttyp == HOLE ||
411 t->ttyp == TRAPDOOR)) ) {
412
413 if (Levitation) {
414 You_feel("a tug from the iron ball.");
415 if (t) t->tseen = 1;
416 } else {
417 struct monst *victim;
418
419 You("are jerked back by the iron ball!");
420 if ((victim = m_at(uchain->ox, uchain->oy)) != 0) {
421 int tmp;
422
423 tmp = -2 + Luck + find_mac(victim);
424 tmp += omon_adj(victim, uball, TRUE);
425 if (tmp >= rnd(20))
426 (void) hmon(victim,uball,1);
427 else
428 miss(xname(uball), victim);
429
430 } /* now check again in case mon died */
431 if (!m_at(uchain->ox, uchain->oy)) {
432 u.ux = uchain->ox;
433 u.uy = uchain->oy;
434 newsym(u.ux0, u.uy0);
435 }
436 nomul(0);
437
438 *bc_control = BC_BALL;
439 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
440 *ballx = uchain->ox;
441 *bally = uchain->oy;
442 move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy);
443 spoteffects(TRUE);
444 return FALSE;
445 }
446 }
447
448 *bc_control = BC_BALL|BC_CHAIN;;
449
450 move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
451 *ballx = uchain->ox;
452 *bally = uchain->oy;
453 *chainx = u.ux;
454 *chainy = u.uy;
455 *cause_delay = TRUE;
456 return TRUE;
457 }
458
459 /*
460 * drop_ball()
461 *
462 * The punished hero drops or throws her iron ball. If the hero is
463 * blind, we must reset the order and glyph. Check for side effects.
464 * This routine expects the ball to be already placed.
465 *
466 * Should not be called while swallowed.
467 */
468 void
drop_ball(x,y)469 drop_ball(x, y)
470 xchar x, y;
471 {
472 if (Blind) {
473 u.bc_order = bc_order(); /* get the order */
474 /* pick up glyph */
475 u.bglyph = (u.bc_order) ? u.cglyph : levl[x][y].glyph;
476 }
477
478 if (x != u.ux || y != u.uy) {
479 struct trap *t;
480 const char *pullmsg = "The ball pulls you out of the %s!";
481
482 if (u.utrap && u.utraptype != TT_INFLOOR) {
483 switch(u.utraptype) {
484 case TT_PIT:
485 pline(pullmsg, "pit");
486 break;
487 case TT_WEB:
488 pline(pullmsg, "web");
489 pline_The("web is destroyed!");
490 deltrap(t_at(u.ux,u.uy));
491 break;
492 case TT_LAVA:
493 pline(pullmsg, "lava");
494 break;
495 case TT_BEARTRAP: {
496 register long side = rn2(3) ? LEFT_SIDE : RIGHT_SIDE;
497 pline(pullmsg, "bear trap");
498 set_wounded_legs(side, rn1(1000, 500));
499 #ifdef STEED
500 if (!u.usteed)
501 #endif
502 {
503 Your("%s %s is severely damaged.",
504 (side == LEFT_SIDE) ? "left" : "right",
505 body_part(LEG));
506 losehp(2, "leg damage from being pulled out of a bear trap",
507 KILLED_BY);
508 }
509 break;
510 }
511 }
512 u.utrap = 0;
513 fill_pit(u.ux, u.uy);
514 }
515
516 u.ux0 = u.ux;
517 u.uy0 = u.uy;
518 if (!Levitation && !MON_AT(x, y) && !u.utrap &&
519 (is_pool(x, y) ||
520 ((t = t_at(x, y)) &&
521 (t->ttyp == PIT || t->ttyp == SPIKED_PIT ||
522 t->ttyp == TRAPDOOR || t->ttyp == HOLE)))) {
523 u.ux = x;
524 u.uy = y;
525 } else {
526 u.ux = x - u.dx;
527 u.uy = y - u.dy;
528 }
529 vision_full_recalc = 1; /* hero has moved, recalculate vision later */
530
531 if (Blind) {
532 /* drop glyph under the chain */
533 if (u.bc_felt & BC_CHAIN)
534 levl[uchain->ox][uchain->oy].glyph = u.cglyph;
535 u.bc_felt = 0; /* feel nothing */
536 /* pick up new glyph */
537 u.cglyph = (u.bc_order) ? u.bglyph : levl[u.ux][u.uy].glyph;
538 }
539 movobj(uchain,u.ux,u.uy); /* has a newsym */
540 if (Blind) {
541 u.bc_order = bc_order();
542 }
543 newsym(u.ux0,u.uy0); /* clean up old position */
544 if (u.ux0 != u.ux || u.uy0 != u.uy) {
545 spoteffects(TRUE);
546 if (In_sokoban(&u.uz))
547 change_luck(-1); /* Sokoban guilt */
548 }
549 }
550 }
551
552
553 STATIC_OVL void
litter()554 litter()
555 {
556 struct obj *otmp = invent, *nextobj;
557 int capacity = weight_cap();
558
559 while (otmp) {
560 nextobj = otmp->nobj;
561 if ((otmp != uball) && (rnd(capacity) <= (int)otmp->owt)) {
562 if (otmp == uwep)
563 setuwep((struct obj *)0);
564 if ((otmp != uwep) && (canletgo(otmp, ""))) {
565 Your("%s you down the stairs.",
566 aobjnam(otmp, "follow"));
567 dropx(otmp);
568 }
569 }
570 otmp = nextobj;
571 }
572 }
573
574 void
drag_down()575 drag_down()
576 {
577 boolean forward;
578 uchar dragchance = 3;
579
580 /*
581 * Assume that the ball falls forward if:
582 *
583 * a) the character is wielding it, or
584 * b) the character has both hands available to hold it (i.e. is
585 * not wielding any weapon), or
586 * c) (perhaps) it falls forward out of his non-weapon hand
587 */
588
589 forward = carried(uball) && (uwep == uball || !uwep || !rn2(3));
590
591 if (carried(uball))
592 You("lose your grip on the iron ball.");
593
594 if (forward) {
595 if(rn2(6)) {
596 pline_The("iron ball drags you downstairs!");
597 losehp(rnd(6), "dragged downstairs by an iron ball",
598 NO_KILLER_PREFIX);
599 litter();
600 }
601 } else {
602 if(rn2(2)) {
603 pline_The("iron ball smacks into you!");
604 losehp(rnd(20), "iron ball collision", KILLED_BY_AN);
605 exercise(A_STR, FALSE);
606 dragchance -= 2;
607 }
608 if( (int) dragchance >= rnd(6)) {
609 pline_The("iron ball drags you downstairs!");
610 losehp(rnd(3), "dragged downstairs by an iron ball",
611 NO_KILLER_PREFIX);
612 exercise(A_STR, FALSE);
613 litter();
614 }
615 }
616 }
617
618 /*ball.c*/
619