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