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