1 /* $OpenBSD: driver.c,v 1.31 2024/08/21 14:55:17 florian Exp $ */
2 /* $NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 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/stat.h>
35
36 #include <arpa/inet.h>
37
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <netdb.h>
42 #include <paths.h>
43 #include <signal.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <unistd.h>
48
49 #include "conf.h"
50 #include "hunt.h"
51 #include "server.h"
52
53 u_int16_t Server_port;
54 int Server_socket; /* test socket to answer datagrams */
55 FLAG should_announce = TRUE; /* true if listening on standard port */
56 u_short sock_port; /* port # of tcp listen socket */
57 u_short stat_port; /* port # of statistics tcp socket */
58 in_addr_t Server_addr = INADDR_ANY; /* address to bind to */
59
60 static void clear_scores(void);
61 static int havechar(PLAYER *);
62 static void init(int);
63 static void makeboots(void);
64 static void send_stats(void);
65 static void zap(PLAYER *, FLAG);
66 static void announce_game(void);
67 static void siginfo(int);
68 static void print_stats(FILE *);
69 static void handle_wkport(int);
70
71 /*
72 * main:
73 * The main program.
74 */
75 int
main(int ac,char ** av)76 main(int ac, char **av)
77 {
78 PLAYER *pp;
79 int had_char;
80 static fd_set read_fds;
81 static FLAG first = TRUE;
82 static FLAG server = FALSE;
83 extern char *__progname;
84 int c;
85 static struct timeval linger = { 0, 0 };
86 static struct timeval timeout = { 0, 0 }, *to;
87 struct spawn *sp, *spnext;
88 int ret;
89 int nready;
90 int fd;
91 int background = 0;
92
93 config();
94
95 while ((c = getopt(ac, av, "bsp:a:D:")) != -1) {
96 switch (c) {
97 case 'b':
98 background = 1;
99 conf_syslog = 1;
100 conf_logerr = 0;
101 break;
102 case 's':
103 server = TRUE;
104 break;
105 case 'p':
106 should_announce = FALSE;
107 Server_port = atoi(optarg);
108 break;
109 case 'a':
110 if (inet_pton(AF_INET, optarg,
111 (struct in_addr *)&Server_addr) != 1)
112 err(1, "bad interface address: %s", optarg);
113 break;
114 case 'D':
115 config_arg(optarg);
116 break;
117 default:
118 erred:
119 fprintf(stderr,
120 "usage: %s [-bs] [-a addr] [-D var=value] "
121 "[-p port]\n",
122 __progname);
123 return 2;
124 }
125 }
126 if (optind < ac)
127 goto erred;
128
129 /* Open syslog: */
130 openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
131 LOG_DAEMON);
132
133 /* Initialise game parameters: */
134 init(background);
135
136 again:
137 do {
138 /* First, poll to see if we can get input */
139 do {
140 read_fds = Fds_mask;
141 errno = 0;
142 timerclear(&timeout);
143 nready = select(Num_fds, &read_fds, NULL, NULL,
144 &timeout);
145 if (nready < 0 && errno != EINTR) {
146 logit(LOG_ERR, "select");
147 cleanup(1);
148 }
149 } while (nready < 0);
150
151 if (nready == 0) {
152 /*
153 * Nothing was ready. We do some work now
154 * to see if the simulation has any pending work
155 * to do, and decide if we need to block
156 * indefinitely or just timeout.
157 */
158 do {
159 if (conf_simstep && can_moveshots()) {
160 /*
161 * block for a short time before continuing
162 * with explosions, bullets and whatnot
163 */
164 to = &timeout;
165 to->tv_sec = conf_simstep / 1000000;
166 to->tv_usec = conf_simstep % 1000000;
167 } else
168 /*
169 * since there's nothing going on,
170 * just block waiting for external activity
171 */
172 to = NULL;
173
174 read_fds = Fds_mask;
175 errno = 0;
176 nready = select(Num_fds, &read_fds, NULL, NULL,
177 to);
178 if (nready < 0 && errno != EINTR) {
179 logit(LOG_ERR, "select");
180 cleanup(1);
181 }
182 } while (nready < 0);
183 }
184
185 /* Remember which descriptors are active: */
186 Have_inp = read_fds;
187
188 /* Answer new player connections: */
189 if (FD_ISSET(Socket, &Have_inp))
190 answer_first();
191
192 /* Continue answering new player connections: */
193 for (sp = Spawn; sp; ) {
194 spnext = sp->next;
195 fd = sp->fd;
196 if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
197 /*
198 * Remove from the spawn list. (fd remains in
199 * read set).
200 */
201 *sp->prevnext = sp->next;
202 if (sp->next)
203 sp->next->prevnext = sp->prevnext;
204 free(sp);
205
206 /* We probably consumed all data. */
207 FD_CLR(fd, &Have_inp);
208
209 /* Announce game if this is the first spawn. */
210 if (first && should_announce)
211 announce_game();
212 first = FALSE;
213 }
214 sp = spnext;
215 }
216
217 /* Process input and move bullets until we've exhausted input */
218 had_char = TRUE;
219 while (had_char) {
220
221 moveshots();
222 for (pp = Player; pp < End_player; )
223 if (pp->p_death[0] != '\0')
224 zap(pp, TRUE);
225 else
226 pp++;
227 for (pp = Monitor; pp < End_monitor; )
228 if (pp->p_death[0] != '\0')
229 zap(pp, FALSE);
230 else
231 pp++;
232
233 had_char = FALSE;
234 for (pp = Player; pp < End_player; pp++)
235 if (havechar(pp)) {
236 execute(pp);
237 pp->p_nexec++;
238 had_char = TRUE;
239 }
240 for (pp = Monitor; pp < End_monitor; pp++)
241 if (havechar(pp)) {
242 mon_execute(pp);
243 pp->p_nexec++;
244 had_char = TRUE;
245 }
246 }
247
248 /* Handle a datagram sent to the server socket: */
249 if (FD_ISSET(Server_socket, &Have_inp))
250 handle_wkport(Server_socket);
251
252 /* Answer statistics connections: */
253 if (FD_ISSET(Status, &Have_inp))
254 send_stats();
255
256 /* Flush/synchronize all the displays: */
257 for (pp = Player; pp < End_player; pp++) {
258 if (FD_ISSET(pp->p_fd, &read_fds)) {
259 sendcom(pp, READY, pp->p_nexec);
260 pp->p_nexec = 0;
261 }
262 flush(pp);
263 }
264 for (pp = Monitor; pp < End_monitor; pp++) {
265 if (FD_ISSET(pp->p_fd, &read_fds)) {
266 sendcom(pp, READY, pp->p_nexec);
267 pp->p_nexec = 0;
268 }
269 flush(pp);
270 }
271 } while (Nplayer > 0);
272
273 /* No more players! */
274
275 /* No players yet or a continuous game? */
276 if (first || conf_linger < 0)
277 goto again;
278
279 /* Wait a short while for one to come back: */
280 read_fds = Fds_mask;
281 linger.tv_sec = conf_linger;
282 while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
283 if (errno != EINTR) {
284 logit(LOG_WARNING, "select");
285 break;
286 }
287 read_fds = Fds_mask;
288 linger.tv_sec = conf_linger;
289 linger.tv_usec = 0;
290 }
291 if (ret > 0)
292 /* Someone returned! Resume the game: */
293 goto again;
294 /* else, it timed out, and the game is really over. */
295
296 /* If we are an inetd server, we should re-init the map and restart: */
297 if (server) {
298 clear_scores();
299 makemaze();
300 clearwalls();
301 makeboots();
302 first = TRUE;
303 goto again;
304 }
305
306 /* Get rid of any attached monitors: */
307 for (pp = Monitor; pp < End_monitor; )
308 zap(pp, FALSE);
309
310 /* Fin: */
311 cleanup(0);
312 return 0;
313 }
314
315 /*
316 * init:
317 * Initialize the global parameters.
318 */
319 static void
init(int background)320 init(int background)
321 {
322 int i;
323 struct sockaddr_in test_port;
324 int true = 1;
325 socklen_t len;
326 struct sockaddr_in addr;
327 struct sigaction sact;
328 struct servent *se;
329
330 sact.sa_flags = SA_RESTART;
331 sigemptyset(&sact.sa_mask);
332
333 /* Ignore HUP, QUIT and PIPE: */
334 sact.sa_handler = SIG_IGN;
335 if (sigaction(SIGHUP, &sact, NULL) == -1)
336 err(1, "sigaction SIGHUP");
337 if (sigaction(SIGQUIT, &sact, NULL) == -1)
338 err(1, "sigaction SIGQUIT");
339 if (sigaction(SIGPIPE, &sact, NULL) == -1)
340 err(1, "sigaction SIGPIPE");
341
342 /* Clean up gracefully on INT and TERM: */
343 sact.sa_handler = cleanup;
344 if (sigaction(SIGINT, &sact, NULL) == -1)
345 err(1, "sigaction SIGINT");
346 if (sigaction(SIGTERM, &sact, NULL) == -1)
347 err(1, "sigaction SIGTERM");
348
349 /* Handle INFO: */
350 sact.sa_handler = siginfo;
351 if (sigaction(SIGINFO, &sact, NULL) == -1)
352 err(1, "sigaction SIGINFO");
353
354 if (chdir("/") == -1)
355 warn("chdir");
356 (void) umask(0777);
357
358 /* Initialize statistics socket: */
359 addr.sin_family = AF_INET;
360 addr.sin_addr.s_addr = Server_addr;
361 addr.sin_port = 0;
362
363 Status = socket(AF_INET, SOCK_STREAM, 0);
364 if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
365 logit(LOG_ERR, "bind");
366 cleanup(1);
367 }
368 if (listen(Status, 5) == -1) {
369 logit(LOG_ERR, "listen");
370 cleanup(1);
371 }
372
373 len = sizeof (struct sockaddr_in);
374 if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0) {
375 logit(LOG_ERR, "getsockname");
376 cleanup(1);
377 }
378 stat_port = ntohs(addr.sin_port);
379
380 /* Initialize main socket: */
381 addr.sin_family = AF_INET;
382 addr.sin_addr.s_addr = Server_addr;
383 addr.sin_port = 0;
384
385 Socket = socket(AF_INET, SOCK_STREAM, 0);
386
387 if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
388 logit(LOG_ERR, "bind");
389 cleanup(1);
390 }
391 if (listen(Socket, 5) == -1) {
392 logit(LOG_ERR, "listen");
393 cleanup(1);
394 }
395
396 len = sizeof (struct sockaddr_in);
397 if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0) {
398 logit(LOG_ERR, "getsockname");
399 cleanup(1);
400 }
401 sock_port = ntohs(addr.sin_port);
402
403 /* Initialize minimal select mask */
404 FD_ZERO(&Fds_mask);
405 FD_SET(Socket, &Fds_mask);
406 FD_SET(Status, &Fds_mask);
407 Num_fds = ((Socket > Status) ? Socket : Status) + 1;
408
409 /* Find the port that huntd should run on */
410 if (Server_port == 0) {
411 se = getservbyname("hunt", "udp");
412 if (se != NULL)
413 Server_port = ntohs(se->s_port);
414 else
415 Server_port = HUNT_PORT;
416 }
417
418 /* Check if stdin is a socket: */
419 len = sizeof (struct sockaddr_in);
420 if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
421 && test_port.sin_family == AF_INET) {
422 /* We are probably running from inetd: don't log to stderr */
423 Server_socket = STDIN_FILENO;
424 conf_logerr = 0;
425 if (test_port.sin_port != htons((u_short) Server_port)) {
426 /* Private game */
427 should_announce = FALSE;
428 Server_port = ntohs(test_port.sin_port);
429 }
430 } else {
431 /* We need to listen on a socket: */
432 test_port = addr;
433 test_port.sin_port = htons((u_short) Server_port);
434
435 Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
436
437 /* Permit multiple huntd's on the same port. */
438 if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
439 sizeof true) < 0)
440 logit(LOG_ERR, "setsockopt SO_REUSEADDR");
441
442 if (bind(Server_socket, (struct sockaddr *) &test_port,
443 sizeof test_port) < 0) {
444 logit(LOG_ERR, "bind port %d", Server_port);
445 cleanup(1);
446 }
447
448 /* Become a daemon if asked to do so. */
449 if (background)
450 daemon(0, 0);
451
452 /* Datagram sockets do not need a listen() call. */
453 }
454
455 /* We'll handle the broadcast listener in the main loop: */
456 FD_SET(Server_socket, &Fds_mask);
457 if (Server_socket + 1 > Num_fds)
458 Num_fds = Server_socket + 1;
459
460 /* Dig the maze: */
461 makemaze();
462
463 /* Create some boots, if needed: */
464 makeboots();
465
466 /* Construct a table of what objects a player can see over: */
467 for (i = 0; i < NASCII; i++)
468 See_over[i] = TRUE;
469 See_over[DOOR] = FALSE;
470 See_over[WALL1] = FALSE;
471 See_over[WALL2] = FALSE;
472 See_over[WALL3] = FALSE;
473 See_over[WALL4] = FALSE;
474 See_over[WALL5] = FALSE;
475
476 logx(LOG_INFO, "game started");
477 }
478
479 /*
480 * makeboots:
481 * Put the boots in the maze
482 */
483 static void
makeboots(void)484 makeboots(void)
485 {
486 int x, y;
487 PLAYER *pp;
488
489 if (conf_boots) {
490 do {
491 x = rand_num(WIDTH - 1) + 1;
492 y = rand_num(HEIGHT - 1) + 1;
493 } while (Maze[y][x] != SPACE);
494 Maze[y][x] = BOOT_PAIR;
495 }
496
497 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
498 pp->p_flying = -1;
499 }
500
501
502 /*
503 * checkdam:
504 * Apply damage to the victim from an attacker.
505 * If the victim dies as a result, give points to 'credit',
506 */
507 void
checkdam(PLAYER * victim,PLAYER * attacker,IDENT * credit,int damage,char shot_type)508 checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
509 char shot_type)
510 {
511 char *cp;
512 int y;
513
514 /* Don't do anything if the victim is already in the throes of death */
515 if (victim->p_death[0] != '\0')
516 return;
517
518 /* Weaken slime attacks by 0.5 * number of boots the victim has on: */
519 if (shot_type == SLIME)
520 switch (victim->p_nboots) {
521 default:
522 break;
523 case 1:
524 damage = (damage + 1) / 2;
525 break;
526 case 2:
527 if (attacker != NULL)
528 message(attacker, "He has boots on!");
529 return;
530 }
531
532 /* The victim sustains some damage: */
533 victim->p_damage += damage;
534
535 /* Check if the victim survives the hit: */
536 if (victim->p_damage <= victim->p_damcap) {
537 /* They survive. */
538 outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
539 victim->p_damage);
540 return;
541 }
542
543 /* Describe how the victim died: */
544 switch (shot_type) {
545 default:
546 cp = "Killed";
547 break;
548 case FALL:
549 cp = "Killed on impact";
550 break;
551 case KNIFE:
552 cp = "Stabbed to death";
553 victim->p_ammo = 0; /* No exploding */
554 break;
555 case SHOT:
556 cp = "Shot to death";
557 break;
558 case GRENADE:
559 case SATCHEL:
560 case BOMB:
561 cp = "Bombed";
562 break;
563 case MINE:
564 case GMINE:
565 cp = "Blown apart";
566 break;
567 case SLIME:
568 cp = "Slimed";
569 if (credit != NULL)
570 credit->i_slime++;
571 break;
572 case LAVA:
573 cp = "Baked";
574 break;
575 case DSHOT:
576 cp = "Eliminated";
577 break;
578 }
579
580 if (credit == NULL) {
581 char *blame;
582
583 /*
584 * Nobody is taking the credit for the kill.
585 * Attribute it to either a mine or 'act of God'.
586 */
587 switch (shot_type) {
588 case MINE:
589 case GMINE:
590 blame = "a mine";
591 break;
592 default:
593 blame = "act of God";
594 break;
595 }
596
597 /* Set the death message: */
598 (void) snprintf(victim->p_death, sizeof victim->p_death,
599 "| %s by %s |", cp, blame);
600
601 /* No further score crediting needed. */
602 return;
603 }
604
605 /* Set the death message: */
606 (void) snprintf(victim->p_death, sizeof victim->p_death,
607 "| %s by %s |", cp, credit->i_name);
608
609 if (victim == attacker) {
610 /* No use killing yourself. */
611 credit->i_kills--;
612 credit->i_bkills++;
613 }
614 else if (victim->p_ident->i_team == ' '
615 || victim->p_ident->i_team != credit->i_team) {
616 /* A cross-team kill: */
617 credit->i_kills++;
618 credit->i_gkills++;
619 }
620 else {
621 /* They killed someone on the same team: */
622 credit->i_kills--;
623 credit->i_bkills++;
624 }
625
626 /* Compute the new credited score: */
627 credit->i_score = credit->i_kills / (double) credit->i_entries;
628
629 /* The victim accrues one death: */
630 victim->p_ident->i_deaths++;
631
632 /* Account for 'Stillborn' deaths */
633 if (victim->p_nchar == 0)
634 victim->p_ident->i_stillb++;
635
636 if (attacker) {
637 /* Give the attacker player a bit more strength */
638 attacker->p_damcap += conf_killgain;
639 attacker->p_damage -= conf_killgain;
640 if (attacker->p_damage < 0)
641 attacker->p_damage = 0;
642
643 /* Tell the attacker his new strength: */
644 outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
645 attacker->p_damage, attacker->p_damcap);
646
647 /* Tell the attacker his new 'kill count': */
648 outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
649 (attacker->p_damcap - conf_maxdam) / 2);
650
651 /* Update the attacker's score for everyone else */
652 y = STAT_PLAY_ROW + 1 + (attacker - Player);
653 outyx(ALL_PLAYERS, y, STAT_NAME_COL,
654 "%5.2f", attacker->p_ident->i_score);
655 }
656 }
657
658 /*
659 * zap:
660 * Kill off a player and take them out of the game.
661 * The 'was_player' flag indicates that the player was not
662 * a monitor and needs extra cleaning up.
663 */
664 static void
zap(PLAYER * pp,FLAG was_player)665 zap(PLAYER *pp, FLAG was_player)
666 {
667 int len;
668 BULLET *bp;
669 PLAYER *np;
670 int x, y;
671 int savefd;
672
673 if (was_player) {
674 /* If they died from a shot, clean up shrapnel */
675 if (pp->p_undershot)
676 fixshots(pp->p_y, pp->p_x, pp->p_over);
677 /* Let the player see their last position: */
678 drawplayer(pp, FALSE);
679 /* Remove from game: */
680 Nplayer--;
681 }
682
683 /* Display the cause of death in the centre of the screen: */
684 len = strlen(pp->p_death);
685 x = (WIDTH - len) / 2;
686 outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
687
688 /* Put some horizontal lines around and below the death message: */
689 memset(pp->p_death + 1, '-', len - 2);
690 pp->p_death[0] = '+';
691 pp->p_death[len - 1] = '+';
692 outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
693 outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
694
695 /* Move to bottom left */
696 cgoto(pp, HEIGHT, 0);
697
698 savefd = pp->p_fd;
699
700 if (was_player) {
701 int expl_charge;
702 int expl_type;
703 int ammo_exploding;
704
705 /* Check all the bullets: */
706 for (bp = Bullets; bp != NULL; bp = bp->b_next) {
707 if (bp->b_owner == pp)
708 /* Zapped players can't own bullets: */
709 bp->b_owner = NULL;
710 if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
711 /* Bullets over the player are now over air: */
712 bp->b_over = SPACE;
713 }
714
715 /* Explode a random fraction of the player's ammo: */
716 ammo_exploding = rand_num(pp->p_ammo);
717
718 /* Determine the type and amount of detonation: */
719 expl_charge = rand_num(ammo_exploding + 1);
720 if (pp->p_ammo == 0)
721 /* Ignore the no-ammo case: */
722 expl_charge = 0;
723 else if (ammo_exploding >= pp->p_ammo - 1) {
724 /* Maximal explosions always appear as slime: */
725 expl_charge = pp->p_ammo;
726 expl_type = SLIME;
727 } else {
728 /*
729 * Figure out the best effective explosion
730 * type to use, given the amount of charge
731 */
732 int btype, stype;
733 for (btype = MAXBOMB - 1; btype > 0; btype--)
734 if (expl_charge >= shot_req[btype])
735 break;
736 for (stype = MAXSLIME - 1; stype > 0; stype--)
737 if (expl_charge >= slime_req[stype])
738 break;
739 /* Pick the larger of the bomb or slime: */
740 if (btype >= 0 && stype >= 0) {
741 if (shot_req[btype] > slime_req[stype])
742 btype = -1;
743 }
744 if (btype >= 0) {
745 expl_type = shot_type[btype];
746 expl_charge = shot_req[btype];
747 } else
748 expl_type = SLIME;
749 }
750
751 if (expl_charge > 0) {
752 char buf[BUFSIZ];
753
754 /* Detonate: */
755 (void) add_shot(expl_type, pp->p_y, pp->p_x,
756 pp->p_face, expl_charge, (PLAYER *) NULL,
757 TRUE, SPACE);
758
759 /* Explain what the explosion is about. */
760 snprintf(buf, sizeof buf, "%s detonated.",
761 pp->p_ident->i_name);
762 message(ALL_PLAYERS, buf);
763
764 while (pp->p_nboots-- > 0) {
765 /* Throw one of the boots away: */
766 for (np = Boot; np < &Boot[NBOOTS]; np++)
767 if (np->p_flying < 0)
768 break;
769 #ifdef DIAGNOSTIC
770 if (np >= &Boot[NBOOTS])
771 err(1, "Too many boots");
772 #endif
773 /* Start the boots from where the player is */
774 np->p_undershot = FALSE;
775 np->p_x = pp->p_x;
776 np->p_y = pp->p_y;
777 /* Throw for up to 20 steps */
778 np->p_flying = rand_num(20);
779 np->p_flyx = 2 * rand_num(6) - 5;
780 np->p_flyy = 2 * rand_num(6) - 5;
781 np->p_over = SPACE;
782 np->p_face = BOOT;
783 showexpl(np->p_y, np->p_x, BOOT);
784 }
785 }
786 /* No explosion. Leave the player's boots behind. */
787 else if (pp->p_nboots > 0) {
788 if (pp->p_nboots == 2)
789 Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
790 else
791 Maze[pp->p_y][pp->p_x] = BOOT;
792 if (pp->p_undershot)
793 fixshots(pp->p_y, pp->p_x,
794 Maze[pp->p_y][pp->p_x]);
795 }
796
797 /* Any unexploded ammo builds up in the volcano: */
798 volcano += pp->p_ammo - expl_charge;
799
800 /* Volcano eruption: */
801 if (conf_volcano && rand_num(100) < volcano /
802 conf_volcano_max) {
803 /* Erupt near the middle of the map */
804 do {
805 x = rand_num(WIDTH / 2) + WIDTH / 4;
806 y = rand_num(HEIGHT / 2) + HEIGHT / 4;
807 } while (Maze[y][x] != SPACE);
808
809 /* Convert volcano charge into lava: */
810 (void) add_shot(LAVA, y, x, LEFTS, volcano,
811 (PLAYER *) NULL, TRUE, SPACE);
812 volcano = 0;
813
814 /* Tell eveyone what's happening */
815 message(ALL_PLAYERS, "Volcano eruption.");
816 }
817
818 /* Drone: */
819 if (conf_drone && rand_num(100) < 2) {
820 /* Find a starting place near the middle of the map: */
821 do {
822 x = rand_num(WIDTH / 2) + WIDTH / 4;
823 y = rand_num(HEIGHT / 2) + HEIGHT / 4;
824 } while (Maze[y][x] != SPACE);
825
826 /* Start the drone going: */
827 add_shot(DSHOT, y, x, rand_dir(),
828 shot_req[conf_mindshot +
829 rand_num(MAXBOMB - conf_mindshot)],
830 (PLAYER *) NULL, FALSE, SPACE);
831 }
832
833 /* Tell the zapped player's client to shut down. */
834 sendcom(pp, ENDWIN, ' ');
835 (void) fclose(pp->p_output);
836
837 /* Close up the gap in the Player array: */
838 End_player--;
839 if (pp != End_player) {
840 /* Move the last player into the gap: */
841 memcpy(pp, End_player, sizeof *pp);
842 outyx(ALL_PLAYERS,
843 STAT_PLAY_ROW + 1 + (pp - Player),
844 STAT_NAME_COL,
845 "%5.2f%c%-10.10s %c",
846 pp->p_ident->i_score, stat_char(pp),
847 pp->p_ident->i_name, pp->p_ident->i_team);
848 }
849
850 /* Erase the last player from the display: */
851 cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
852 ce(ALL_PLAYERS);
853 }
854 else {
855 /* Zap a monitor */
856
857 /* Close the session: */
858 sendcom(pp, ENDWIN, LAST_PLAYER);
859 (void) fclose(pp->p_output);
860
861 /* shuffle the monitor table */
862 End_monitor--;
863 if (pp != End_monitor) {
864 memcpy(pp, End_monitor, sizeof *pp);
865 outyx(ALL_PLAYERS,
866 STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
867 "%5.5s %-10.10s %c", " ",
868 pp->p_ident->i_name, pp->p_ident->i_team);
869 }
870
871 /* Erase the last monitor in the list */
872 cgoto(ALL_PLAYERS,
873 STAT_MON_ROW + 1 + (End_monitor - Monitor),
874 STAT_NAME_COL);
875 ce(ALL_PLAYERS);
876 }
877
878 /* Update the file descriptor sets used by select: */
879 FD_CLR(savefd, &Fds_mask);
880 if (Num_fds == savefd + 1) {
881 Num_fds = Socket;
882 if (Server_socket > Socket)
883 Num_fds = Server_socket;
884 for (np = Player; np < End_player; np++)
885 if (np->p_fd > Num_fds)
886 Num_fds = np->p_fd;
887 for (np = Monitor; np < End_monitor; np++)
888 if (np->p_fd > Num_fds)
889 Num_fds = np->p_fd;
890 Num_fds++;
891 }
892 }
893
894 /*
895 * rand_num:
896 * Return a random number in a given range.
897 */
898 int
rand_num(int range)899 rand_num(int range)
900 {
901 return (arc4random_uniform(range));
902 }
903
904 /*
905 * havechar:
906 * Check to see if we have any characters in the input queue; if
907 * we do, read them, stash them away, and return TRUE; else return
908 * FALSE.
909 */
910 static int
havechar(PLAYER * pp)911 havechar(PLAYER *pp)
912 {
913 int ret;
914
915 /* Do we already have characters? */
916 if (pp->p_ncount < pp->p_nchar)
917 return TRUE;
918 /* Ignore if nothing to read. */
919 if (!FD_ISSET(pp->p_fd, &Have_inp))
920 return FALSE;
921 /* Remove the player from the read set until we have drained them: */
922 FD_CLR(pp->p_fd, &Have_inp);
923
924 /* Suck their keypresses into a buffer: */
925 check_again:
926 errno = 0;
927 ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
928 if (ret == -1) {
929 if (errno == EINTR)
930 goto check_again;
931 if (errno == EAGAIN) {
932 #ifdef DEBUG
933 warn("Have_inp is wrong for %d", pp->p_fd);
934 #endif
935 return FALSE;
936 }
937 logit(LOG_INFO, "read");
938 }
939 if (ret > 0) {
940 /* Got some data */
941 pp->p_nchar = ret;
942 } else {
943 /* Connection was lost/closed: */
944 pp->p_cbuf[0] = 'q';
945 pp->p_nchar = 1;
946 }
947 /* Reset pointer into read buffer */
948 pp->p_ncount = 0;
949 return TRUE;
950 }
951
952 /*
953 * cleanup:
954 * Exit with the given value, cleaning up any droppings lying around
955 */
956 void
cleanup(int eval)957 cleanup(int eval)
958 {
959 PLAYER *pp;
960
961 /* Place their cursor in a friendly position: */
962 cgoto(ALL_PLAYERS, HEIGHT, 0);
963
964 /* Send them all the ENDWIN command: */
965 sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
966
967 /* And close their connections: */
968 for (pp = Player; pp < End_player; pp++)
969 (void) fclose(pp->p_output);
970 for (pp = Monitor; pp < End_monitor; pp++)
971 (void) fclose(pp->p_output);
972
973 /* Close the server socket: */
974 (void) close(Socket);
975
976 /* The end: */
977 logx(LOG_INFO, "game over");
978 exit(eval);
979 }
980
981 /*
982 * send_stats:
983 * Accept a connection to the statistics port, and emit
984 * the stats.
985 */
986 static void
send_stats(void)987 send_stats(void)
988 {
989 FILE *fp;
990 int s;
991 struct sockaddr_in sockstruct;
992 socklen_t socklen;
993
994 /* Accept a connection to the statistics socket: */
995 socklen = sizeof sockstruct;
996 s = accept4(Status, (struct sockaddr *) &sockstruct, &socklen,
997 SOCK_NONBLOCK);
998 if (s < 0) {
999 if (errno == EINTR)
1000 return;
1001 logx(LOG_ERR, "accept");
1002 return;
1003 }
1004
1005 fp = fdopen(s, "w");
1006 if (fp == NULL) {
1007 logit(LOG_ERR, "fdopen");
1008 (void) close(s);
1009 return;
1010 }
1011
1012 print_stats(fp);
1013
1014 (void) fclose(fp);
1015 }
1016
1017 /*
1018 * print_stats:
1019 * emit the game statistics
1020 */
1021 void
print_stats(FILE * fp)1022 print_stats(FILE *fp)
1023 {
1024 IDENT *ip;
1025 PLAYER *pp;
1026
1027 /* Send the statistics as raw text down the socket: */
1028 fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
1029 for (ip = Scores; ip != NULL; ip = ip->i_next) {
1030 fprintf(fp, "%s%c%c%c\t", ip->i_name,
1031 ip->i_team == ' ' ? ' ' : '[',
1032 ip->i_team,
1033 ip->i_team == ' ' ? ' ' : ']'
1034 );
1035 if (strlen(ip->i_name) + 3 < 8)
1036 putc('\t', fp);
1037 fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1038 ip->i_score, ip->i_ducked, ip->i_absorbed,
1039 ip->i_faced, ip->i_shot, ip->i_robbed,
1040 ip->i_missed, ip->i_slime);
1041 }
1042 fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1043 for (ip = Scores; ip != NULL; ip = ip->i_next) {
1044 fprintf(fp, "%s%c%c%c\t", ip->i_name,
1045 ip->i_team == ' ' ? ' ' : '[',
1046 ip->i_team,
1047 ip->i_team == ' ' ? ' ' : ']'
1048 );
1049 if (strlen(ip->i_name) + 3 < 8)
1050 putc('\t', fp);
1051 fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1052 ip->i_gkills, ip->i_bkills, ip->i_deaths,
1053 ip->i_stillb, ip->i_saved);
1054 for (pp = Player; pp < End_player; pp++)
1055 if (pp->p_ident == ip)
1056 putc('p', fp);
1057 for (pp = Monitor; pp < End_monitor; pp++)
1058 if (pp->p_ident == ip)
1059 putc('m', fp);
1060 putc('\n', fp);
1061 }
1062 }
1063
1064
1065 /*
1066 * Send the game statistics to the controlling tty
1067 */
1068 static void
siginfo(int sig)1069 siginfo(int sig)
1070 {
1071 int tty;
1072 FILE *fp;
1073
1074 if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1075 fp = fdopen(tty, "w");
1076 print_stats(fp);
1077 answer_info(fp);
1078 fclose(fp);
1079 }
1080 }
1081
1082 /*
1083 * clear_scores:
1084 * Clear the Scores list.
1085 */
1086 static void
clear_scores(void)1087 clear_scores(void)
1088 {
1089 IDENT *ip, *nextip;
1090
1091 /* Release the list of scores: */
1092 for (ip = Scores; ip != NULL; ip = nextip) {
1093 nextip = ip->i_next;
1094 free((char *) ip);
1095 }
1096 Scores = NULL;
1097 }
1098
1099 /*
1100 * announce_game:
1101 * Publically announce the game
1102 */
1103 static void
announce_game(void)1104 announce_game(void)
1105 {
1106
1107 /* TODO: could use system() to do something user-configurable */
1108 }
1109
1110 /*
1111 * Handle a UDP packet sent to the well known port.
1112 */
1113 static void
handle_wkport(int fd)1114 handle_wkport(int fd)
1115 {
1116 struct sockaddr fromaddr;
1117 socklen_t fromlen;
1118 u_int16_t query;
1119 u_int16_t response;
1120
1121 fromlen = sizeof fromaddr;
1122 if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1123 {
1124 logit(LOG_WARNING, "recvfrom");
1125 return;
1126 }
1127
1128 #ifdef DEBUG
1129 fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1130 query == C_MESSAGE ? "C_MESSAGE" :
1131 query == C_SCORES ? "C_SCORES" :
1132 query == C_PLAYER ? "C_PLAYER" :
1133 query == C_MONITOR ? "C_MONITOR" : "?",
1134 inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1135 ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1136 #endif
1137
1138 query = ntohs(query);
1139
1140 switch (query) {
1141 case C_MESSAGE:
1142 if (Nplayer <= 0)
1143 /* Don't bother replying if nobody to talk to: */
1144 return;
1145 /* Return the number of people playing: */
1146 response = Nplayer;
1147 break;
1148 case C_SCORES:
1149 /* Someone wants the statistics port: */
1150 response = stat_port;
1151 break;
1152 case C_PLAYER:
1153 case C_MONITOR:
1154 /* Someone wants to play or watch: */
1155 if (query == C_MONITOR && Nplayer <= 0)
1156 /* Don't bother replying if there's nothing to watch: */
1157 return;
1158 /* Otherwise, tell them how to get to the game: */
1159 response = sock_port;
1160 break;
1161 default:
1162 logit(LOG_INFO, "unknown udp query %d", query);
1163 return;
1164 }
1165
1166 response = ntohs(response);
1167 if (sendto(fd, &response, sizeof response, 0,
1168 &fromaddr, sizeof fromaddr) == -1)
1169 logit(LOG_WARNING, "sendto");
1170 }
1171