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
45static	void	chkshot(BULLET *, BULLET *);
46static	void	chkslime(BULLET *, BULLET *);
47static	void	explshot(BULLET *, int, int);
48static	void	find_under(BULLET *, BULLET *);
49static	int	iswall(int, int);
50static	void	mark_boot(BULLET *);
51static	void	mark_player(BULLET *);
52static	int	move_drone(BULLET *);
53static	void	move_flyer(PLAYER *);
54static	int	move_normal_shot(BULLET *);
55static	void	move_slime(BULLET *, int, BULLET *);
56static	void	save_bullet(BULLET *);
57static	void	zapshot(BULLET *, BULLET *);
58
59/* Return true if there is pending activity */
60int
61can_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 */
89void
90moveshots(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
199no_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 */
224static int
225move_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 */
451static int
452move_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
528drone_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 */
591static void
592save_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 */
642static void
643move_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: */
678again:
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 */
748static void
749chkshot(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 */
839static void
840chkslime(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 */
887static void
888move_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 */
1045static int
1046iswall(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 */
1068static void
1069zapshot(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 */
1089static void
1090explshot(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 */
1106PLAYER *
1107play_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 */
1125int
1126opposite(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 */
1147BULLET *
1148is_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 */
1163void
1164fixshots(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 */
1178static void
1179find_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 */
1194static void
1195mark_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 */
1210static void
1211mark_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