2 * Copyright (c) 1983-2003, Regents of the University of California.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * + Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * + Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * + Neither the name of the University of California, San Francisco nor
15 * the names of its contributors may be used to endorse or promote
16 * products derived from this software without specific prior written
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 * $OpenBSD: shots.c,v 1.9 2006/03/27 00:10:15 tedu Exp $
32 * $NetBSD: shots.c,v 1.3 1997/10/11 08:13:50 lukem Exp $
33 * $DragonFly: src/games/hunt/huntd/shots.c,v 1.2 2008/09/04 16:12:51 swildner Exp $
44 #define PLUS_DELTA(x, max) if (x < max) x++; else x--
45 #define MINUS_DELTA(x, min) if (x > min) x--; else x++
47 static void chkshot(BULLET *, BULLET *);
48 static void chkslime(BULLET *, BULLET *);
49 static void explshot(BULLET *, int, int);
50 static void find_under(BULLET *, BULLET *);
51 static int iswall(int, int);
52 static void mark_boot(BULLET *);
53 static void mark_player(BULLET *);
54 static int move_drone(BULLET *);
55 static void move_flyer(PLAYER *);
56 static int move_normal_shot(BULLET *);
57 static void move_slime(BULLET *, int, BULLET *);
58 static void save_bullet(BULLET *);
59 static void zapshot(BULLET *, BULLET *);
61 /* Return true if there is pending activity */
67 /* Bullets are moving? */
71 /* Explosions are happening? */
75 /* Things are flying? */
76 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
77 if (pp->p_flying >= 0)
79 for (pp = Player; pp < End_player; pp++)
80 if (pp->p_flying >= 0)
83 /* Everything is quiet: */
89 * Move the shots already in the air, taking explosions into account
104 * First we move through the bullet list conf_bulspd times, looking
105 * for things we may have run into. If we do run into
106 * something, we set up the explosion and disappear, checking
107 * for damage to any player who got in the way.
110 /* Move the list to a working list */
114 /* Work with bullets on the working list (blist) */
115 for (bp = blist; bp != NULL; bp = next) {
121 /* Un-draw the bullet on all screens: */
122 Maze[y][x] = bp->b_over;
123 check(ALL_PLAYERS, y, x);
125 /* Decide how to move the bullet: */
126 switch (bp->b_type) {
128 /* Normal, atomic bullets: */
133 if (move_normal_shot(bp)) {
134 /* Still there: put back on the active list */
135 bp->b_next = Bullets;
140 /* Slime bullets that explode into slime on impact: */
142 if (bp->b_expl || move_normal_shot(bp)) {
143 /* Still there: put back on the active list */
144 bp->b_next = Bullets;
149 /* Drones that wander about: */
151 if (move_drone(bp)) {
152 /* Still there: put back on the active list */
153 bp->b_next = Bullets;
160 /* Place it back on the active list: */
161 bp->b_next = Bullets;
167 /* Again, hang the Bullets list off `blist' and work with that: */
170 for (bp = blist; bp != NULL; bp = next) {
172 /* Is the bullet exploding? */
175 * Its still flying through the air.
176 * Put it back on the bullet list.
180 /* All the monitors can see the bullet: */
181 for (pp = Monitor; pp < End_monitor; pp++)
182 check(pp, bp->b_y, bp->b_x);
184 /* All the scanning players can see the drone: */
185 if (bp->b_type == DSHOT)
186 for (pp = Player; pp < End_player; pp++)
188 check(pp, bp->b_y, bp->b_x);
190 /* It is exploding. Check what we hit: */
192 /* Release storage for the destroyed bullet: */
197 /* Re-draw all the players: (in case a bullet wiped them out) */
198 for (pp = Player; pp < End_player; pp++)
199 Maze[pp->p_y][pp->p_x] = pp->p_face;
203 /* Move flying boots through the air: */
204 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
205 if (pp->p_flying >= 0)
208 /* Move flying players through the air: */
209 for (pp = Player; pp < End_player; pp++) {
210 if (pp->p_flying >= 0)
212 /* Flush out the explosions: */
213 sendcom(pp, REFRESH);
217 /* Flush out and synchronise all the displays: */
218 sendcom(ALL_PLAYERS, REFRESH);
223 * Move a normal shot along its trajectory.
224 * Returns false if the bullet no longer needs tracking.
227 move_normal_shot(BULLET *bp)
233 * Walk an unexploded bullet along conf_bulspd times, moving it
234 * one unit along each step. We flag it as exploding if it
238 for (i = 0; i < conf_bulspd; i++) {
240 /* Stop if the bullet has already exploded: */
244 /* Adjust the bullet's co-ordinates: */
247 switch (bp->b_face) {
263 /* Look at what the bullet is colliding with : */
264 switch (Maze[y][x]) {
265 /* Gun shots have a chance of collision: */
267 if (rand_num(100) < conf_pshot_coll) {
268 zapshot(Bullets, bp);
269 zapshot(bp->b_next, bp);
272 /* Grenades only have a chance of collision: */
274 if (rand_num(100) < conf_pgren_coll) {
275 zapshot(Bullets, bp);
276 zapshot(bp->b_next, bp);
279 /* Reflecting walls richochet the bullet: */
281 switch (bp->b_face) {
296 for (pp = Monitor; pp < End_monitor; pp++)
300 switch (bp->b_face) {
315 for (pp = Monitor; pp < End_monitor; pp++)
318 /* Dispersion doors randomly disperse bullets: */
320 switch (rand_num(4)) {
335 /* Bullets zing past fliers: */
338 message(pp, "Zing!");
340 /* Bullets encountering a player: */
346 * Give the person a chance to catch a
347 * grenade if s/he is facing it:
350 pp->p_ident->i_shot += bp->b_charge;
351 if (opposite(bp->b_face, Maze[y][x])) {
352 /* Give them a 10% chance: */
353 if (rand_num(100) < conf_pgren_catch) {
354 /* They caught it! */
355 if (bp->b_owner != NULL)
357 "Your charge was absorbed!");
360 * The target player stole from the bullet's
361 * owner. Charge stolen statistics:
363 if (bp->b_score != NULL)
364 bp->b_score->i_robbed += bp->b_charge;
366 /* They acquire more ammo: */
367 pp->p_ammo += bp->b_charge;
369 /* Check if it would have destroyed them: */
370 if (pp->p_damage + bp->b_size * conf_mindam
372 /* Lucky escape statistics: */
373 pp->p_ident->i_saved++;
376 message(pp, "Absorbed charge (good shield!)");
378 /* Absorbtion statistics: */
379 pp->p_ident->i_absorbed += bp->b_charge;
381 /* Deallocate storage: */
384 /* Update ammo display: */
387 /* No need for caller to keep tracking it: */
391 /* Bullets faced head-on (statistics): */
392 pp->p_ident->i_faced += bp->b_charge;
396 * Small chance that the bullet just misses the
397 * person. If so, the bullet just goes on its
398 * merry way without exploding. (5% chance)
400 if (rand_num(100) < conf_pmiss) {
401 /* Ducked statistics: */
402 pp->p_ident->i_ducked += bp->b_charge;
404 /* Check if it would have killed them: */
405 if (pp->p_damage + bp->b_size * conf_mindam
407 /* Lucky escape statistics: */
408 pp->p_ident->i_saved++;
410 /* Shooter missed statistics: */
411 if (bp->b_score != NULL)
412 bp->b_score->i_missed += bp->b_charge;
414 /* Tell target that they were missed: */
415 message(pp, "Zing!");
417 /* Tell the bullet owner they missed: */
418 if (bp->b_owner != NULL)
420 ((bp->b_score->i_missed & 0x7) == 0x7) ?
421 "My! What a bad shot you are!" :
424 /* Don't fall through */
427 /* The player is to be blown up: */
431 /* Bullet hits a wall, and always explodes: */
439 /* Update the bullet's new position: */
444 /* Caller should keep tracking the bullet: */
450 * Move the drone to the next square
451 * Returns FALSE if the drone need no longer be tracked.
454 move_drone(BULLET *bp)
460 /* See if we can give someone a blast: */
461 if (is_player(Maze[bp->b_y][bp->b_x - 1])) {
465 if (is_player(Maze[bp->b_y - 1][bp->b_x])) {
469 if (is_player(Maze[bp->b_y + 1][bp->b_x])) {
473 if (is_player(Maze[bp->b_y][bp->b_x + 1])) {
478 /* Find out what directions are clear and move that way: */
480 if (!iswall(bp->b_y, bp->b_x - 1))
481 mask |= WEST, count++;
482 if (!iswall(bp->b_y - 1, bp->b_x))
483 mask |= NORTH, count++;
484 if (!iswall(bp->b_y + 1, bp->b_x))
485 mask |= SOUTH, count++;
486 if (!iswall(bp->b_y, bp->b_x + 1))
487 mask |= EAST, count++;
489 /* All blocked up, just wait: */
493 /* Only one way to go: */
499 /* Avoid backtracking, and remove the direction we came from: */
500 switch (bp->b_face) {
503 mask &= ~EAST, count--;
507 mask &= ~WEST, count--;
511 mask &= ~SOUTH, count--;
515 mask &= ~NORTH, count--;
519 /* Pick one of the remaining directions: */
521 if (n >= 0 && mask & NORTH)
523 if (n >= 0 && mask & SOUTH)
525 if (n >= 0 && mask & EAST)
527 if (n >= 0 && mask & WEST)
531 /* Move the drone: */
553 /* Look at what the drone moved onto: */
554 switch (Maze[bp->b_y][bp->b_x]) {
560 * Players have a 1% chance of absorbing a drone,
561 * if they are facing it.
563 if (rand_num(100) < conf_pdroneabsorb && opposite(bp->b_face,
564 Maze[bp->b_y][bp->b_x])) {
566 /* Feel the power: */
567 pp = play_at(bp->b_y, bp->b_x);
568 pp->p_ammo += bp->b_charge;
569 message(pp, "**** Absorbed drone ****");
571 /* Release drone storage: */
577 /* No need for caller to keep tracking drone: */
580 /* Detonate the drone: */
585 /* Keep tracking the drone. */
591 * Put a bullet back onto the bullet list
594 save_bullet(BULLET *bp)
597 /* Save what the bullet will be flying over: */
598 bp->b_over = Maze[bp->b_y][bp->b_x];
600 switch (bp->b_over) {
601 /* Bullets that can pass through each other: */
609 find_under(Bullets, bp);
613 switch (bp->b_over) {
614 /* A bullet hits a player: */
623 /* A bullet passes a boot: */
629 /* The bullet flies over everything else: */
631 Maze[bp->b_y][bp->b_x] = bp->b_type;
635 /* Insert the bullet into the Bullets list: */
636 bp->b_next = Bullets;
642 * Update the position of a player in flight
645 move_flyer(PLAYER *pp)
649 if (pp->p_undershot) {
650 fixshots(pp->p_y, pp->p_x, pp->p_over);
651 pp->p_undershot = FALSE;
654 /* Restore what the flier was flying over */
655 Maze[pp->p_y][pp->p_x] = pp->p_over;
658 x = pp->p_x + pp->p_flyx;
659 y = pp->p_y + pp->p_flyy;
661 /* Bouncing off the edges of the maze: */
664 pp->p_flyx = -pp->p_flyx;
666 else if (x > WIDTH - 2) {
667 x = (WIDTH - 2) - (x - (WIDTH - 2));
668 pp->p_flyx = -pp->p_flyx;
672 pp->p_flyy = -pp->p_flyy;
674 else if (y > HEIGHT - 2) {
675 y = (HEIGHT - 2) - (y - (HEIGHT - 2));
676 pp->p_flyy = -pp->p_flyy;
679 /* Make sure we don't land on something we can't: */
681 switch (Maze[y][x]) {
684 * Flier is over something other than space, a wall
685 * or a door. Randomly move (drift) the flier a little bit
686 * and then try again:
688 switch (rand_num(4)) {
690 PLUS_DELTA(x, WIDTH - 2);
696 PLUS_DELTA(y, HEIGHT - 2);
703 /* Give a little boost when about to land on a wall or door: */
710 if (pp->p_flying == 0)
713 /* Spaces are okay: */
718 /* Update flier's coordinates: */
722 /* Consume 'flying' time: */
723 if (pp->p_flying-- == 0) {
725 if (pp->p_face != BOOT && pp->p_face != BOOT_PAIR) {
726 /* Land a player - they stustain a fall: */
727 checkdam(pp, NULL, NULL,
728 rand_num(pp->p_damage / conf_fall_frac), FALL);
729 pp->p_face = rand_dir();
733 if (Maze[y][x] == BOOT)
734 pp->p_face = BOOT_PAIR;
739 /* Save under the flier: */
740 pp->p_over = Maze[y][x];
741 /* Draw in the flier: */
742 Maze[y][x] = pp->p_face;
743 showexpl(y, x, pp->p_face);
751 chkshot(BULLET *bp, BULLET *next)
760 switch (bp->b_type) {
767 delta = bp->b_size - 1;
779 /* Draw the explosion square: */
780 for (y = bp->b_y - delta; y <= bp->b_y + delta; y++) {
781 if (y < 0 || y >= HEIGHT)
784 absdy = (dy < 0) ? -dy : dy;
785 for (x = bp->b_x - delta; x <= bp->b_x + delta; x++) {
786 /* Draw a part of the explosion cloud: */
787 if (x < 0 || x >= WIDTH)
791 expl = (dy == 0) ? '*' : '|';
800 showexpl(y, x, expl);
802 /* Check what poor bastard was in the explosion: */
803 switch (Maze[y][x]) {
812 damage = bp->b_size - absdy;
814 damage = bp->b_size - dx;
816 /* Everybody hurts, sometimes. */
818 checkdam(pp, bp->b_owner, bp->b_score,
819 damage * conf_mindam, bp->b_type);
823 /* Mines detonate in a chain reaction: */
824 add_shot((Maze[y][x] == GMINE) ?
827 (Maze[y][x] == GMINE) ?
839 * handle slime shot exploding
842 chkslime(BULLET *bp, BULLET *next)
846 switch (Maze[bp->b_y][bp->b_x]) {
847 /* Slime explodes on walls and doors: */
854 switch (bp->b_face) {
871 /* Duplicate the unit of slime: */
872 nbp = (BULLET *) malloc(sizeof (BULLET));
874 logit(LOG_ERR, "malloc");
879 /* Move it around: */
880 move_slime(nbp, nbp->b_type == SLIME ? conf_slimespeed :
881 conf_lavaspeed, next);
886 * move the given slime shot speed times and add it back if
887 * it hasn't fizzled yet
890 move_slime(BULLET *bp, int speed, BULLET *next)
892 int i, j, dirmask, count;
897 if (bp->b_charge <= 0)
905 showexpl(bp->b_y, bp->b_x, bp->b_type == LAVA ? LAVA : '*');
907 switch (Maze[bp->b_y][bp->b_x]) {
908 /* Someone got hit by slime or lava: */
914 pp = play_at(bp->b_y, bp->b_x);
915 message(pp, "You've been slimed.");
916 checkdam(pp, bp->b_owner, bp->b_score, conf_mindam, bp->b_type);
918 /* Bullets detonate in slime and lava: */
924 explshot(next, bp->b_y, bp->b_x);
925 explshot(Bullets, bp->b_y, bp->b_x);
930 /* Drain the slime/lava of some energy: */
931 if (--bp->b_charge <= 0) {
937 /* Figure out which way the slime should flow: */
940 switch (bp->b_face) {
942 if (!iswall(bp->b_y, bp->b_x - 1))
943 dirmask |= WEST, count++;
944 if (!iswall(bp->b_y - 1, bp->b_x))
945 dirmask |= NORTH, count++;
946 if (!iswall(bp->b_y + 1, bp->b_x))
947 dirmask |= SOUTH, count++;
949 if (!iswall(bp->b_y, bp->b_x + 1))
950 dirmask |= EAST, count++;
953 if (!iswall(bp->b_y, bp->b_x + 1))
954 dirmask |= EAST, count++;
955 if (!iswall(bp->b_y - 1, bp->b_x))
956 dirmask |= NORTH, count++;
957 if (!iswall(bp->b_y + 1, bp->b_x))
958 dirmask |= SOUTH, count++;
960 if (!iswall(bp->b_y, bp->b_x - 1))
961 dirmask |= WEST, count++;
964 if (!iswall(bp->b_y - 1, bp->b_x))
965 dirmask |= NORTH, count++;
966 if (!iswall(bp->b_y, bp->b_x - 1))
967 dirmask |= WEST, count++;
968 if (!iswall(bp->b_y, bp->b_x + 1))
969 dirmask |= EAST, count++;
971 if (!iswall(bp->b_y + 1, bp->b_x))
972 dirmask |= SOUTH, count++;
975 if (!iswall(bp->b_y + 1, bp->b_x))
976 dirmask |= SOUTH, count++;
977 if (!iswall(bp->b_y, bp->b_x - 1))
978 dirmask |= WEST, count++;
979 if (!iswall(bp->b_y, bp->b_x + 1))
980 dirmask |= EAST, count++;
982 if (!iswall(bp->b_y - 1, bp->b_x))
983 dirmask |= NORTH, count++;
988 * No place to go. Just sit here for a while and wait
989 * for adjacent squares to clear out.
994 if (bp->b_charge < count) {
995 /* Only bp->b_charge paths may be taken */
996 while (count > bp->b_charge) {
999 else if (dirmask & EAST)
1001 else if (dirmask & NORTH)
1003 else if (dirmask & SOUTH)
1009 /* Spawn little slimes off in every possible direction: */
1010 i = bp->b_charge / count;
1011 j = bp->b_charge % count;
1012 if (dirmask & WEST) {
1014 nbp = create_shot(bp->b_type, bp->b_y, bp->b_x - 1, LEFTS,
1015 i, bp->b_size, bp->b_owner, bp->b_score, TRUE, SPACE);
1016 move_slime(nbp, speed - 1, next);
1018 if (dirmask & EAST) {
1020 nbp = create_shot(bp->b_type, bp->b_y, bp->b_x + 1, RIGHT,
1021 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
1022 bp->b_score, TRUE, SPACE);
1023 move_slime(nbp, speed - 1, next);
1025 if (dirmask & NORTH) {
1027 nbp = create_shot(bp->b_type, bp->b_y - 1, bp->b_x, ABOVE,
1028 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
1029 bp->b_score, TRUE, SPACE);
1030 move_slime(nbp, speed - 1, next);
1032 if (dirmask & SOUTH) {
1034 nbp = create_shot(bp->b_type, bp->b_y + 1, bp->b_x, BELOW,
1035 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
1036 bp->b_score, TRUE, SPACE);
1037 move_slime(nbp, speed - 1, next);
1045 * returns whether the given location is a wall
1048 iswall(int y, int x)
1050 if (y < 0 || x < 0 || y >= HEIGHT || x >= WIDTH)
1052 switch (Maze[y][x]) {
1068 * Take a shot out of the air.
1071 zapshot(BULLET *blist, BULLET *obp)
1075 for (bp = blist; bp != NULL; bp = bp->b_next) {
1076 /* Find co-located bullets not facing the same way: */
1077 if (bp->b_face != obp->b_face
1078 && bp->b_x == obp->b_x && bp->b_y == obp->b_y)
1080 /* Bullet collision: */
1081 explshot(blist, obp->b_y, obp->b_x);
1089 * Make all shots at this location blow up
1092 explshot(BULLET *blist, int y, int x)
1096 for (bp = blist; bp != NULL; bp = bp->b_next)
1097 if (bp->b_x == x && bp->b_y == y) {
1099 if (bp->b_owner != NULL)
1100 message(bp->b_owner, "Shot intercepted.");
1106 * Return a pointer to the player at the given location
1109 play_at(int y, int x)
1113 for (pp = Player; pp < End_player; pp++)
1114 if (pp->p_x == x && pp->p_y == y)
1117 /* Internal fault: */
1118 logx(LOG_ERR, "play_at: not a player");
1124 * Return TRUE if the bullet direction faces the opposite direction
1125 * of the player in the maze
1128 opposite(int face, char dir)
1132 return (dir == RIGHT);
1134 return (dir == LEFTS);
1136 return (dir == BELOW);
1138 return (dir == ABOVE);
1146 * Is there a bullet at the given coordinates? If so, return
1147 * a pointer to the bullet, otherwise return NULL
1150 is_bullet(int y, int x)
1154 for (bp = Bullets; bp != NULL; bp = bp->b_next)
1155 if (bp->b_y == y && bp->b_x == x)
1162 * change the underlying character of the shots at a location
1163 * to the given character.
1166 fixshots(int y, int x, char over)
1170 for (bp = Bullets; bp != NULL; bp = bp->b_next)
1171 if (bp->b_y == y && bp->b_x == x)
1177 * find the underlying character for a bullet when it lands
1178 * on another bullet.
1181 find_under(BULLET *blist, BULLET *bp)
1185 for (nbp = blist; nbp != NULL; nbp = nbp->b_next)
1186 if (bp->b_y == nbp->b_y && bp->b_x == nbp->b_x) {
1187 bp->b_over = nbp->b_over;
1194 * mark a player as under a shot
1197 mark_player(BULLET *bp)
1201 for (pp = Player; pp < End_player; pp++)
1202 if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) {
1203 pp->p_undershot = TRUE;
1210 * mark a boot as under a shot
1213 mark_boot(BULLET *bp)
1217 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
1218 if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) {
1219 pp->p_undershot = TRUE;