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