1 /* $OpenBSD: shots.c,v 1.14 2017/01/21 08:22:57 krw Exp $ */ 2 /* $NetBSD: shots.c,v 1.3 1997/10/11 08:13:50 lukem Exp $ */ 3 /* 4 * Copyright (c) 1983-2003, Regents of the University of California. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are 9 * met: 10 * 11 * + Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * + Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * + Neither the name of the University of California, San Francisco nor 17 * the names of its contributors may be used to endorse or promote 18 * products derived from this software without specific prior written 19 * permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 22 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <sys/select.h> 35 #include <stdlib.h> 36 #include <syslog.h> 37 38 #include "conf.h" 39 #include "hunt.h" 40 #include "server.h" 41 42 #define PLUS_DELTA(x, max) if (x < max) x++; else x-- 43 #define MINUS_DELTA(x, min) if (x > min) x--; else x++ 44 45 static void chkshot(BULLET *, BULLET *); 46 static void chkslime(BULLET *, BULLET *); 47 static void explshot(BULLET *, int, int); 48 static void find_under(BULLET *, BULLET *); 49 static int iswall(int, int); 50 static void mark_boot(BULLET *); 51 static void mark_player(BULLET *); 52 static int move_drone(BULLET *); 53 static void move_flyer(PLAYER *); 54 static int move_normal_shot(BULLET *); 55 static void move_slime(BULLET *, int, BULLET *); 56 static void save_bullet(BULLET *); 57 static void zapshot(BULLET *, BULLET *); 58 59 /* Return true if there is pending activity */ 60 int 61 can_moveshots(void) 62 { 63 PLAYER *pp; 64 65 /* Bullets are moving? */ 66 if (Bullets) 67 return 1; 68 69 /* Explosions are happening? */ 70 if (can_rollexpl()) 71 return 1; 72 73 /* Things are flying? */ 74 for (pp = Boot; pp < &Boot[NBOOTS]; pp++) 75 if (pp->p_flying >= 0) 76 return 1; 77 for (pp = Player; pp < End_player; pp++) 78 if (pp->p_flying >= 0) 79 return 1; 80 81 /* Everything is quiet: */ 82 return 0; 83 } 84 85 /* 86 * moveshots: 87 * Move the shots already in the air, taking explosions into account 88 */ 89 void 90 moveshots(void) 91 { 92 BULLET *bp, *next; 93 PLAYER *pp; 94 int x, y; 95 BULLET *blist; 96 97 rollexpl(); 98 if (Bullets == NULL) 99 goto no_bullets; 100 101 /* 102 * First we move through the bullet list conf_bulspd times, looking 103 * for things we may have run into. If we do run into 104 * something, we set up the explosion and disappear, checking 105 * for damage to any player who got in the way. 106 */ 107 108 /* Move the list to a working list */ 109 blist = Bullets; 110 Bullets = NULL; 111 112 /* Work with bullets on the working list (blist) */ 113 for (bp = blist; bp != NULL; bp = next) { 114 next = bp->b_next; 115 116 x = bp->b_x; 117 y = bp->b_y; 118 119 /* Un-draw the bullet on all screens: */ 120 Maze[y][x] = bp->b_over; 121 check(ALL_PLAYERS, y, x); 122 123 /* Decide how to move the bullet: */ 124 switch (bp->b_type) { 125 126 /* Normal, atomic bullets: */ 127 case SHOT: 128 case GRENADE: 129 case SATCHEL: 130 case BOMB: 131 if (move_normal_shot(bp)) { 132 /* Still there: put back on the active list */ 133 bp->b_next = Bullets; 134 Bullets = bp; 135 } 136 break; 137 138 /* Slime bullets that explode into slime on impact: */ 139 case SLIME: 140 if (bp->b_expl || move_normal_shot(bp)) { 141 /* Still there: put back on the active list */ 142 bp->b_next = Bullets; 143 Bullets = bp; 144 } 145 break; 146 147 /* Drones that wander about: */ 148 case DSHOT: 149 if (move_drone(bp)) { 150 /* Still there: put back on the active list */ 151 bp->b_next = Bullets; 152 Bullets = bp; 153 } 154 break; 155 156 /* Other/unknown: */ 157 default: 158 /* Place it back on the active list: */ 159 bp->b_next = Bullets; 160 Bullets = bp; 161 break; 162 } 163 } 164 165 /* Again, hang the Bullets list off `blist' and work with that: */ 166 blist = Bullets; 167 Bullets = NULL; 168 for (bp = blist; bp != NULL; bp = next) { 169 next = bp->b_next; 170 /* Is the bullet exploding? */ 171 if (!bp->b_expl) { 172 /* 173 * Its still flying through the air. 174 * Put it back on the bullet list. 175 */ 176 save_bullet(bp); 177 178 /* All the monitors can see the bullet: */ 179 for (pp = Monitor; pp < End_monitor; pp++) 180 check(pp, bp->b_y, bp->b_x); 181 182 /* All the scanning players can see the drone: */ 183 if (bp->b_type == DSHOT) 184 for (pp = Player; pp < End_player; pp++) 185 if (pp->p_scan >= 0) 186 check(pp, bp->b_y, bp->b_x); 187 } else { 188 /* It is exploding. Check what we hit: */ 189 chkshot(bp, next); 190 /* Release storage for the destroyed bullet: */ 191 free(bp); 192 } 193 } 194 195 /* Re-draw all the players: (in case a bullet wiped them out) */ 196 for (pp = Player; pp < End_player; pp++) 197 Maze[pp->p_y][pp->p_x] = pp->p_face; 198 199 no_bullets: 200 201 /* Move flying boots through the air: */ 202 for (pp = Boot; pp < &Boot[NBOOTS]; pp++) 203 if (pp->p_flying >= 0) 204 move_flyer(pp); 205 206 /* Move flying players through the air: */ 207 for (pp = Player; pp < End_player; pp++) { 208 if (pp->p_flying >= 0) 209 move_flyer(pp); 210 /* Flush out the explosions: */ 211 sendcom(pp, REFRESH); 212 look(pp); 213 } 214 215 /* Flush out and synchronise all the displays: */ 216 sendcom(ALL_PLAYERS, REFRESH); 217 } 218 219 /* 220 * move_normal_shot: 221 * Move a normal shot along its trajectory. 222 * Returns false if the bullet no longer needs tracking. 223 */ 224 static int 225 move_normal_shot(BULLET *bp) 226 { 227 int i, x, y; 228 PLAYER *pp; 229 230 /* 231 * Walk an unexploded bullet along conf_bulspd times, moving it 232 * one unit along each step. We flag it as exploding if it 233 * meets something. 234 */ 235 236 for (i = 0; i < conf_bulspd; i++) { 237 238 /* Stop if the bullet has already exploded: */ 239 if (bp->b_expl) 240 break; 241 242 /* Adjust the bullet's co-ordinates: */ 243 x = bp->b_x; 244 y = bp->b_y; 245 switch (bp->b_face) { 246 case LEFTS: 247 x--; 248 break; 249 case RIGHT: 250 x++; 251 break; 252 case ABOVE: 253 y--; 254 break; 255 case BELOW: 256 y++; 257 break; 258 } 259 260 261 /* Look at what the bullet is colliding with : */ 262 switch (Maze[y][x]) { 263 /* Gun shots have a chance of collision: */ 264 case SHOT: 265 if (rand_num(100) < conf_pshot_coll) { 266 zapshot(Bullets, bp); 267 zapshot(bp->b_next, bp); 268 } 269 break; 270 /* Grenades only have a chance of collision: */ 271 case GRENADE: 272 if (rand_num(100) < conf_pgren_coll) { 273 zapshot(Bullets, bp); 274 zapshot(bp->b_next, bp); 275 } 276 break; 277 /* Reflecting walls richochet the bullet: */ 278 case WALL4: 279 switch (bp->b_face) { 280 case LEFTS: 281 bp->b_face = BELOW; 282 break; 283 case RIGHT: 284 bp->b_face = ABOVE; 285 break; 286 case ABOVE: 287 bp->b_face = RIGHT; 288 break; 289 case BELOW: 290 bp->b_face = LEFTS; 291 break; 292 } 293 Maze[y][x] = WALL5; 294 for (pp = Monitor; pp < End_monitor; pp++) 295 check(pp, y, x); 296 break; 297 case WALL5: 298 switch (bp->b_face) { 299 case LEFTS: 300 bp->b_face = ABOVE; 301 break; 302 case RIGHT: 303 bp->b_face = BELOW; 304 break; 305 case ABOVE: 306 bp->b_face = LEFTS; 307 break; 308 case BELOW: 309 bp->b_face = RIGHT; 310 break; 311 } 312 Maze[y][x] = WALL4; 313 for (pp = Monitor; pp < End_monitor; pp++) 314 check(pp, y, x); 315 break; 316 /* Dispersion doors randomly disperse bullets: */ 317 case DOOR: 318 switch (rand_num(4)) { 319 case 0: 320 bp->b_face = ABOVE; 321 break; 322 case 1: 323 bp->b_face = BELOW; 324 break; 325 case 2: 326 bp->b_face = LEFTS; 327 break; 328 case 3: 329 bp->b_face = RIGHT; 330 break; 331 } 332 break; 333 /* Bullets zing past fliers: */ 334 case FLYER: 335 pp = play_at(y, x); 336 message(pp, "Zing!"); 337 break; 338 /* Bullets encountering a player: */ 339 case LEFTS: 340 case RIGHT: 341 case BELOW: 342 case ABOVE: 343 /* 344 * Give the person a chance to catch a 345 * grenade if s/he is facing it: 346 */ 347 pp = play_at(y, x); 348 pp->p_ident->i_shot += bp->b_charge; 349 if (opposite(bp->b_face, Maze[y][x])) { 350 /* Give them a 10% chance: */ 351 if (rand_num(100) < conf_pgren_catch) { 352 /* They caught it! */ 353 if (bp->b_owner != NULL) 354 message(bp->b_owner, 355 "Your charge was absorbed!"); 356 357 /* 358 * The target player stole from the bullet's 359 * owner. Charge stolen statistics: 360 */ 361 if (bp->b_score != NULL) 362 bp->b_score->i_robbed += bp->b_charge; 363 364 /* They acquire more ammo: */ 365 pp->p_ammo += bp->b_charge; 366 367 /* Check if it would have destroyed them: */ 368 if (pp->p_damage + bp->b_size * conf_mindam 369 > pp->p_damcap) 370 /* Lucky escape statistics: */ 371 pp->p_ident->i_saved++; 372 373 /* Tell them: */ 374 message(pp, "Absorbed charge (good shield!)"); 375 376 /* Absorbtion statistics: */ 377 pp->p_ident->i_absorbed += bp->b_charge; 378 379 /* Deallocate storage: */ 380 free(bp); 381 382 /* Update ammo display: */ 383 ammo_update(pp); 384 385 /* No need for caller to keep tracking it: */ 386 return FALSE; 387 } 388 389 /* Bullets faced head-on (statistics): */ 390 pp->p_ident->i_faced += bp->b_charge; 391 } 392 393 /* 394 * Small chance that the bullet just misses the 395 * person. If so, the bullet just goes on its 396 * merry way without exploding. (5% chance) 397 */ 398 if (rand_num(100) < conf_pmiss) { 399 /* Ducked statistics: */ 400 pp->p_ident->i_ducked += bp->b_charge; 401 402 /* Check if it would have killed them: */ 403 if (pp->p_damage + bp->b_size * conf_mindam 404 > pp->p_damcap) 405 /* Lucky escape statistics: */ 406 pp->p_ident->i_saved++; 407 408 /* Shooter missed statistics: */ 409 if (bp->b_score != NULL) 410 bp->b_score->i_missed += bp->b_charge; 411 412 /* Tell target that they were missed: */ 413 message(pp, "Zing!"); 414 415 /* Tell the bullet owner they missed: */ 416 if (bp->b_owner != NULL) 417 message(bp->b_owner, 418 ((bp->b_score->i_missed & 0x7) == 0x7) ? 419 "My! What a bad shot you are!" : 420 "Missed him"); 421 422 /* Don't fall through */ 423 break; 424 } else { 425 /* The player is to be blown up: */ 426 bp->b_expl = TRUE; 427 } 428 break; 429 /* Bullet hits a wall, and always explodes: */ 430 case WALL1: 431 case WALL2: 432 case WALL3: 433 bp->b_expl = TRUE; 434 break; 435 } 436 437 /* Update the bullet's new position: */ 438 bp->b_x = x; 439 bp->b_y = y; 440 } 441 442 /* Caller should keep tracking the bullet: */ 443 return TRUE; 444 } 445 446 /* 447 * move_drone: 448 * Move the drone to the next square 449 * Returns FALSE if the drone need no longer be tracked. 450 */ 451 static int 452 move_drone(BULLET *bp) 453 { 454 int mask, count; 455 int n, dir = -1; 456 PLAYER *pp; 457 458 /* See if we can give someone a blast: */ 459 if (is_player(Maze[bp->b_y][bp->b_x - 1])) { 460 dir = WEST; 461 goto drone_move; 462 } 463 if (is_player(Maze[bp->b_y - 1][bp->b_x])) { 464 dir = NORTH; 465 goto drone_move; 466 } 467 if (is_player(Maze[bp->b_y + 1][bp->b_x])) { 468 dir = SOUTH; 469 goto drone_move; 470 } 471 if (is_player(Maze[bp->b_y][bp->b_x + 1])) { 472 dir = EAST; 473 goto drone_move; 474 } 475 476 /* Find out what directions are clear and move that way: */ 477 mask = count = 0; 478 if (!iswall(bp->b_y, bp->b_x - 1)) 479 mask |= WEST, count++; 480 if (!iswall(bp->b_y - 1, bp->b_x)) 481 mask |= NORTH, count++; 482 if (!iswall(bp->b_y + 1, bp->b_x)) 483 mask |= SOUTH, count++; 484 if (!iswall(bp->b_y, bp->b_x + 1)) 485 mask |= EAST, count++; 486 487 /* All blocked up, just wait: */ 488 if (count == 0) 489 return TRUE; 490 491 /* Only one way to go: */ 492 if (count == 1) { 493 dir = mask; 494 goto drone_move; 495 } 496 497 /* Avoid backtracking, and remove the direction we came from: */ 498 switch (bp->b_face) { 499 case LEFTS: 500 if (mask & EAST) 501 mask &= ~EAST, count--; 502 break; 503 case RIGHT: 504 if (mask & WEST) 505 mask &= ~WEST, count--; 506 break; 507 case ABOVE: 508 if (mask & SOUTH) 509 mask &= ~SOUTH, count--; 510 break; 511 case BELOW: 512 if (mask & NORTH) 513 mask &= ~NORTH, count--; 514 break; 515 } 516 517 /* Pick one of the remaining directions: */ 518 n = rand_num(count); 519 if (n >= 0 && mask & NORTH) 520 dir = NORTH, n--; 521 if (n >= 0 && mask & SOUTH) 522 dir = SOUTH, n--; 523 if (n >= 0 && mask & EAST) 524 dir = EAST, n--; 525 if (n >= 0 && mask & WEST) 526 dir = WEST, n--; 527 528 drone_move: 529 /* Move the drone: */ 530 switch (dir) { 531 case -1: 532 /* no move */ 533 case WEST: 534 bp->b_x--; 535 bp->b_face = LEFTS; 536 break; 537 case EAST: 538 bp->b_x++; 539 bp->b_face = RIGHT; 540 break; 541 case NORTH: 542 bp->b_y--; 543 bp->b_face = ABOVE; 544 break; 545 case SOUTH: 546 bp->b_y++; 547 bp->b_face = BELOW; 548 break; 549 } 550 551 /* Look at what the drone moved onto: */ 552 switch (Maze[bp->b_y][bp->b_x]) { 553 case LEFTS: 554 case RIGHT: 555 case BELOW: 556 case ABOVE: 557 /* 558 * Players have a 1% chance of absorbing a drone, 559 * if they are facing it. 560 */ 561 if (rand_num(100) < conf_pdroneabsorb && opposite(bp->b_face, 562 Maze[bp->b_y][bp->b_x])) { 563 564 /* Feel the power: */ 565 pp = play_at(bp->b_y, bp->b_x); 566 pp->p_ammo += bp->b_charge; 567 message(pp, "**** Absorbed drone ****"); 568 569 /* Release drone storage: */ 570 free(bp); 571 572 /* Update ammo: */ 573 ammo_update(pp); 574 575 /* No need for caller to keep tracking drone: */ 576 return FALSE; 577 } 578 /* Detonate the drone: */ 579 bp->b_expl = TRUE; 580 break; 581 } 582 583 /* Keep tracking the drone. */ 584 return TRUE; 585 } 586 587 /* 588 * save_bullet: 589 * Put a bullet back onto the bullet list 590 */ 591 static void 592 save_bullet(BULLET *bp) 593 { 594 595 /* Save what the bullet will be flying over: */ 596 bp->b_over = Maze[bp->b_y][bp->b_x]; 597 598 switch (bp->b_over) { 599 /* Bullets that can pass through each other: */ 600 case SHOT: 601 case GRENADE: 602 case SATCHEL: 603 case BOMB: 604 case SLIME: 605 case LAVA: 606 case DSHOT: 607 find_under(Bullets, bp); 608 break; 609 } 610 611 switch (bp->b_over) { 612 /* A bullet hits a player: */ 613 case LEFTS: 614 case RIGHT: 615 case ABOVE: 616 case BELOW: 617 case FLYER: 618 mark_player(bp); 619 break; 620 621 /* A bullet passes a boot: */ 622 case BOOT: 623 case BOOT_PAIR: 624 mark_boot(bp); 625 /* FALLTHROUGH */ 626 627 /* The bullet flies over everything else: */ 628 default: 629 Maze[bp->b_y][bp->b_x] = bp->b_type; 630 break; 631 } 632 633 /* Insert the bullet into the Bullets list: */ 634 bp->b_next = Bullets; 635 Bullets = bp; 636 } 637 638 /* 639 * move_flyer: 640 * Update the position of a player in flight 641 */ 642 static void 643 move_flyer(PLAYER *pp) 644 { 645 int x, y; 646 647 if (pp->p_undershot) { 648 fixshots(pp->p_y, pp->p_x, pp->p_over); 649 pp->p_undershot = FALSE; 650 } 651 652 /* Restore what the flier was flying over */ 653 Maze[pp->p_y][pp->p_x] = pp->p_over; 654 655 /* Fly: */ 656 x = pp->p_x + pp->p_flyx; 657 y = pp->p_y + pp->p_flyy; 658 659 /* Bouncing off the edges of the maze: */ 660 if (x < 1) { 661 x = 1 - x; 662 pp->p_flyx = -pp->p_flyx; 663 } 664 else if (x > WIDTH - 2) { 665 x = (WIDTH - 2) - (x - (WIDTH - 2)); 666 pp->p_flyx = -pp->p_flyx; 667 } 668 if (y < 1) { 669 y = 1 - y; 670 pp->p_flyy = -pp->p_flyy; 671 } 672 else if (y > HEIGHT - 2) { 673 y = (HEIGHT - 2) - (y - (HEIGHT - 2)); 674 pp->p_flyy = -pp->p_flyy; 675 } 676 677 /* Make sure we don't land on something we can't: */ 678 again: 679 switch (Maze[y][x]) { 680 default: 681 /* 682 * Flier is over something other than space, a wall 683 * or a door. Randomly move (drift) the flier a little bit 684 * and then try again: 685 */ 686 switch (rand_num(4)) { 687 case 0: 688 PLUS_DELTA(x, WIDTH - 2); 689 break; 690 case 1: 691 MINUS_DELTA(x, 1); 692 break; 693 case 2: 694 PLUS_DELTA(y, HEIGHT - 2); 695 break; 696 case 3: 697 MINUS_DELTA(y, 1); 698 break; 699 } 700 goto again; 701 /* Give a little boost when about to land on a wall or door: */ 702 case WALL1: 703 case WALL2: 704 case WALL3: 705 case WALL4: 706 case WALL5: 707 case DOOR: 708 if (pp->p_flying == 0) 709 pp->p_flying++; 710 break; 711 /* Spaces are okay: */ 712 case SPACE: 713 break; 714 } 715 716 /* Update flier's coordinates: */ 717 pp->p_y = y; 718 pp->p_x = x; 719 720 /* Consume 'flying' time: */ 721 if (pp->p_flying-- == 0) { 722 /* Land: */ 723 if (pp->p_face != BOOT && pp->p_face != BOOT_PAIR) { 724 /* Land a player - they stustain a fall: */ 725 checkdam(pp, (PLAYER *) NULL, (IDENT *) NULL, 726 rand_num(pp->p_damage / conf_fall_frac), FALL); 727 pp->p_face = rand_dir(); 728 showstat(pp); 729 } else { 730 /* Land boots: */ 731 if (Maze[y][x] == BOOT) 732 pp->p_face = BOOT_PAIR; 733 Maze[y][x] = SPACE; 734 } 735 } 736 737 /* Save under the flier: */ 738 pp->p_over = Maze[y][x]; 739 /* Draw in the flier: */ 740 Maze[y][x] = pp->p_face; 741 showexpl(y, x, pp->p_face); 742 } 743 744 /* 745 * chkshot 746 * Handle explosions 747 */ 748 static void 749 chkshot(BULLET *bp, BULLET *next) 750 { 751 int y, x; 752 int dy, dx, absdy; 753 int delta, damage; 754 char expl; 755 PLAYER *pp; 756 757 delta = 0; 758 switch (bp->b_type) { 759 case SHOT: 760 case MINE: 761 case GRENADE: 762 case GMINE: 763 case SATCHEL: 764 case BOMB: 765 delta = bp->b_size - 1; 766 break; 767 case SLIME: 768 case LAVA: 769 chkslime(bp, next); 770 return; 771 case DSHOT: 772 bp->b_type = SLIME; 773 chkslime(bp, next); 774 return; 775 } 776 777 /* Draw the explosion square: */ 778 for (y = bp->b_y - delta; y <= bp->b_y + delta; y++) { 779 if (y < 0 || y >= HEIGHT) 780 continue; 781 dy = y - bp->b_y; 782 absdy = (dy < 0) ? -dy : dy; 783 for (x = bp->b_x - delta; x <= bp->b_x + delta; x++) { 784 /* Draw a part of the explosion cloud: */ 785 if (x < 0 || x >= WIDTH) 786 continue; 787 dx = x - bp->b_x; 788 if (dx == 0) 789 expl = (dy == 0) ? '*' : '|'; 790 else if (dy == 0) 791 expl = '-'; 792 else if (dx == dy) 793 expl = '\\'; 794 else if (dx == -dy) 795 expl = '/'; 796 else 797 expl = '*'; 798 showexpl(y, x, expl); 799 800 /* Check what poor bastard was in the explosion: */ 801 switch (Maze[y][x]) { 802 case LEFTS: 803 case RIGHT: 804 case ABOVE: 805 case BELOW: 806 case FLYER: 807 if (dx < 0) 808 dx = -dx; 809 if (absdy > dx) 810 damage = bp->b_size - absdy; 811 else 812 damage = bp->b_size - dx; 813 814 /* Everybody hurts, sometimes. */ 815 pp = play_at(y, x); 816 checkdam(pp, bp->b_owner, bp->b_score, 817 damage * conf_mindam, bp->b_type); 818 break; 819 case GMINE: 820 case MINE: 821 /* Mines detonate in a chain reaction: */ 822 add_shot((Maze[y][x] == GMINE) ? 823 GRENADE : SHOT, 824 y, x, LEFTS, 825 (Maze[y][x] == GMINE) ? 826 GRENREQ : BULREQ, 827 (PLAYER *) NULL, TRUE, SPACE); 828 Maze[y][x] = SPACE; 829 break; 830 } 831 } 832 } 833 } 834 835 /* 836 * chkslime: 837 * handle slime shot exploding 838 */ 839 static void 840 chkslime(BULLET *bp, BULLET *next) 841 { 842 BULLET *nbp; 843 844 switch (Maze[bp->b_y][bp->b_x]) { 845 /* Slime explodes on walls and doors: */ 846 case WALL1: 847 case WALL2: 848 case WALL3: 849 case WALL4: 850 case WALL5: 851 case DOOR: 852 switch (bp->b_face) { 853 case LEFTS: 854 bp->b_x++; 855 break; 856 case RIGHT: 857 bp->b_x--; 858 break; 859 case ABOVE: 860 bp->b_y++; 861 break; 862 case BELOW: 863 bp->b_y--; 864 break; 865 } 866 break; 867 } 868 869 /* Duplicate the unit of slime: */ 870 nbp = malloc(sizeof (BULLET)); 871 if (nbp == NULL) { 872 logit(LOG_ERR, "malloc"); 873 return; 874 } 875 *nbp = *bp; 876 877 /* Move it around: */ 878 move_slime(nbp, nbp->b_type == SLIME ? conf_slimespeed : 879 conf_lavaspeed, next); 880 } 881 882 /* 883 * move_slime: 884 * move the given slime shot speed times and add it back if 885 * it hasn't fizzled yet 886 */ 887 static void 888 move_slime(BULLET *bp, int speed, BULLET *next) 889 { 890 int i, j, dirmask, count; 891 PLAYER *pp; 892 BULLET *nbp; 893 894 if (speed == 0) { 895 if (bp->b_charge <= 0) 896 free(bp); 897 else 898 save_bullet(bp); 899 return; 900 } 901 902 /* Draw it: */ 903 showexpl(bp->b_y, bp->b_x, bp->b_type == LAVA ? LAVA : '*'); 904 905 switch (Maze[bp->b_y][bp->b_x]) { 906 /* Someone got hit by slime or lava: */ 907 case LEFTS: 908 case RIGHT: 909 case ABOVE: 910 case BELOW: 911 case FLYER: 912 pp = play_at(bp->b_y, bp->b_x); 913 message(pp, "You've been slimed."); 914 checkdam(pp, bp->b_owner, bp->b_score, conf_mindam, bp->b_type); 915 break; 916 /* Bullets detonate in slime and lava: */ 917 case SHOT: 918 case GRENADE: 919 case SATCHEL: 920 case BOMB: 921 case DSHOT: 922 explshot(next, bp->b_y, bp->b_x); 923 explshot(Bullets, bp->b_y, bp->b_x); 924 break; 925 } 926 927 928 /* Drain the slime/lava of some energy: */ 929 if (--bp->b_charge <= 0) { 930 /* It fizzled: */ 931 free(bp); 932 return; 933 } 934 935 /* Figure out which way the slime should flow: */ 936 dirmask = 0; 937 count = 0; 938 switch (bp->b_face) { 939 case LEFTS: 940 if (!iswall(bp->b_y, bp->b_x - 1)) 941 dirmask |= WEST, count++; 942 if (!iswall(bp->b_y - 1, bp->b_x)) 943 dirmask |= NORTH, count++; 944 if (!iswall(bp->b_y + 1, bp->b_x)) 945 dirmask |= SOUTH, count++; 946 if (dirmask == 0) 947 if (!iswall(bp->b_y, bp->b_x + 1)) 948 dirmask |= EAST, count++; 949 break; 950 case RIGHT: 951 if (!iswall(bp->b_y, bp->b_x + 1)) 952 dirmask |= EAST, count++; 953 if (!iswall(bp->b_y - 1, bp->b_x)) 954 dirmask |= NORTH, count++; 955 if (!iswall(bp->b_y + 1, bp->b_x)) 956 dirmask |= SOUTH, count++; 957 if (dirmask == 0) 958 if (!iswall(bp->b_y, bp->b_x - 1)) 959 dirmask |= WEST, count++; 960 break; 961 case ABOVE: 962 if (!iswall(bp->b_y - 1, bp->b_x)) 963 dirmask |= NORTH, count++; 964 if (!iswall(bp->b_y, bp->b_x - 1)) 965 dirmask |= WEST, count++; 966 if (!iswall(bp->b_y, bp->b_x + 1)) 967 dirmask |= EAST, count++; 968 if (dirmask == 0) 969 if (!iswall(bp->b_y + 1, bp->b_x)) 970 dirmask |= SOUTH, count++; 971 break; 972 case BELOW: 973 if (!iswall(bp->b_y + 1, bp->b_x)) 974 dirmask |= SOUTH, count++; 975 if (!iswall(bp->b_y, bp->b_x - 1)) 976 dirmask |= WEST, count++; 977 if (!iswall(bp->b_y, bp->b_x + 1)) 978 dirmask |= EAST, count++; 979 if (dirmask == 0) 980 if (!iswall(bp->b_y - 1, bp->b_x)) 981 dirmask |= NORTH, count++; 982 break; 983 } 984 if (count == 0) { 985 /* 986 * No place to go. Just sit here for a while and wait 987 * for adjacent squares to clear out. 988 */ 989 save_bullet(bp); 990 return; 991 } 992 if (bp->b_charge < count) { 993 /* Only bp->b_charge paths may be taken */ 994 while (count > bp->b_charge) { 995 if (dirmask & WEST) 996 dirmask &= ~WEST; 997 else if (dirmask & EAST) 998 dirmask &= ~EAST; 999 else if (dirmask & NORTH) 1000 dirmask &= ~NORTH; 1001 else if (dirmask & SOUTH) 1002 dirmask &= ~SOUTH; 1003 count--; 1004 } 1005 } 1006 1007 /* Spawn little slimes off in every possible direction: */ 1008 i = bp->b_charge / count; 1009 j = bp->b_charge % count; 1010 if (dirmask & WEST) { 1011 count--; 1012 nbp = create_shot(bp->b_type, bp->b_y, bp->b_x - 1, LEFTS, 1013 i, bp->b_size, bp->b_owner, bp->b_score, TRUE, SPACE); 1014 move_slime(nbp, speed - 1, next); 1015 } 1016 if (dirmask & EAST) { 1017 count--; 1018 nbp = create_shot(bp->b_type, bp->b_y, bp->b_x + 1, RIGHT, 1019 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner, 1020 bp->b_score, TRUE, SPACE); 1021 move_slime(nbp, speed - 1, next); 1022 } 1023 if (dirmask & NORTH) { 1024 count--; 1025 nbp = create_shot(bp->b_type, bp->b_y - 1, bp->b_x, ABOVE, 1026 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner, 1027 bp->b_score, TRUE, SPACE); 1028 move_slime(nbp, speed - 1, next); 1029 } 1030 if (dirmask & SOUTH) { 1031 count--; 1032 nbp = create_shot(bp->b_type, bp->b_y + 1, bp->b_x, BELOW, 1033 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner, 1034 bp->b_score, TRUE, SPACE); 1035 move_slime(nbp, speed - 1, next); 1036 } 1037 1038 free(bp); 1039 } 1040 1041 /* 1042 * iswall: 1043 * returns whether the given location is a wall 1044 */ 1045 static int 1046 iswall(int y, int x) 1047 { 1048 if (y < 0 || x < 0 || y >= HEIGHT || x >= WIDTH) 1049 return TRUE; 1050 switch (Maze[y][x]) { 1051 case WALL1: 1052 case WALL2: 1053 case WALL3: 1054 case WALL4: 1055 case WALL5: 1056 case DOOR: 1057 case SLIME: 1058 case LAVA: 1059 return TRUE; 1060 } 1061 return FALSE; 1062 } 1063 1064 /* 1065 * zapshot: 1066 * Take a shot out of the air. 1067 */ 1068 static void 1069 zapshot(BULLET *blist, BULLET *obp) 1070 { 1071 BULLET *bp; 1072 1073 for (bp = blist; bp != NULL; bp = bp->b_next) { 1074 /* Find co-located bullets not facing the same way: */ 1075 if (bp->b_face != obp->b_face 1076 && bp->b_x == obp->b_x && bp->b_y == obp->b_y) 1077 { 1078 /* Bullet collision: */ 1079 explshot(blist, obp->b_y, obp->b_x); 1080 return; 1081 } 1082 } 1083 } 1084 1085 /* 1086 * explshot - 1087 * Make all shots at this location blow up 1088 */ 1089 static void 1090 explshot(BULLET *blist, int y, int x) 1091 { 1092 BULLET *bp; 1093 1094 for (bp = blist; bp != NULL; bp = bp->b_next) 1095 if (bp->b_x == x && bp->b_y == y) { 1096 bp->b_expl = TRUE; 1097 if (bp->b_owner != NULL) 1098 message(bp->b_owner, "Shot intercepted."); 1099 } 1100 } 1101 1102 /* 1103 * play_at: 1104 * Return a pointer to the player at the given location 1105 */ 1106 PLAYER * 1107 play_at(int y, int x) 1108 { 1109 PLAYER *pp; 1110 1111 for (pp = Player; pp < End_player; pp++) 1112 if (pp->p_x == x && pp->p_y == y) 1113 return pp; 1114 1115 /* Internal fault: */ 1116 logx(LOG_ERR, "play_at: not a player"); 1117 abort(); 1118 } 1119 1120 /* 1121 * opposite: 1122 * Return TRUE if the bullet direction faces the opposite direction 1123 * of the player in the maze 1124 */ 1125 int 1126 opposite(int face, char dir) 1127 { 1128 switch (face) { 1129 case LEFTS: 1130 return (dir == RIGHT); 1131 case RIGHT: 1132 return (dir == LEFTS); 1133 case ABOVE: 1134 return (dir == BELOW); 1135 case BELOW: 1136 return (dir == ABOVE); 1137 default: 1138 return FALSE; 1139 } 1140 } 1141 1142 /* 1143 * is_bullet: 1144 * Is there a bullet at the given coordinates? If so, return 1145 * a pointer to the bullet, otherwise return NULL 1146 */ 1147 BULLET * 1148 is_bullet(int y, int x) 1149 { 1150 BULLET *bp; 1151 1152 for (bp = Bullets; bp != NULL; bp = bp->b_next) 1153 if (bp->b_y == y && bp->b_x == x) 1154 return bp; 1155 return NULL; 1156 } 1157 1158 /* 1159 * fixshots: 1160 * change the underlying character of the shots at a location 1161 * to the given character. 1162 */ 1163 void 1164 fixshots(int y, int x, char over) 1165 { 1166 BULLET *bp; 1167 1168 for (bp = Bullets; bp != NULL; bp = bp->b_next) 1169 if (bp->b_y == y && bp->b_x == x) 1170 bp->b_over = over; 1171 } 1172 1173 /* 1174 * find_under: 1175 * find the underlying character for a bullet when it lands 1176 * on another bullet. 1177 */ 1178 static void 1179 find_under(BULLET *blist, BULLET *bp) 1180 { 1181 BULLET *nbp; 1182 1183 for (nbp = blist; nbp != NULL; nbp = nbp->b_next) 1184 if (bp->b_y == nbp->b_y && bp->b_x == nbp->b_x) { 1185 bp->b_over = nbp->b_over; 1186 break; 1187 } 1188 } 1189 1190 /* 1191 * mark_player: 1192 * mark a player as under a shot 1193 */ 1194 static void 1195 mark_player(BULLET *bp) 1196 { 1197 PLAYER *pp; 1198 1199 for (pp = Player; pp < End_player; pp++) 1200 if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) { 1201 pp->p_undershot = TRUE; 1202 break; 1203 } 1204 } 1205 1206 /* 1207 * mark_boot: 1208 * mark a boot as under a shot 1209 */ 1210 static void 1211 mark_boot(BULLET *bp) 1212 { 1213 PLAYER *pp; 1214 1215 for (pp = Boot; pp < &Boot[NBOOTS]; pp++) 1216 if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) { 1217 pp->p_undershot = TRUE; 1218 break; 1219 } 1220 } 1221