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.1 2008/09/02 21:50:21 dillon 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.
234 * Walk an unexploded bullet along conf_bulspd times, moving it
235 * one unit along each step. We flag it as exploding if it
239 for (i = 0; i < conf_bulspd; i++) {
241 /* Stop if the bullet has already exploded: */
245 /* Adjust the bullet's co-ordinates: */
248 switch (bp->b_face) {
264 /* Look at what the bullet is colliding with : */
265 switch (Maze[y][x]) {
266 /* Gun shots have a chance of collision: */
268 if (rand_num(100) < conf_pshot_coll) {
269 zapshot(Bullets, bp);
270 zapshot(bp->b_next, bp);
273 /* Grenades only have a chance of collision: */
275 if (rand_num(100) < conf_pgren_coll) {
276 zapshot(Bullets, bp);
277 zapshot(bp->b_next, bp);
280 /* Reflecting walls richochet the bullet: */
282 switch (bp->b_face) {
297 for (pp = Monitor; pp < End_monitor; pp++)
301 switch (bp->b_face) {
316 for (pp = Monitor; pp < End_monitor; pp++)
319 /* Dispersion doors randomly disperse bullets: */
321 switch (rand_num(4)) {
336 /* Bullets zing past fliers: */
339 message(pp, "Zing!");
341 /* Bullets encountering a player: */
347 * Give the person a chance to catch a
348 * grenade if s/he is facing it:
351 pp->p_ident->i_shot += bp->b_charge;
352 if (opposite(bp->b_face, Maze[y][x])) {
353 /* Give them a 10% chance: */
354 if (rand_num(100) < conf_pgren_catch) {
355 /* They caught it! */
356 if (bp->b_owner != NULL)
358 "Your charge was absorbed!");
361 * The target player stole from the bullet's
362 * owner. Charge stolen statistics:
364 if (bp->b_score != NULL)
365 bp->b_score->i_robbed += bp->b_charge;
367 /* They acquire more ammo: */
368 pp->p_ammo += bp->b_charge;
370 /* Check if it would have destroyed them: */
371 if (pp->p_damage + bp->b_size * conf_mindam
373 /* Lucky escape statistics: */
374 pp->p_ident->i_saved++;
377 message(pp, "Absorbed charge (good shield!)");
379 /* Absorbtion statistics: */
380 pp->p_ident->i_absorbed += bp->b_charge;
382 /* Deallocate storage: */
385 /* Update ammo display: */
388 /* No need for caller to keep tracking it: */
392 /* Bullets faced head-on (statistics): */
393 pp->p_ident->i_faced += bp->b_charge;
397 * Small chance that the bullet just misses the
398 * person. If so, the bullet just goes on its
399 * merry way without exploding. (5% chance)
401 if (rand_num(100) < conf_pmiss) {
402 /* Ducked statistics: */
403 pp->p_ident->i_ducked += bp->b_charge;
405 /* Check if it would have killed them: */
406 if (pp->p_damage + bp->b_size * conf_mindam
408 /* Lucky escape statistics: */
409 pp->p_ident->i_saved++;
411 /* Shooter missed statistics: */
412 if (bp->b_score != NULL)
413 bp->b_score->i_missed += bp->b_charge;
415 /* Tell target that they were missed: */
416 message(pp, "Zing!");
418 /* Tell the bullet owner they missed: */
419 if (bp->b_owner != NULL)
421 ((bp->b_score->i_missed & 0x7) == 0x7) ?
422 "My! What a bad shot you are!" :
425 /* Don't fall through */
428 /* The player is to be blown up: */
432 /* Bullet hits a wall, and always explodes: */
440 /* Update the bullet's new position: */
445 /* Caller should keep tracking the bullet: */
451 * Move the drone to the next square
452 * Returns FALSE if the drone need no longer be tracked.
462 /* See if we can give someone a blast: */
463 if (is_player(Maze[bp->b_y][bp->b_x - 1])) {
467 if (is_player(Maze[bp->b_y - 1][bp->b_x])) {
471 if (is_player(Maze[bp->b_y + 1][bp->b_x])) {
475 if (is_player(Maze[bp->b_y][bp->b_x + 1])) {
480 /* Find out what directions are clear and move that way: */
482 if (!iswall(bp->b_y, bp->b_x - 1))
483 mask |= WEST, count++;
484 if (!iswall(bp->b_y - 1, bp->b_x))
485 mask |= NORTH, count++;
486 if (!iswall(bp->b_y + 1, bp->b_x))
487 mask |= SOUTH, count++;
488 if (!iswall(bp->b_y, bp->b_x + 1))
489 mask |= EAST, count++;
491 /* All blocked up, just wait: */
495 /* Only one way to go: */
501 /* Avoid backtracking, and remove the direction we came from: */
502 switch (bp->b_face) {
505 mask &= ~EAST, count--;
509 mask &= ~WEST, count--;
513 mask &= ~SOUTH, count--;
517 mask &= ~NORTH, count--;
521 /* Pick one of the remaining directions: */
523 if (n >= 0 && mask & NORTH)
525 if (n >= 0 && mask & SOUTH)
527 if (n >= 0 && mask & EAST)
529 if (n >= 0 && mask & WEST)
533 /* Move the drone: */
555 /* Look at what the drone moved onto: */
556 switch (Maze[bp->b_y][bp->b_x]) {
562 * Players have a 1% chance of absorbing a drone,
563 * if they are facing it.
565 if (rand_num(100) < conf_pdroneabsorb && opposite(bp->b_face,
566 Maze[bp->b_y][bp->b_x])) {
568 /* Feel the power: */
569 pp = play_at(bp->b_y, bp->b_x);
570 pp->p_ammo += bp->b_charge;
571 message(pp, "**** Absorbed drone ****");
573 /* Release drone storage: */
579 /* No need for caller to keep tracking drone: */
582 /* Detonate the drone: */
587 /* Keep tracking the drone. */
593 * Put a bullet back onto the bullet list
600 /* Save what the bullet will be flying over: */
601 bp->b_over = Maze[bp->b_y][bp->b_x];
603 switch (bp->b_over) {
604 /* Bullets that can pass through each other: */
612 find_under(Bullets, bp);
616 switch (bp->b_over) {
617 /* A bullet hits a player: */
626 /* A bullet passes a boot: */
632 /* The bullet flies over everything else: */
634 Maze[bp->b_y][bp->b_x] = bp->b_type;
638 /* Insert the bullet into the Bullets list: */
639 bp->b_next = Bullets;
645 * Update the position of a player in flight
653 if (pp->p_undershot) {
654 fixshots(pp->p_y, pp->p_x, pp->p_over);
655 pp->p_undershot = FALSE;
658 /* Restore what the flier was flying over */
659 Maze[pp->p_y][pp->p_x] = pp->p_over;
662 x = pp->p_x + pp->p_flyx;
663 y = pp->p_y + pp->p_flyy;
665 /* Bouncing off the edges of the maze: */
668 pp->p_flyx = -pp->p_flyx;
670 else if (x > WIDTH - 2) {
671 x = (WIDTH - 2) - (x - (WIDTH - 2));
672 pp->p_flyx = -pp->p_flyx;
676 pp->p_flyy = -pp->p_flyy;
678 else if (y > HEIGHT - 2) {
679 y = (HEIGHT - 2) - (y - (HEIGHT - 2));
680 pp->p_flyy = -pp->p_flyy;
683 /* Make sure we don't land on something we can't: */
685 switch (Maze[y][x]) {
688 * Flier is over something other than space, a wall
689 * or a door. Randomly move (drift) the flier a little bit
690 * and then try again:
692 switch (rand_num(4)) {
694 PLUS_DELTA(x, WIDTH - 2);
700 PLUS_DELTA(y, HEIGHT - 2);
707 /* Give a little boost when about to land on a wall or door: */
714 if (pp->p_flying == 0)
717 /* Spaces are okay: */
722 /* Update flier's coordinates: */
726 /* Consume 'flying' time: */
727 if (pp->p_flying-- == 0) {
729 if (pp->p_face != BOOT && pp->p_face != BOOT_PAIR) {
730 /* Land a player - they stustain a fall: */
731 checkdam(pp, (PLAYER *) NULL, (IDENT *) NULL,
732 rand_num(pp->p_damage / conf_fall_frac), FALL);
733 pp->p_face = rand_dir();
737 if (Maze[y][x] == BOOT)
738 pp->p_face = BOOT_PAIR;
743 /* Save under the flier: */
744 pp->p_over = Maze[y][x];
745 /* Draw in the flier: */
746 Maze[y][x] = pp->p_face;
747 showexpl(y, x, pp->p_face);
766 switch (bp->b_type) {
773 delta = bp->b_size - 1;
785 /* Draw the explosion square: */
786 for (y = bp->b_y - delta; y <= bp->b_y + delta; y++) {
787 if (y < 0 || y >= HEIGHT)
790 absdy = (dy < 0) ? -dy : dy;
791 for (x = bp->b_x - delta; x <= bp->b_x + delta; x++) {
792 /* Draw a part of the explosion cloud: */
793 if (x < 0 || x >= WIDTH)
797 expl = (dy == 0) ? '*' : '|';
806 showexpl(y, x, expl);
808 /* Check what poor bastard was in the explosion: */
809 switch (Maze[y][x]) {
818 damage = bp->b_size - absdy;
820 damage = bp->b_size - dx;
822 /* Everybody hurts, sometimes. */
824 checkdam(pp, bp->b_owner, bp->b_score,
825 damage * conf_mindam, bp->b_type);
829 /* Mines detonate in a chain reaction: */
830 add_shot((Maze[y][x] == GMINE) ?
833 (Maze[y][x] == GMINE) ?
835 (PLAYER *) NULL, TRUE, SPACE);
845 * handle slime shot exploding
854 switch (Maze[bp->b_y][bp->b_x]) {
855 /* Slime explodes on walls and doors: */
862 switch (bp->b_face) {
879 /* Duplicate the unit of slime: */
880 nbp = (BULLET *) malloc(sizeof (BULLET));
882 logit(LOG_ERR, "malloc");
887 /* Move it around: */
888 move_slime(nbp, nbp->b_type == SLIME ? conf_slimespeed :
889 conf_lavaspeed, next);
894 * move the given slime shot speed times and add it back if
895 * it hasn't fizzled yet
898 move_slime(bp, speed, next)
903 int i, j, dirmask, count;
908 if (bp->b_charge <= 0)
916 showexpl(bp->b_y, bp->b_x, bp->b_type == LAVA ? LAVA : '*');
918 switch (Maze[bp->b_y][bp->b_x]) {
919 /* Someone got hit by slime or lava: */
925 pp = play_at(bp->b_y, bp->b_x);
926 message(pp, "You've been slimed.");
927 checkdam(pp, bp->b_owner, bp->b_score, conf_mindam, bp->b_type);
929 /* Bullets detonate in slime and lava: */
935 explshot(next, bp->b_y, bp->b_x);
936 explshot(Bullets, bp->b_y, bp->b_x);
941 /* Drain the slime/lava of some energy: */
942 if (--bp->b_charge <= 0) {
948 /* Figure out which way the slime should flow: */
951 switch (bp->b_face) {
953 if (!iswall(bp->b_y, bp->b_x - 1))
954 dirmask |= WEST, 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 |= EAST, count++;
964 if (!iswall(bp->b_y, bp->b_x + 1))
965 dirmask |= EAST, count++;
966 if (!iswall(bp->b_y - 1, bp->b_x))
967 dirmask |= NORTH, count++;
968 if (!iswall(bp->b_y + 1, bp->b_x))
969 dirmask |= SOUTH, count++;
971 if (!iswall(bp->b_y, bp->b_x - 1))
972 dirmask |= WEST, count++;
975 if (!iswall(bp->b_y - 1, bp->b_x))
976 dirmask |= NORTH, 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 |= SOUTH, count++;
986 if (!iswall(bp->b_y + 1, bp->b_x))
987 dirmask |= SOUTH, count++;
988 if (!iswall(bp->b_y, bp->b_x - 1))
989 dirmask |= WEST, count++;
990 if (!iswall(bp->b_y, bp->b_x + 1))
991 dirmask |= EAST, count++;
993 if (!iswall(bp->b_y - 1, bp->b_x))
994 dirmask |= NORTH, count++;
999 * No place to go. Just sit here for a while and wait
1000 * for adjacent squares to clear out.
1005 if (bp->b_charge < count) {
1006 /* Only bp->b_charge paths may be taken */
1007 while (count > bp->b_charge) {
1010 else if (dirmask & EAST)
1012 else if (dirmask & NORTH)
1014 else if (dirmask & SOUTH)
1020 /* Spawn little slimes off in every possible direction: */
1021 i = bp->b_charge / count;
1022 j = bp->b_charge % count;
1023 if (dirmask & WEST) {
1025 nbp = create_shot(bp->b_type, bp->b_y, bp->b_x - 1, LEFTS,
1026 i, bp->b_size, bp->b_owner, bp->b_score, TRUE, SPACE);
1027 move_slime(nbp, speed - 1, next);
1029 if (dirmask & EAST) {
1031 nbp = create_shot(bp->b_type, bp->b_y, bp->b_x + 1, RIGHT,
1032 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
1033 bp->b_score, TRUE, SPACE);
1034 move_slime(nbp, speed - 1, next);
1036 if (dirmask & NORTH) {
1038 nbp = create_shot(bp->b_type, bp->b_y - 1, bp->b_x, ABOVE,
1039 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
1040 bp->b_score, TRUE, SPACE);
1041 move_slime(nbp, speed - 1, next);
1043 if (dirmask & SOUTH) {
1045 nbp = create_shot(bp->b_type, bp->b_y + 1, bp->b_x, BELOW,
1046 (count < j) ? i + 1 : i, bp->b_size, bp->b_owner,
1047 bp->b_score, TRUE, SPACE);
1048 move_slime(nbp, speed - 1, next);
1056 * returns whether the given location is a wall
1062 if (y < 0 || x < 0 || y >= HEIGHT || x >= WIDTH)
1064 switch (Maze[y][x]) {
1080 * Take a shot out of the air.
1084 BULLET *blist, *obp;
1088 for (bp = blist; bp != NULL; bp = bp->b_next) {
1089 /* Find co-located bullets not facing the same way: */
1090 if (bp->b_face != obp->b_face
1091 && bp->b_x == obp->b_x && bp->b_y == obp->b_y)
1093 /* Bullet collision: */
1094 explshot(blist, obp->b_y, obp->b_x);
1102 * Make all shots at this location blow up
1105 explshot(blist, y, x)
1111 for (bp = blist; bp != NULL; bp = bp->b_next)
1112 if (bp->b_x == x && bp->b_y == y) {
1114 if (bp->b_owner != NULL)
1115 message(bp->b_owner, "Shot intercepted.");
1121 * Return a pointer to the player at the given location
1129 for (pp = Player; pp < End_player; pp++)
1130 if (pp->p_x == x && pp->p_y == y)
1133 /* Internal fault: */
1134 logx(LOG_ERR, "play_at: not a player");
1140 * Return TRUE if the bullet direction faces the opposite direction
1141 * of the player in the maze
1150 return (dir == RIGHT);
1152 return (dir == LEFTS);
1154 return (dir == BELOW);
1156 return (dir == ABOVE);
1164 * Is there a bullet at the given coordinates? If so, return
1165 * a pointer to the bullet, otherwise return NULL
1173 for (bp = Bullets; bp != NULL; bp = bp->b_next)
1174 if (bp->b_y == y && bp->b_x == x)
1181 * change the underlying character of the shots at a location
1182 * to the given character.
1185 fixshots(y, x, over)
1191 for (bp = Bullets; bp != NULL; bp = bp->b_next)
1192 if (bp->b_y == y && bp->b_x == x)
1198 * find the underlying character for a bullet when it lands
1199 * on another bullet.
1202 find_under(blist, bp)
1207 for (nbp = blist; nbp != NULL; nbp = nbp->b_next)
1208 if (bp->b_y == nbp->b_y && bp->b_x == nbp->b_x) {
1209 bp->b_over = nbp->b_over;
1216 * mark a player as under a shot
1224 for (pp = Player; pp < End_player; pp++)
1225 if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) {
1226 pp->p_undershot = TRUE;
1233 * mark a boot as under a shot
1241 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
1242 if (pp->p_y == bp->b_y && pp->p_x == bp->b_x) {
1243 pp->p_undershot = TRUE;