Ansify (silence -Wold-style-definition)
[dragonfly.git] / games / hunt / hunt / hunt.c
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  * + Redistributions of source code must retain the above copyright 
10  *   notice, this list of conditions and the following disclaimer.
11  * + 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  * + 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: hunt.c,v 1.13 2008/03/17 09:17:56 sobrado Exp $
32  * $NetBSD: hunt.c,v 1.8 1998/09/13 15:27:28 hubertf Exp $
33  * $DragonFly: src/games/hunt/hunt/hunt.c,v 1.2 2008/09/04 16:12:51 swildner Exp $
34  */
35
36 #include <ctype.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <curses.h>
40 #include <signal.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <netdb.h>
45
46 #include <sys/stat.h>
47 #include <sys/time.h>
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <sys/ioctl.h>
51 #include <sys/sockio.h>
52
53 #include <netinet/in.h>
54 #include <net/if.h>
55
56 #include <arpa/inet.h>
57
58 #include "hunt.h"
59 #include "display.h"
60 #include "client.h"
61 #include "list.h"
62
63 #ifndef __GNUC__
64 #define __attribute__(x)
65 #endif
66
67 FLAG    Am_monitor = FALSE;
68 int     Socket;
69 char    map_key[256];                   /* what to map keys to */
70 FLAG    no_beep = FALSE;
71 char    *Send_message = NULL;
72
73 static char     *Sock_host;
74 static char     *use_port;
75 static FLAG     Query_driver = FALSE;
76 static FLAG     Show_scores = FALSE;
77 static struct sockaddr  Daemon;
78
79
80 static char     name[NAMELEN];
81 static char     team = '-';
82
83 static int      in_visual;
84
85 static void     dump_scores(void);
86 static long     env_init(long);
87 static void     fill_in_blanks(void);
88 static void     leave(int, const char *) __attribute__((__noreturn__));
89 static void     sigterm(int);
90 static int      find_driver(void);
91
92 /*
93  * main:
94  *      Main program for local process
95  */
96 int
97 main(int ac, char **av)
98 {
99         int             c;
100         long            enter_status;
101         int             option;
102         struct servent  *se;
103
104         enter_status = env_init((long) Q_CLOAK);
105         while ((c = getopt(ac, av, "Sbcfh:l:mn:op:qst:w:")) != -1) {
106                 switch (c) {
107                 case 'l':       /* rsh compatibility */
108                 case 'n':
109                         (void) strlcpy(name, optarg, sizeof name);
110                         break;
111                 case 't':
112                         team = *optarg;
113                         if (!isdigit(team) && team != ' ') {
114                                 warnx("Team names must be numeric or space");
115                                 team = '-';
116                         }
117                         break;
118                 case 'o':
119                         Otto_mode = TRUE;
120                         break;
121                 case 'm':
122                         Am_monitor = TRUE;
123                         break;
124                 case 'S':
125                         Show_scores = TRUE;
126                         break;
127                 case 'q':       /* query whether hunt is running */
128                         Query_driver = TRUE;
129                         break;
130                 case 'w':
131                         Send_message = optarg;
132                         break;
133                 case 'h':
134                         Sock_host = optarg;
135                         break;
136                 case 'p':
137                         use_port = optarg;
138                         Server_port = atoi(use_port);
139                         break;
140                 case 'c':
141                         enter_status = Q_CLOAK;
142                         break;
143                 case 'f':
144                         enter_status = Q_FLY;
145                         break;
146                 case 's':
147                         enter_status = Q_SCAN;
148                         break;
149                 case 'b':
150                         no_beep = !no_beep;
151                         break;
152                 default:
153                 usage:
154                         fputs("usage: hunt [-bcfmqSs] [-n name] [-p port] "
155                             "[-t team] [-w message] [[-h] host]\n",
156                             stderr);
157                         exit(1);
158                 }
159         }
160         if (optind + 1 < ac)
161                 goto usage;
162         else if (optind + 1 == ac)
163                 Sock_host = av[ac - 1];
164
165         if (Server_port == 0) {
166                 se = getservbyname("hunt", "udp");
167                 if (se != NULL)
168                         Server_port = ntohs(se->s_port);
169                 else
170                         Server_port = HUNT_PORT;
171         }
172
173         if (Show_scores) {
174                 dump_scores();
175                 exit(0);
176         }
177
178         if (Query_driver) {
179                 struct driver           *driver;
180
181                 probe_drivers(C_MESSAGE, Sock_host);
182                 while ((driver = next_driver()) != NULL) {
183                         printf("%d player%s hunting on %s!\n",
184                             driver->response,
185                             (driver->response == 1) ? "" : "s",
186                             driver_name(driver));
187                         if (Sock_host)
188                                 break;
189                 }
190                 exit(0);
191         }
192         if (Otto_mode) {
193                 if (Am_monitor)
194                         errx(1, "otto mode incompatible with monitor mode");
195                 (void) strlcpy(name, "otto", sizeof name);
196                 team = ' ';
197         } else
198                 fill_in_blanks();
199
200         (void) fflush(stdout);
201         display_open();
202         in_visual = TRUE;
203         if (LINES < SCREEN_HEIGHT || COLS < SCREEN_WIDTH) {
204                 errno = 0;
205                 leave(1, "Need a larger window");
206         }
207         display_clear_the_screen();
208         (void) signal(SIGINT, intr);
209         (void) signal(SIGTERM, sigterm);
210         /* (void) signal(SIGPIPE, SIG_IGN); */
211
212         Daemon.sa_len = 0;
213     ask_driver:
214         while (!find_driver()) {
215                 if (Am_monitor) {
216                         errno = 0;
217                         leave(1, "No one playing");
218                 }
219
220                 if (Sock_host == NULL) {
221                         errno = 0;
222                         leave(1, "huntd not running");
223                 }
224
225                 sleep(3);
226         }
227         Socket = -1;
228
229         for (;;) {
230                 if (Socket != -1)
231                         close(Socket);
232
233                 Socket = socket(Daemon.sa_family, SOCK_STREAM, 0);
234                 if (Socket < 0)
235                         leave(1, "socket");
236
237                 option = 1;
238                 if (setsockopt(Socket, SOL_SOCKET, SO_USELOOPBACK,
239                     &option, sizeof option) < 0)
240                         warn("setsockopt loopback");
241
242                 errno = 0;
243                 if (connect(Socket, &Daemon, Daemon.sa_len) == -1)  {
244                         if (errno == ECONNREFUSED)
245                                 goto ask_driver;
246                         leave(1, "connect");
247                 }
248
249                 do_connect(name, team, enter_status);
250                 if (Send_message != NULL) {
251                         do_message();
252                         if (enter_status == Q_MESSAGE)
253                                 break;
254                         Send_message = NULL;
255                         continue;
256                 }
257                 playit();
258                 if ((enter_status = quit(enter_status)) == Q_QUIT)
259                         break;
260         }
261         leave(0, (char *) NULL);
262         /* NOTREACHED */
263         return(0);
264 }
265
266 /*
267  * Set Daemon to be the address of a hunt driver, or return 0 on failure.
268  *
269  * We start quietly probing for drivers. As soon as one driver is found
270  * we show it in the list. If we run out of drivers and we only have one
271  * then we choose it. Otherwise we present a list of the found drivers.
272  */
273 static int
274 find_driver(void)
275 {
276         int last_driver, numdrivers, waiting, is_current;
277         struct driver *driver;
278         int c;
279         char buf[80];
280         const char *xname;
281
282         probe_drivers(Am_monitor ? C_MONITOR : C_PLAYER, Sock_host);
283
284         last_driver = -1;
285         numdrivers = 0;
286         waiting = 1;
287         for (;;) {
288                 if (numdrivers == 0) {
289                         /* Silently wait for at least one driver */
290                         driver = next_driver();
291                 } else if (!waiting || (driver = 
292                     next_driver_fd(STDIN_FILENO)) == (struct driver *)-1) {
293                         /* We have a key waiting, or no drivers left */
294                         c = getchar();
295                         if (c == '\r' || c == '\n' || c == ' ') {
296                                 if (numdrivers == 1)
297                                         c = 'a';
298                                 else if (last_driver != -1)
299                                         c = 'a' + last_driver;
300                         }
301                         if (c < 'a' || c >= numdrivers + 'a') {
302                                 display_beep();
303                                 continue;
304                         }
305                         driver = &drivers[c - 'a'];
306                         break;
307                 }
308
309                 if (driver == NULL) {
310                         waiting = 0;
311                         if (numdrivers == 0) {
312                                 probe_cleanup();
313                                 return 0;       /* Failure */
314                         }
315                         if (numdrivers == 1) {
316                                 driver = &drivers[0];
317                                 break;
318                         }
319                         continue;
320                 }
321
322                 /* Use the preferred host straight away. */
323                 if (Sock_host)
324                         break;
325
326                 if (numdrivers == 0) {
327                         display_clear_the_screen();
328                         display_move(1, 0);
329                         display_put_str("Pick one:");
330                 }
331
332                 /* Mark the last driver we used with an asterisk */
333                 is_current = (last_driver == -1 && Daemon.sa_len != 0 && 
334                     memcmp(&Daemon, &driver->addr, Daemon.sa_len) == 0);
335                 if (is_current)
336                         last_driver = numdrivers;
337
338                 /* Display it in the list if there is room */
339                 if (numdrivers < HEIGHT - 3) {
340                         xname = driver_name(driver);
341                         display_move(3 + numdrivers, 0);
342                         snprintf(buf, sizeof buf, "%6c %c    %s", 
343                             is_current ? '*' : ' ', 'a' + numdrivers, xname);
344                         display_put_str(buf);
345                 }
346
347                 /* Clear the last 'Enter letter' line if any */
348                 display_move(4 + numdrivers, 0);
349                 display_clear_eol();
350
351                 if (last_driver != -1)
352                         snprintf(buf, sizeof buf, "Enter letter [%c]: ", 
353                             'a' + last_driver);
354                 else
355                         snprintf(buf, sizeof buf, "Enter letter: ");
356
357                 display_move(5 + numdrivers, 0);
358                 display_put_str(buf);
359                 display_refresh();
360
361                 numdrivers++;
362         }
363
364         display_clear_the_screen();
365         Daemon = driver->addr;
366
367         probe_cleanup();
368         return 1;               /* Success */
369 }
370
371 static void
372 dump_scores(void)
373 {
374         struct  driver *driver;
375         int     s, cnt, i;
376         char    buf[1024];
377
378         probe_drivers(C_SCORES, Sock_host);
379         while ((driver = next_driver()) != NULL) {
380                 printf("\n%s:\n", driver_name(driver));
381                 fflush(stdout);
382
383                 if ((s = socket(driver->addr.sa_family, SOCK_STREAM, 0)) < 0) {
384                         warn("socket");
385                         continue;
386                 }
387                 if (connect(s, &driver->addr, driver->addr.sa_len) < 0) {
388                         warn("connect");
389                         close(s);
390                         continue;
391                 }
392                 while ((cnt = read(s, buf, sizeof buf)) > 0) {
393                         /* Whittle out bad characters */
394                         for (i = 0; i < cnt; i++)
395                                 if ((buf[i] < ' ' || buf[i] > '~') &&
396                                     buf[i] != '\n' && buf[i] != '\t')
397                                         buf[i] = '?';
398                         fwrite(buf, cnt, 1, stdout);
399                 }
400                 if (cnt < 0)
401                         warn("read");
402                 (void)close(s);
403                 if (Sock_host)
404                         break;
405         }
406         probe_cleanup();
407 }
408
409
410 /*
411  * bad_con:
412  *      We had a bad connection.  For the moment we assume that this
413  *      means the game is full.
414  */
415 void
416 bad_con(void)
417 {
418         leave(1, "lost connection to huntd");
419 }
420
421 /*
422  * bad_ver:
423  *      version number mismatch.
424  */
425 void
426 bad_ver(void)
427 {
428         errno = 0;
429         leave(1, "Version number mismatch. No go.");
430 }
431
432 /*
433  * sigterm:
434  *      Handle a terminate signal
435  */
436 static void
437 sigterm(int signo __unused)
438 {
439         leave(0, (char *) NULL);
440 }
441
442 /*
443  * rmnl:
444  *      Remove a '\n' at the end of a string if there is one
445  */
446 static void
447 rmnl(char *s)
448 {
449         char    *cp;
450
451         cp = strrchr(s, '\n');
452         if (cp != NULL)
453                 *cp = '\0';
454 }
455
456 /*
457  * intr:
458  *      Handle a interrupt signal
459  */
460 void
461 intr(int dummy __unused)
462 {
463         int     ch;
464         int     explained;
465         int     y, x;
466
467         (void) signal(SIGINT, SIG_IGN);
468         display_getyx(&y, &x);
469         display_move(HEIGHT, 0);
470         display_put_str("Really quit? ");
471         display_clear_eol();
472         display_refresh();
473         explained = FALSE;
474         for (;;) {
475                 ch = getchar();
476                 if (isupper(ch))
477                         ch = tolower(ch);
478                 if (ch == 'y') {
479                         if (Socket != 0) {
480                                 (void) write(Socket, "q", 1);
481                                 (void) close(Socket);
482                         }
483                         leave(0, (char *) NULL);
484                 }
485                 else if (ch == 'n') {
486                         (void) signal(SIGINT, intr);
487                         display_move(y, x);
488                         display_refresh();
489                         return;
490                 }
491                 if (!explained) {
492                         display_put_str("(Yes or No) ");
493                         display_refresh();
494                         explained = TRUE;
495                 }
496                 display_beep();
497                 display_refresh();
498         }
499 }
500
501 /*
502  * leave:
503  *      Leave the game somewhat gracefully, restoring all current
504  *      tty stats.
505  */
506 static void
507 leave(int eval, const char *mesg)
508 {
509         int saved_errno;
510
511         saved_errno = errno;
512         if (in_visual) {
513                 display_move(HEIGHT, 0);
514                 display_refresh();
515                 display_end();
516         }
517         errno = saved_errno;
518
519         if (errno == 0 && mesg != NULL)
520                 errx(eval, mesg);
521         else if (mesg != NULL)
522                 err(eval, mesg);
523         exit(eval);
524 }
525
526 /*
527  * env_init:
528  *      initialise game parameters from the HUNT envvar
529  */
530 static long
531 env_init(long enter_status)
532 {
533         int     i;
534         char    *envp, *envname, *s;
535
536         /* Map all keys to themselves: */
537         for (i = 0; i < 256; i++)
538                 map_key[i] = (char) i;
539
540         envname = NULL;
541         if ((envp = getenv("HUNT")) != NULL) {
542                 while ((s = strpbrk(envp, "=,")) != NULL) {
543                         if (strncmp(envp, "cloak,", s - envp + 1) == 0) {
544                                 enter_status = Q_CLOAK;
545                                 envp = s + 1;
546                         }
547                         else if (strncmp(envp, "scan,", s - envp + 1) == 0) {
548                                 enter_status = Q_SCAN;
549                                 envp = s + 1;
550                         }
551                         else if (strncmp(envp, "fly,", s - envp + 1) == 0) {
552                                 enter_status = Q_FLY;
553                                 envp = s + 1;
554                         }
555                         else if (strncmp(envp, "nobeep,", s - envp + 1) == 0) {
556                                 no_beep = TRUE;
557                                 envp = s + 1;
558                         }
559                         else if (strncmp(envp, "name=", s - envp + 1) == 0) {
560                                 envname = s + 1;
561                                 if ((s = strchr(envp, ',')) == NULL) {
562                                         *envp = '\0';
563                                         strlcpy(name, envname, sizeof name);
564                                         break;
565                                 }
566                                 *s = '\0';
567                                 strlcpy(name, envname, sizeof name);
568                                 envp = s + 1;
569                         }
570                         else if (strncmp(envp, "port=", s - envp + 1) == 0) {
571                                 use_port = s + 1;
572                                 Server_port = atoi(use_port);
573                                 if ((s = strchr(envp, ',')) == NULL) {
574                                         *envp = '\0';
575                                         break;
576                                 }
577                                 *s = '\0';
578                                 envp = s + 1;
579                         }
580                         else if (strncmp(envp, "host=", s - envp + 1) == 0) {
581                                 Sock_host = s + 1;
582                                 if ((s = strchr(envp, ',')) == NULL) {
583                                         *envp = '\0';
584                                         break;
585                                 }
586                                 *s = '\0';
587                                 envp = s + 1;
588                         }
589                         else if (strncmp(envp, "message=", s - envp + 1) == 0) {
590                                 Send_message = s + 1;
591                                 if ((s = strchr(envp, ',')) == NULL) {
592                                         *envp = '\0';
593                                         break;
594                                 }
595                                 *s = '\0';
596                                 envp = s + 1;
597                         }
598                         else if (strncmp(envp, "team=", s - envp + 1) == 0) {
599                                 team = *(s + 1);
600                                 if (!isdigit(team))
601                                         team = ' ';
602                                 if ((s = strchr(envp, ',')) == NULL) {
603                                         *envp = '\0';
604                                         break;
605                                 }
606                                 *s = '\0';
607                                 envp = s + 1;
608                         }                       /* must be last option */
609                         else if (strncmp(envp, "mapkey=", s - envp + 1) == 0) {
610                                 for (s = s + 1; *s != '\0'; s += 2) {
611                                         map_key[(unsigned int) *s] = *(s + 1);
612                                         if (*(s + 1) == '\0') {
613                                                 break;
614                                         }
615                                 }
616                                 *envp = '\0';
617                                 break;
618                         } else {
619                                 *s = '\0';
620                                 printf("unknown option %s\n", envp);
621                                 if ((s = strchr(envp, ',')) == NULL) {
622                                         *envp = '\0';
623                                         break;
624                                 }
625                                 envp = s + 1;
626                         }
627                 }
628                 if (*envp != '\0') {
629                         if (envname == NULL)
630                                 strlcpy(name, envp, sizeof name);
631                         else
632                                 printf("unknown option %s\n", envp);
633                 }
634         }
635         return enter_status;
636 }
637
638 /*
639  * fill_in_blanks:
640  *      quiz the user for the information they didn't provide earlier
641  */
642 static void
643 fill_in_blanks(void)
644 {
645         int     i;
646         char    *cp;
647
648 again:
649         if (name[0] != '\0') {
650                 printf("Entering as '%s'", name);
651                 if (team != ' ' && team != '-')
652                         printf(" on team %c.\n", team);
653                 else
654                         putchar('\n');
655         } else {
656                 printf("Enter your code name: ");
657                 if (fgets(name, sizeof name, stdin) == NULL)
658                         exit(1);
659         }
660         rmnl(name);
661         if (name[0] == '\0') {
662                 printf("You have to have a code name!\n");
663                 goto again;
664         }
665         for (cp = name; *cp != '\0'; cp++)
666                 if (!isprint(*cp)) {
667                         name[0] = '\0';
668                         printf("Illegal character in your code name.\n");
669                         goto again;
670                 }
671         if (team == '-') {
672                 printf("Enter your team (0-9 or nothing): ");
673                 i = getchar();
674                 if (isdigit(i))
675                         team = i;
676                 else if (i == '\n' || i == EOF || i == ' ')
677                         team = ' ';
678                 /* ignore trailing chars */
679                 while (i != '\n' && i != EOF)
680                         i = getchar();
681                 if (team == '-') {
682                         printf("Teams must be numeric.\n");
683                         goto again;
684                 }
685         }
686 }