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