xref: /netbsd-src/games/hack/hack.main.c (revision 2c0ecb1ab63c4e87a4abcff12e0a9e999ff836d9)
1 /*	$NetBSD: hack.main.c,v 1.17 2011/08/06 20:42:43 dholland 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.17 2011/08/06 20:42:43 dholland 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 
94 static char obuf[BUFSIZ];	/* BUFSIZ is defined in stdio.h */
95 
96 int main(int, char *[]);
97 static void chdirx(const char *, boolean);
98 
99 int
main(int argc,char * argv[])100 main(int argc, char *argv[])
101 {
102 	int             fd;
103 #ifdef CHDIR
104 	char           *dir;
105 #endif
106 
107 	/* Check for dirty tricks with closed fds 0, 1, 2 */
108 	fd = open("/dev/null", O_RDONLY);
109 	if (fd < 3)
110 		exit(1);
111 	close(fd);
112 
113 	hname = argv[0];
114 	hackpid = getpid();
115 
116 #ifdef CHDIR			/* otherwise no chdir() */
117 	/*
118 	 * See if we must change directory to the playground.
119 	 * (Perhaps hack runs suid and playground is inaccessible
120 	 *  for the player.)
121 	 * The environment variable HACKDIR is overridden by a
122 	 *  -d command line option (must be the first option given)
123 	 */
124 
125 	dir = getenv("HACKDIR");
126 	if (argc > 1 && !strncmp(argv[1], "-d", 2)) {
127 		argc--;
128 		argv++;
129 		dir = argv[0] + 2;
130 		if (*dir == '=' || *dir == ':')
131 			dir++;
132 		if (!*dir && argc > 1) {
133 			argc--;
134 			argv++;
135 			dir = argv[0];
136 		}
137 		if (!*dir)
138 			error("Flag -d must be followed by a directory name.");
139 	}
140 #endif
141 
142 	/*
143 	 * Who am i? Algorithm: 1. Use name as specified in HACKOPTIONS
144 	 *			2. Use $USER or $LOGNAME	(if 1. fails)
145 	 *			3. Use getlogin()		(if 2. fails)
146 	 * The resulting name is overridden by command line options.
147 	 * If everything fails, or if the resulting name is some generic
148 	 * account like "games", "play", "player", "hack" then eventually
149 	 * we'll ask him.
150 	 * Note that we trust him here; it is possible to play under
151 	 * somebody else's name.
152 	 */
153 	{
154 		char           *s;
155 
156 		initoptions();
157 		if (!*plname && (s = getenv("USER")))
158 			(void) strncpy(plname, s, sizeof(plname) - 1);
159 		if (!*plname && (s = getenv("LOGNAME")))
160 			(void) strncpy(plname, s, sizeof(plname) - 1);
161 		if (!*plname && (s = getlogin()))
162 			(void) strncpy(plname, s, sizeof(plname) - 1);
163 	}
164 
165 	/*
166 	 * Now we know the directory containing 'record' and
167 	 * may do a prscore().
168 	 */
169 	if (argc > 1 && !strncmp(argv[1], "-s", 2)) {
170 #ifdef CHDIR
171 		chdirx(dir, 0);
172 #endif
173 		prscore(argc, argv);
174 		exit(0);
175 	}
176 	/*
177 	 * It seems he really wants to play.
178 	 * Remember tty modes, to be restored on exit.
179 	 */
180 	gettty();
181 	setbuf(stdout, obuf);
182 	setrandom();
183 	startup();
184 	cls();
185 	u.uhp = 1;		/* prevent RIP on early quits */
186 	u.ux = FAR;		/* prevent nscr() */
187 	(void) signal(SIGHUP, hang_up);
188 
189 	/*
190 	 * Find the creation date of this game,
191 	 * so as to avoid restoring outdated savefiles.
192 	 */
193 	gethdate(hname);
194 
195 	/*
196 	 * We cannot do chdir earlier, otherwise gethdate will fail.
197 	 */
198 #ifdef CHDIR
199 	chdirx(dir, 1);
200 #endif
201 
202 	/*
203 	 * Process options.
204 	 */
205 	while (argc > 1 && argv[1][0] == '-') {
206 		argv++;
207 		argc--;
208 		switch (argv[0][1]) {
209 #ifdef WIZARD
210 		case 'D':
211 			/* if(!strcmp(getlogin(), WIZARD)) */
212 			wizard = TRUE;
213 			/*
214 			 * else printf("Sorry.\n");
215 			 */
216 			break;
217 #endif
218 #ifdef NEWS
219 		case 'n':
220 			flags.nonews = TRUE;
221 			break;
222 #endif
223 		case 'u':
224 			if (argv[0][2])
225 				(void) strncpy(plname, argv[0] + 2, sizeof(plname) - 1);
226 			else if (argc > 1) {
227 				argc--;
228 				argv++;
229 				(void) strncpy(plname, argv[0], sizeof(plname) - 1);
230 			} else
231 				printf("Player name expected after -u\n");
232 			break;
233 		default:
234 			/* allow -T for Tourist, etc. */
235 			(void) strncpy(pl_character, argv[0] + 1,
236 				       sizeof(pl_character) - 1);
237 
238 			/* printf("Unknown option: %s\n", *argv); */
239 		}
240 	}
241 
242 	if (argc > 1)
243 		locknum = atoi(argv[1]);
244 #ifdef MAX_NR_OF_PLAYERS
245 	if (!locknum || locknum > MAX_NR_OF_PLAYERS)
246 		locknum = MAX_NR_OF_PLAYERS;
247 #endif
248 #ifdef DEF_PAGER
249 	if (((catmore = getenv("HACKPAGER")) == NULL &&
250 	    (catmore = getenv("PAGER")) == NULL) ||
251 	    catmore[0] == '\0')
252 		catmore = DEF_PAGER;
253 #endif
254 #ifdef MAIL
255 	getmailstatus();
256 #endif
257 #ifdef WIZARD
258 	if (wizard)
259 		(void) strcpy(plname, "wizard");
260 	else
261 #endif
262 		if (!*plname || !strncmp(plname, "player", 4)
263 		    || !strncmp(plname, "games", 4))
264 		askname();
265 	plnamesuffix();		/* strip suffix from name; calls askname() */
266 	/* again if suffix was whole name */
267 	/* accepts any suffix */
268 #ifdef WIZARD
269 	if (!wizard) {
270 #endif
271 		/*
272 		 * check for multiple games under the same name
273 		 * (if !locknum) or check max nr of players (otherwise)
274 		 */
275 		(void) signal(SIGQUIT, SIG_IGN);
276 		(void) signal(SIGINT, SIG_IGN);
277 		if (!locknum)
278 			(void) strcpy(lock, plname);
279 		getlock();	/* sets lock if locknum != 0 */
280 #ifdef WIZARD
281 	} else {
282 		char           *sfoo;
283 		(void) strcpy(lock, plname);
284 		if ((sfoo = getenv("MAGIC")) != NULL)
285 			while (*sfoo) {
286 				switch (*sfoo++) {
287 				case 'n':
288 					(void) srandom(*sfoo++);
289 					break;
290 				}
291 			}
292 		if ((sfoo = getenv("GENOCIDED")) != NULL) {
293 			if (*sfoo == '!') {
294 				const struct permonst *pm = mons;
295 				char           *gp = genocided;
296 
297 				while (pm < mons + CMNUM + 2) {
298 					if (!strchr(sfoo, pm->mlet))
299 						*gp++ = pm->mlet;
300 					pm++;
301 				}
302 				*gp = 0;
303 			} else
304 				(void) strlcpy(genocided, sfoo,
305 						sizeof(genocided));
306 			(void) strcpy(fut_geno, genocided);
307 		}
308 	}
309 #endif
310 	setftty();
311 	(void) snprintf(SAVEF, sizeof(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 				if (nomovemsg)
422 					pline("%s", nomovemsg);
423 				else
424 					pline("You can move again.");
425 				nomovemsg = 0;
426 				if (afternmv)
427 					(*afternmv) ();
428 				afternmv = 0;
429 			}
430 		}
431 		find_ac();
432 #ifndef QUEST
433 		if (!flags.mv || Blind)
434 #endif
435 		{
436 			seeobjs();
437 			seemons();
438 			nscr();
439 		}
440 		if (flags.botl || flags.botlx)
441 			bot();
442 
443 		flags.move = 1;
444 
445 		if (multi >= 0 && occupation) {
446 			if (monster_nearby())
447 				stop_occupation();
448 			else if ((*occupation) () == 0)
449 				occupation = 0;
450 			continue;
451 		}
452 		if (multi > 0) {
453 #ifdef QUEST
454 			if (flags.run >= 4)
455 				finddir();
456 #endif
457 			lookaround();
458 			if (!multi) {	/* lookaround may clear multi */
459 				flags.move = 0;
460 				continue;
461 			}
462 			if (flags.mv) {
463 				if (multi < COLNO && !--multi)
464 					flags.mv = flags.run = 0;
465 				domove();
466 			} else {
467 				--multi;
468 				rhack(save_cm);
469 			}
470 		} else if (multi == 0) {
471 #ifdef MAIL
472 			ckmailstatus();
473 #endif
474 			rhack(NULL);
475 		}
476 		if (multi && multi % 7 == 0)
477 			(void) fflush(stdout);
478 	}
479 }
480 
481 void
glo(int foo)482 glo(int foo)
483 {
484 	/* construct the string  xlock.n  */
485 	size_t pos;
486 
487 	pos = 0;
488 	while (lock[pos] && lock[pos] != '.')
489 		pos++;
490 	(void) snprintf(lock + pos, sizeof(lock) - pos, ".%d", foo);
491 }
492 
493 /*
494  * plname is filled either by an option (-u Player  or  -uPlayer) or
495  * explicitly (-w implies wizard) or by askname.
496  * It may still contain a suffix denoting pl_character.
497  */
498 void
askname(void)499 askname(void)
500 {
501 	int             c, ct;
502 	printf("\nWho are you? ");
503 	(void) fflush(stdout);
504 	ct = 0;
505 	while ((c = getchar()) != '\n') {
506 		if (c == EOF)
507 			error("End of input\n");
508 		/* some people get confused when their erase char is not ^H */
509 		if (c == '\010') {
510 			if (ct)
511 				ct--;
512 			continue;
513 		}
514 		if (c != '-')
515 			if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z')
516 				c = '_';
517 		if (ct < (int)sizeof(plname) - 1)
518 			plname[ct++] = c;
519 	}
520 	plname[ct] = 0;
521 	if (ct == 0)
522 		askname();
523 }
524 
525 /* VARARGS1 */
526 void
impossible(const char * s,...)527 impossible(const char *s, ...)
528 {
529 	va_list ap;
530 
531 	va_start(ap, s);
532 	vpline(s, ap);
533 	va_end(ap);
534 	pline("Program in disorder - perhaps you'd better Quit.");
535 }
536 
537 #ifdef CHDIR
538 static void
chdirx(const char * dir,boolean wr)539 chdirx(const char *dir, boolean wr)
540 {
541 
542 #ifdef SECURE
543 	if (dir			/* User specified directory? */
544 #ifdef HACKDIR
545 	    && strcmp(dir, HACKDIR)	/* and not the default? */
546 #endif
547 		) {
548 		(void) setuid(getuid());	/* Ron Wessels */
549 		(void) setgid(getgid());
550 	}
551 #endif
552 
553 #ifdef HACKDIR
554 	if (dir == NULL)
555 		dir = HACKDIR;
556 #endif
557 
558 	if (dir && chdir(dir) < 0) {
559 		perror(dir);
560 		error("Cannot chdir to %s.", dir);
561 	}
562 	/* warn the player if he cannot write the record file */
563 	/* perhaps we should also test whether . is writable */
564 	/* unfortunately the access systemcall is worthless */
565 	if (wr) {
566 		int fd;
567 
568 		if (dir == NULL)
569 			dir = ".";
570 		if ((fd = open(RECORD, O_RDWR)) < 0) {
571 			printf("Warning: cannot write %s/%s", dir, RECORD);
572 			getret();
573 		} else
574 			(void) close(fd);
575 	}
576 }
577 #endif
578 
579 void
stop_occupation(void)580 stop_occupation(void)
581 {
582 	if (occupation) {
583 		pline("You stop %s.", occtxt);
584 		occupation = 0;
585 	}
586 }
587