xref: /netbsd-src/games/hack/hack.main.c (revision 23c8222edbfb0f0932d88a8351d3a0cf817dfb9e)
1 /*	$NetBSD: hack.main.c,v 1.9 2004/01/27 20:30:29 jsm Exp $	*/
2 
3 /*
4  * Copyright (c) 1985, Stichting Centrum voor Wiskunde en Informatica,
5  * Amsterdam
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met:
11  *
12  * - Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  *
15  * - Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in the
17  * documentation and/or other materials provided with the distribution.
18  *
19  * - Neither the name of the Stichting Centrum voor Wiskunde en
20  * Informatica, nor the names of its contributors may be used to endorse or
21  * promote products derived from this software without specific prior
22  * written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
27  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
28  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 /*
38  * Copyright (c) 1982 Jay Fenlason <hack@gnu.org>
39  * All rights reserved.
40  *
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  *    notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  *    notice, this list of conditions and the following disclaimer in the
48  *    documentation and/or other materials provided with the distribution.
49  * 3. The name of the author may not be used to endorse or promote products
50  *    derived from this software without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
53  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
54  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
55  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
56  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
57  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
58  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
59  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
60  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
61  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62  */
63 
64 #include <sys/cdefs.h>
65 #ifndef lint
66 __RCSID("$NetBSD: hack.main.c,v 1.9 2004/01/27 20:30:29 jsm Exp $");
67 #endif				/* not lint */
68 
69 #include <signal.h>
70 #include <stdlib.h>
71 #include <unistd.h>
72 #include <fcntl.h>
73 #include "hack.h"
74 #include "extern.h"
75 
76 #ifdef QUEST
77 #define	gamename	"quest"
78 #else
79 #define	gamename	"hack"
80 #endif
81 
82 int             (*afternmv)(void);
83 int             (*occupation)(void);
84 const char           *occtxt;		/* defined when occupation != NULL */
85 
86 int             hackpid;	/* current pid */
87 int             locknum;	/* max num of players */
88 #ifdef DEF_PAGER
89 const char     *catmore;	/* default pager */
90 #endif
91 char            SAVEF[PL_NSIZ + 11] = "save/";	/* save/99999player */
92 char           *hname;		/* name of the game (argv[0] of call) */
93 char            obuf[BUFSIZ];	/* BUFSIZ is defined in stdio.h */
94 
95 int main(int, char *[]);
96 static void chdirx(const char *, boolean);
97 
98 int
99 main(argc, argv)
100 	int             argc;
101 	char           *argv[];
102 {
103 	int             fd;
104 #ifdef CHDIR
105 	char           *dir;
106 #endif
107 
108 	/* Check for dirty tricks with closed fds 0, 1, 2 */
109 	fd = open("/dev/null", O_RDONLY);
110 	if (fd < 3)
111 		exit(1);
112 	close(fd);
113 
114 	hname = argv[0];
115 	hackpid = getpid();
116 
117 #ifdef CHDIR			/* otherwise no chdir() */
118 	/*
119 	 * See if we must change directory to the playground.
120 	 * (Perhaps hack runs suid and playground is inaccessible
121 	 *  for the player.)
122 	 * The environment variable HACKDIR is overridden by a
123 	 *  -d command line option (must be the first option given)
124 	 */
125 
126 	dir = getenv("HACKDIR");
127 	if (argc > 1 && !strncmp(argv[1], "-d", 2)) {
128 		argc--;
129 		argv++;
130 		dir = argv[0] + 2;
131 		if (*dir == '=' || *dir == ':')
132 			dir++;
133 		if (!*dir && argc > 1) {
134 			argc--;
135 			argv++;
136 			dir = argv[0];
137 		}
138 		if (!*dir)
139 			error("Flag -d must be followed by a directory name.");
140 	}
141 #endif
142 
143 	/*
144 	 * Who am i? Algorithm: 1. Use name as specified in HACKOPTIONS
145 	 *			2. Use $USER or $LOGNAME	(if 1. fails)
146 	 *			3. Use getlogin()		(if 2. fails)
147 	 * The resulting name is overridden by command line options.
148 	 * If everything fails, or if the resulting name is some generic
149 	 * account like "games", "play", "player", "hack" then eventually
150 	 * we'll ask him.
151 	 * Note that we trust him here; it is possible to play under
152 	 * somebody else's name.
153 	 */
154 	{
155 		char           *s;
156 
157 		initoptions();
158 		if (!*plname && (s = getenv("USER")))
159 			(void) strncpy(plname, s, sizeof(plname) - 1);
160 		if (!*plname && (s = getenv("LOGNAME")))
161 			(void) strncpy(plname, s, sizeof(plname) - 1);
162 		if (!*plname && (s = getlogin()))
163 			(void) strncpy(plname, s, sizeof(plname) - 1);
164 	}
165 
166 	/*
167 	 * Now we know the directory containing 'record' and
168 	 * may do a prscore().
169 	 */
170 	if (argc > 1 && !strncmp(argv[1], "-s", 2)) {
171 #ifdef CHDIR
172 		chdirx(dir, 0);
173 #endif
174 		prscore(argc, argv);
175 		exit(0);
176 	}
177 	/*
178 	 * It seems he really wants to play.
179 	 * Remember tty modes, to be restored on exit.
180 	 */
181 	gettty();
182 	setbuf(stdout, obuf);
183 	setrandom();
184 	startup();
185 	cls();
186 	u.uhp = 1;		/* prevent RIP on early quits */
187 	u.ux = FAR;		/* prevent nscr() */
188 	(void) signal(SIGHUP, hangup);
189 
190 	/*
191 	 * Find the creation date of this game,
192 	 * so as to avoid restoring outdated savefiles.
193 	 */
194 	gethdate(hname);
195 
196 	/*
197 	 * We cannot do chdir earlier, otherwise gethdate will fail.
198 	 */
199 #ifdef CHDIR
200 	chdirx(dir, 1);
201 #endif
202 
203 	/*
204 	 * Process options.
205 	 */
206 	while (argc > 1 && argv[1][0] == '-') {
207 		argv++;
208 		argc--;
209 		switch (argv[0][1]) {
210 #ifdef WIZARD
211 		case 'D':
212 			/* if(!strcmp(getlogin(), WIZARD)) */
213 			wizard = TRUE;
214 			/*
215 			 * else printf("Sorry.\n");
216 			 */
217 			break;
218 #endif
219 #ifdef NEWS
220 		case 'n':
221 			flags.nonews = TRUE;
222 			break;
223 #endif
224 		case 'u':
225 			if (argv[0][2])
226 				(void) strncpy(plname, argv[0] + 2, sizeof(plname) - 1);
227 			else if (argc > 1) {
228 				argc--;
229 				argv++;
230 				(void) strncpy(plname, argv[0], sizeof(plname) - 1);
231 			} else
232 				printf("Player name expected after -u\n");
233 			break;
234 		default:
235 			/* allow -T for Tourist, etc. */
236 			(void) strncpy(pl_character, argv[0] + 1,
237 				       sizeof(pl_character) - 1);
238 
239 			/* printf("Unknown option: %s\n", *argv); */
240 		}
241 	}
242 
243 	if (argc > 1)
244 		locknum = atoi(argv[1]);
245 #ifdef MAX_NR_OF_PLAYERS
246 	if (!locknum || locknum > MAX_NR_OF_PLAYERS)
247 		locknum = MAX_NR_OF_PLAYERS;
248 #endif
249 #ifdef DEF_PAGER
250 	if (((catmore = getenv("HACKPAGER")) == NULL &&
251 	    (catmore = getenv("PAGER")) == NULL) ||
252 	    catmore[0] == '\0')
253 		catmore = DEF_PAGER;
254 #endif
255 #ifdef MAIL
256 	getmailstatus();
257 #endif
258 #ifdef WIZARD
259 	if (wizard)
260 		(void) strcpy(plname, "wizard");
261 	else
262 #endif
263 		if (!*plname || !strncmp(plname, "player", 4)
264 		    || !strncmp(plname, "games", 4))
265 		askname();
266 	plnamesuffix();		/* strip suffix from name; calls askname() */
267 	/* again if suffix was whole name */
268 	/* accepts any suffix */
269 #ifdef WIZARD
270 	if (!wizard) {
271 #endif
272 		/*
273 		 * check for multiple games under the same name
274 		 * (if !locknum) or check max nr of players (otherwise)
275 		 */
276 		(void) signal(SIGQUIT, SIG_IGN);
277 		(void) signal(SIGINT, SIG_IGN);
278 		if (!locknum)
279 			(void) strcpy(lock, plname);
280 		getlock();	/* sets lock if locknum != 0 */
281 #ifdef WIZARD
282 	} else {
283 		char           *sfoo;
284 		(void) strcpy(lock, plname);
285 		if ((sfoo = getenv("MAGIC")) != NULL)
286 			while (*sfoo) {
287 				switch (*sfoo++) {
288 				case 'n':
289 					(void) srandom(*sfoo++);
290 					break;
291 				}
292 			}
293 		if ((sfoo = getenv("GENOCIDED")) != NULL) {
294 			if (*sfoo == '!') {
295 				const struct permonst *pm = mons;
296 				char           *gp = genocided;
297 
298 				while (pm < mons + CMNUM + 2) {
299 					if (!strchr(sfoo, pm->mlet))
300 						*gp++ = pm->mlet;
301 					pm++;
302 				}
303 				*gp = 0;
304 			} else
305 				(void) strcpy(genocided, sfoo);
306 			(void) strcpy(fut_geno, genocided);
307 		}
308 	}
309 #endif
310 	setftty();
311 	(void) sprintf(SAVEF, "save/%d%s", getuid(), plname);
312 	regularize(SAVEF + 5);	/* avoid . or / in name */
313 	if ((fd = open(SAVEF, O_RDONLY)) >= 0 &&
314 	    (uptodate(fd) || unlink(SAVEF) == 666)) {
315 		(void) signal(SIGINT, done1);
316 		pline("Restoring old save file...");
317 		(void) fflush(stdout);
318 		if (!dorecover(fd))
319 			goto not_recovered;
320 		pline("Hello %s, welcome to %s!", plname, gamename);
321 		flags.move = 0;
322 	} else {
323 not_recovered:
324 		fobj = fcobj = invent = 0;
325 		fmon = fallen_down = 0;
326 		ftrap = 0;
327 		fgold = 0;
328 		flags.ident = 1;
329 		init_objects();
330 		u_init();
331 
332 		(void) signal(SIGINT, done1);
333 		mklev();
334 		u.ux = xupstair;
335 		u.uy = yupstair;
336 		(void) inshop();
337 		setsee();
338 		flags.botlx = 1;
339 		makedog();
340 		{
341 			struct monst   *mtmp;
342 			if ((mtmp = m_at(u.ux, u.uy)) != NULL)
343 				mnexto(mtmp);	/* riv05!a3 */
344 		}
345 		seemons();
346 #ifdef NEWS
347 		if (flags.nonews || !readnews())
348 			/* after reading news we did docrt() already */
349 #endif
350 			docrt();
351 
352 		/* give welcome message before pickup messages */
353 		pline("Hello %s, welcome to %s!", plname, gamename);
354 
355 		pickup(1);
356 		read_engr_at(u.ux, u.uy);
357 		flags.move = 1;
358 	}
359 
360 	flags.moonphase = phase_of_the_moon();
361 	if (flags.moonphase == FULL_MOON) {
362 		pline("You are lucky! Full moon tonight.");
363 		u.uluck++;
364 	} else if (flags.moonphase == NEW_MOON) {
365 		pline("Be careful! New moon tonight.");
366 	}
367 	initrack();
368 
369 	for (;;) {
370 		if (flags.move) {	/* actual time passed */
371 
372 			settrack();
373 
374 			if (moves % 2 == 0 ||
375 			    (!(Fast & ~INTRINSIC) && (!Fast || rn2(3)))) {
376 				movemon();
377 				if (!rn2(70))
378 					(void) makemon((struct permonst *) 0, 0, 0);
379 			}
380 			if (Glib)
381 				glibr();
382 			timeout();
383 			++moves;
384 			if (flags.time)
385 				flags.botl = 1;
386 			if (u.uhp < 1) {
387 				pline("You die...");
388 				done("died");
389 			}
390 			if (u.uhp * 10 < u.uhpmax && moves - wailmsg > 50) {
391 				wailmsg = moves;
392 				if (u.uhp == 1)
393 					pline("You hear the wailing of the Banshee...");
394 				else
395 					pline("You hear the howling of the CwnAnnwn...");
396 			}
397 			if (u.uhp < u.uhpmax) {
398 				if (u.ulevel > 9) {
399 					if (Regeneration || !(moves % 3)) {
400 						flags.botl = 1;
401 						u.uhp += rnd((int) u.ulevel - 9);
402 						if (u.uhp > u.uhpmax)
403 							u.uhp = u.uhpmax;
404 					}
405 				} else if (Regeneration ||
406 					 (!(moves % (22 - u.ulevel * 2)))) {
407 					flags.botl = 1;
408 					u.uhp++;
409 				}
410 			}
411 			if (Teleportation && !rn2(85))
412 				tele();
413 			if (Searching && multi >= 0)
414 				(void) dosearch();
415 			gethungry();
416 			invault();
417 			amulet();
418 		}
419 		if (multi < 0) {
420 			if (!++multi) {
421 				pline(nomovemsg ? nomovemsg :
422 				      "You can move again.");
423 				nomovemsg = 0;
424 				if (afternmv)
425 					(*afternmv) ();
426 				afternmv = 0;
427 			}
428 		}
429 		find_ac();
430 #ifndef QUEST
431 		if (!flags.mv || Blind)
432 #endif
433 		{
434 			seeobjs();
435 			seemons();
436 			nscr();
437 		}
438 		if (flags.botl || flags.botlx)
439 			bot();
440 
441 		flags.move = 1;
442 
443 		if (multi >= 0 && occupation) {
444 			if (monster_nearby())
445 				stop_occupation();
446 			else if ((*occupation) () == 0)
447 				occupation = 0;
448 			continue;
449 		}
450 		if (multi > 0) {
451 #ifdef QUEST
452 			if (flags.run >= 4)
453 				finddir();
454 #endif
455 			lookaround();
456 			if (!multi) {	/* lookaround may clear multi */
457 				flags.move = 0;
458 				continue;
459 			}
460 			if (flags.mv) {
461 				if (multi < COLNO && !--multi)
462 					flags.mv = flags.run = 0;
463 				domove();
464 			} else {
465 				--multi;
466 				rhack(save_cm);
467 			}
468 		} else if (multi == 0) {
469 #ifdef MAIL
470 			ckmailstatus();
471 #endif
472 			rhack((char *) 0);
473 		}
474 		if (multi && multi % 7 == 0)
475 			(void) fflush(stdout);
476 	}
477 }
478 
479 void
480 glo(foo)
481 	int foo;
482 {
483 	/* construct the string  xlock.n  */
484 	char           *tf;
485 
486 	tf = lock;
487 	while (*tf && *tf != '.')
488 		tf++;
489 	(void) sprintf(tf, ".%d", foo);
490 }
491 
492 /*
493  * plname is filled either by an option (-u Player  or  -uPlayer) or
494  * explicitly (-w implies wizard) or by askname.
495  * It may still contain a suffix denoting pl_character.
496  */
497 void
498 askname()
499 {
500 	int             c, ct;
501 	printf("\nWho are you? ");
502 	(void) fflush(stdout);
503 	ct = 0;
504 	while ((c = getchar()) != '\n') {
505 		if (c == EOF)
506 			error("End of input\n");
507 		/* some people get confused when their erase char is not ^H */
508 		if (c == '\010') {
509 			if (ct)
510 				ct--;
511 			continue;
512 		}
513 		if (c != '-')
514 			if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z')
515 				c = '_';
516 		if (ct < sizeof(plname) - 1)
517 			plname[ct++] = c;
518 	}
519 	plname[ct] = 0;
520 	if (ct == 0)
521 		askname();
522 }
523 
524 /* VARARGS1 */
525 void
526 impossible(const char *s, ...)
527 {
528 	va_list ap;
529 
530 	va_start(ap, s);
531 	vpline(s, ap);
532 	va_end(ap);
533 	pline("Program in disorder - perhaps you'd better Quit.");
534 }
535 
536 #ifdef CHDIR
537 static void
538 chdirx(dir, wr)
539 	const char     *dir;
540 	boolean         wr;
541 {
542 
543 #ifdef SECURE
544 	if (dir			/* User specified directory? */
545 #ifdef HACKDIR
546 	    && strcmp(dir, HACKDIR)	/* and not the default? */
547 #endif
548 		) {
549 		(void) setuid(getuid());	/* Ron Wessels */
550 		(void) setgid(getgid());
551 	}
552 #endif
553 
554 #ifdef HACKDIR
555 	if (dir == NULL)
556 		dir = HACKDIR;
557 #endif
558 
559 	if (dir && chdir(dir) < 0) {
560 		perror(dir);
561 		error("Cannot chdir to %s.", dir);
562 	}
563 	/* warn the player if he cannot write the record file */
564 	/* perhaps we should also test whether . is writable */
565 	/* unfortunately the access systemcall is worthless */
566 	if (wr) {
567 		int fd;
568 
569 		if (dir == NULL)
570 			dir = ".";
571 		if ((fd = open(RECORD, O_RDWR)) < 0) {
572 			printf("Warning: cannot write %s/%s", dir, RECORD);
573 			getret();
574 		} else
575 			(void) close(fd);
576 	}
577 }
578 #endif
579 
580 void
581 stop_occupation()
582 {
583 	if (occupation) {
584 		pline("You stop %s.", occtxt);
585 		occupation = 0;
586 	}
587 }
588