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
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
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
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
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
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
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
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
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
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
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*/