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
can_moveshots(void)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
moveshots(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
move_normal_shot(BULLET * bp)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
move_drone(BULLET * bp)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
save_bullet(BULLET * bp)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
move_flyer(PLAYER * pp)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
chkshot(BULLET * bp,BULLET * next)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
chkslime(BULLET * bp,BULLET * next)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
move_slime(BULLET * bp,int speed,BULLET * next)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
iswall(int y,int x)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
zapshot(BULLET * blist,BULLET * obp)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
explshot(BULLET * blist,int y,int x)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 *
play_at(int y,int x)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
opposite(int face,char dir)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 *
is_bullet(int y,int x)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
fixshots(int y,int x,char over)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
find_under(BULLET * blist,BULLET * bp)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
mark_player(BULLET * bp)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
mark_boot(BULLET * bp)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