xref: /netbsd-src/games/hack/hack.main.c (revision 10ad5ffa714ce1a679dcc9dd8159648df2d67b5a)
1 /*	$NetBSD: hack.main.c,v 1.13 2009/06/29 23:05:33 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.13 2009/06/29 23:05:33 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 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(int argc, char *argv[])
100 {
101 	int             fd;
102 #ifdef CHDIR
103 	char           *dir;
104 #endif
105 
106 	/* Check for dirty tricks with closed fds 0, 1, 2 */
107 	fd = open("/dev/null", O_RDONLY);
108 	if (fd < 3)
109 		exit(1);
110 	close(fd);
111 
112 	hname = argv[0];
113 	hackpid = getpid();
114 
115 #ifdef CHDIR			/* otherwise no chdir() */
116 	/*
117 	 * See if we must change directory to the playground.
118 	 * (Perhaps hack runs suid and playground is inaccessible
119 	 *  for the player.)
120 	 * The environment variable HACKDIR is overridden by a
121 	 *  -d command line option (must be the first option given)
122 	 */
123 
124 	dir = getenv("HACKDIR");
125 	if (argc > 1 && !strncmp(argv[1], "-d", 2)) {
126 		argc--;
127 		argv++;
128 		dir = argv[0] + 2;
129 		if (*dir == '=' || *dir == ':')
130 			dir++;
131 		if (!*dir && argc > 1) {
132 			argc--;
133 			argv++;
134 			dir = argv[0];
135 		}
136 		if (!*dir)
137 			error("Flag -d must be followed by a directory name.");
138 	}
139 #endif
140 
141 	/*
142 	 * Who am i? Algorithm: 1. Use name as specified in HACKOPTIONS
143 	 *			2. Use $USER or $LOGNAME	(if 1. fails)
144 	 *			3. Use getlogin()		(if 2. fails)
145 	 * The resulting name is overridden by command line options.
146 	 * If everything fails, or if the resulting name is some generic
147 	 * account like "games", "play", "player", "hack" then eventually
148 	 * we'll ask him.
149 	 * Note that we trust him here; it is possible to play under
150 	 * somebody else's name.
151 	 */
152 	{
153 		char           *s;
154 
155 		initoptions();
156 		if (!*plname && (s = getenv("USER")))
157 			(void) strncpy(plname, s, sizeof(plname) - 1);
158 		if (!*plname && (s = getenv("LOGNAME")))
159 			(void) strncpy(plname, s, sizeof(plname) - 1);
160 		if (!*plname && (s = getlogin()))
161 			(void) strncpy(plname, s, sizeof(plname) - 1);
162 	}
163 
164 	/*
165 	 * Now we know the directory containing 'record' and
166 	 * may do a prscore().
167 	 */
168 	if (argc > 1 && !strncmp(argv[1], "-s", 2)) {
169 #ifdef CHDIR
170 		chdirx(dir, 0);
171 #endif
172 		prscore(argc, argv);
173 		exit(0);
174 	}
175 	/*
176 	 * It seems he really wants to play.
177 	 * Remember tty modes, to be restored on exit.
178 	 */
179 	gettty();
180 	setbuf(stdout, obuf);
181 	setrandom();
182 	startup();
183 	cls();
184 	u.uhp = 1;		/* prevent RIP on early quits */
185 	u.ux = FAR;		/* prevent nscr() */
186 	(void) signal(SIGHUP, hangup);
187 
188 	/*
189 	 * Find the creation date of this game,
190 	 * so as to avoid restoring outdated savefiles.
191 	 */
192 	gethdate(hname);
193 
194 	/*
195 	 * We cannot do chdir earlier, otherwise gethdate will fail.
196 	 */
197 #ifdef CHDIR
198 	chdirx(dir, 1);
199 #endif
200 
201 	/*
202 	 * Process options.
203 	 */
204 	while (argc > 1 && argv[1][0] == '-') {
205 		argv++;
206 		argc--;
207 		switch (argv[0][1]) {
208 #ifdef WIZARD
209 		case 'D':
210 			/* if(!strcmp(getlogin(), WIZARD)) */
211 			wizard = TRUE;
212 			/*
213 			 * else printf("Sorry.\n");
214 			 */
215 			break;
216 #endif
217 #ifdef NEWS
218 		case 'n':
219 			flags.nonews = TRUE;
220 			break;
221 #endif
222 		case 'u':
223 			if (argv[0][2])
224 				(void) strncpy(plname, argv[0] + 2, sizeof(plname) - 1);
225 			else if (argc > 1) {
226 				argc--;
227 				argv++;
228 				(void) strncpy(plname, argv[0], sizeof(plname) - 1);
229 			} else
230 				printf("Player name expected after -u\n");
231 			break;
232 		default:
233 			/* allow -T for Tourist, etc. */
234 			(void) strncpy(pl_character, argv[0] + 1,
235 				       sizeof(pl_character) - 1);
236 
237 			/* printf("Unknown option: %s\n", *argv); */
238 		}
239 	}
240 
241 	if (argc > 1)
242 		locknum = atoi(argv[1]);
243 #ifdef MAX_NR_OF_PLAYERS
244 	if (!locknum || locknum > MAX_NR_OF_PLAYERS)
245 		locknum = MAX_NR_OF_PLAYERS;
246 #endif
247 #ifdef DEF_PAGER
248 	if (((catmore = getenv("HACKPAGER")) == NULL &&
249 	    (catmore = getenv("PAGER")) == NULL) ||
250 	    catmore[0] == '\0')
251 		catmore = DEF_PAGER;
252 #endif
253 #ifdef MAIL
254 	getmailstatus();
255 #endif
256 #ifdef WIZARD
257 	if (wizard)
258 		(void) strcpy(plname, "wizard");
259 	else
260 #endif
261 		if (!*plname || !strncmp(plname, "player", 4)
262 		    || !strncmp(plname, "games", 4))
263 		askname();
264 	plnamesuffix();		/* strip suffix from name; calls askname() */
265 	/* again if suffix was whole name */
266 	/* accepts any suffix */
267 #ifdef WIZARD
268 	if (!wizard) {
269 #endif
270 		/*
271 		 * check for multiple games under the same name
272 		 * (if !locknum) or check max nr of players (otherwise)
273 		 */
274 		(void) signal(SIGQUIT, SIG_IGN);
275 		(void) signal(SIGINT, SIG_IGN);
276 		if (!locknum)
277 			(void) strcpy(lock, plname);
278 		getlock();	/* sets lock if locknum != 0 */
279 #ifdef WIZARD
280 	} else {
281 		char           *sfoo;
282 		(void) strcpy(lock, plname);
283 		if ((sfoo = getenv("MAGIC")) != NULL)
284 			while (*sfoo) {
285 				switch (*sfoo++) {
286 				case 'n':
287 					(void) srandom(*sfoo++);
288 					break;
289 				}
290 			}
291 		if ((sfoo = getenv("GENOCIDED")) != NULL) {
292 			if (*sfoo == '!') {
293 				const struct permonst *pm = mons;
294 				char           *gp = genocided;
295 
296 				while (pm < mons + CMNUM + 2) {
297 					if (!strchr(sfoo, pm->mlet))
298 						*gp++ = pm->mlet;
299 					pm++;
300 				}
301 				*gp = 0;
302 			} else
303 				(void) strlcpy(genocided, sfoo,
304 						sizeof(genocided));
305 			(void) strcpy(fut_geno, genocided);
306 		}
307 	}
308 #endif
309 	setftty();
310 	(void) snprintf(SAVEF, sizeof(SAVEF), "save/%d%s", getuid(), plname);
311 	regularize(SAVEF + 5);	/* avoid . or / in name */
312 	if ((fd = open(SAVEF, O_RDONLY)) >= 0 &&
313 	    (uptodate(fd) || unlink(SAVEF) == 666)) {
314 		(void) signal(SIGINT, done1);
315 		pline("Restoring old save file...");
316 		(void) fflush(stdout);
317 		if (!dorecover(fd))
318 			goto not_recovered;
319 		pline("Hello %s, welcome to %s!", plname, gamename);
320 		flags.move = 0;
321 	} else {
322 not_recovered:
323 		fobj = fcobj = invent = 0;
324 		fmon = fallen_down = 0;
325 		ftrap = 0;
326 		fgold = 0;
327 		flags.ident = 1;
328 		init_objects();
329 		u_init();
330 
331 		(void) signal(SIGINT, done1);
332 		mklev();
333 		u.ux = xupstair;
334 		u.uy = yupstair;
335 		(void) inshop();
336 		setsee();
337 		flags.botlx = 1;
338 		makedog();
339 		{
340 			struct monst   *mtmp;
341 			if ((mtmp = m_at(u.ux, u.uy)) != NULL)
342 				mnexto(mtmp);	/* riv05!a3 */
343 		}
344 		seemons();
345 #ifdef NEWS
346 		if (flags.nonews || !readnews())
347 			/* after reading news we did docrt() already */
348 #endif
349 			docrt();
350 
351 		/* give welcome message before pickup messages */
352 		pline("Hello %s, welcome to %s!", plname, gamename);
353 
354 		pickup(1);
355 		read_engr_at(u.ux, u.uy);
356 		flags.move = 1;
357 	}
358 
359 	flags.moonphase = phase_of_the_moon();
360 	if (flags.moonphase == FULL_MOON) {
361 		pline("You are lucky! Full moon tonight.");
362 		u.uluck++;
363 	} else if (flags.moonphase == NEW_MOON) {
364 		pline("Be careful! New moon tonight.");
365 	}
366 	initrack();
367 
368 	for (;;) {
369 		if (flags.move) {	/* actual time passed */
370 
371 			settrack();
372 
373 			if (moves % 2 == 0 ||
374 			    (!(Fast & ~INTRINSIC) && (!Fast || rn2(3)))) {
375 				movemon();
376 				if (!rn2(70))
377 					(void) makemon((struct permonst *) 0, 0, 0);
378 			}
379 			if (Glib)
380 				glibr();
381 			timeout();
382 			++moves;
383 			if (flags.time)
384 				flags.botl = 1;
385 			if (u.uhp < 1) {
386 				pline("You die...");
387 				done("died");
388 			}
389 			if (u.uhp * 10 < u.uhpmax && moves - wailmsg > 50) {
390 				wailmsg = moves;
391 				if (u.uhp == 1)
392 					pline("You hear the wailing of the Banshee...");
393 				else
394 					pline("You hear the howling of the CwnAnnwn...");
395 			}
396 			if (u.uhp < u.uhpmax) {
397 				if (u.ulevel > 9) {
398 					if (Regeneration || !(moves % 3)) {
399 						flags.botl = 1;
400 						u.uhp += rnd((int) u.ulevel - 9);
401 						if (u.uhp > u.uhpmax)
402 							u.uhp = u.uhpmax;
403 					}
404 				} else if (Regeneration ||
405 					 (!(moves % (22 - u.ulevel * 2)))) {
406 					flags.botl = 1;
407 					u.uhp++;
408 				}
409 			}
410 			if (Teleportation && !rn2(85))
411 				tele();
412 			if (Searching && multi >= 0)
413 				(void) dosearch();
414 			gethungry();
415 			invault();
416 			amulet();
417 		}
418 		if (multi < 0) {
419 			if (!++multi) {
420 				pline(nomovemsg ? nomovemsg :
421 				      "You can move again.");
422 				nomovemsg = 0;
423 				if (afternmv)
424 					(*afternmv) ();
425 				afternmv = 0;
426 			}
427 		}
428 		find_ac();
429 #ifndef QUEST
430 		if (!flags.mv || Blind)
431 #endif
432 		{
433 			seeobjs();
434 			seemons();
435 			nscr();
436 		}
437 		if (flags.botl || flags.botlx)
438 			bot();
439 
440 		flags.move = 1;
441 
442 		if (multi >= 0 && occupation) {
443 			if (monster_nearby())
444 				stop_occupation();
445 			else if ((*occupation) () == 0)
446 				occupation = 0;
447 			continue;
448 		}
449 		if (multi > 0) {
450 #ifdef QUEST
451 			if (flags.run >= 4)
452 				finddir();
453 #endif
454 			lookaround();
455 			if (!multi) {	/* lookaround may clear multi */
456 				flags.move = 0;
457 				continue;
458 			}
459 			if (flags.mv) {
460 				if (multi < COLNO && !--multi)
461 					flags.mv = flags.run = 0;
462 				domove();
463 			} else {
464 				--multi;
465 				rhack(save_cm);
466 			}
467 		} else if (multi == 0) {
468 #ifdef MAIL
469 			ckmailstatus();
470 #endif
471 			rhack((char *) 0);
472 		}
473 		if (multi && multi % 7 == 0)
474 			(void) fflush(stdout);
475 	}
476 }
477 
478 void
479 glo(int foo)
480 {
481 	/* construct the string  xlock.n  */
482 	size_t pos;
483 
484 	pos = 0;
485 	while (lock[pos] && lock[pos] != '.')
486 		pos++;
487 	(void) snprintf(lock + pos, sizeof(lock) - pos, ".%d", foo);
488 }
489 
490 /*
491  * plname is filled either by an option (-u Player  or  -uPlayer) or
492  * explicitly (-w implies wizard) or by askname.
493  * It may still contain a suffix denoting pl_character.
494  */
495 void
496 askname(void)
497 {
498 	int             c, ct;
499 	printf("\nWho are you? ");
500 	(void) fflush(stdout);
501 	ct = 0;
502 	while ((c = getchar()) != '\n') {
503 		if (c == EOF)
504 			error("End of input\n");
505 		/* some people get confused when their erase char is not ^H */
506 		if (c == '\010') {
507 			if (ct)
508 				ct--;
509 			continue;
510 		}
511 		if (c != '-')
512 			if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z')
513 				c = '_';
514 		if (ct < (int)sizeof(plname) - 1)
515 			plname[ct++] = c;
516 	}
517 	plname[ct] = 0;
518 	if (ct == 0)
519 		askname();
520 }
521 
522 /* VARARGS1 */
523 void
524 impossible(const char *s, ...)
525 {
526 	va_list ap;
527 
528 	va_start(ap, s);
529 	vpline(s, ap);
530 	va_end(ap);
531 	pline("Program in disorder - perhaps you'd better Quit.");
532 }
533 
534 #ifdef CHDIR
535 static void
536 chdirx(const char *dir, boolean wr)
537 {
538 
539 #ifdef SECURE
540 	if (dir			/* User specified directory? */
541 #ifdef HACKDIR
542 	    && strcmp(dir, HACKDIR)	/* and not the default? */
543 #endif
544 		) {
545 		(void) setuid(getuid());	/* Ron Wessels */
546 		(void) setgid(getgid());
547 	}
548 #endif
549 
550 #ifdef HACKDIR
551 	if (dir == NULL)
552 		dir = HACKDIR;
553 #endif
554 
555 	if (dir && chdir(dir) < 0) {
556 		perror(dir);
557 		error("Cannot chdir to %s.", dir);
558 	}
559 	/* warn the player if he cannot write the record file */
560 	/* perhaps we should also test whether . is writable */
561 	/* unfortunately the access systemcall is worthless */
562 	if (wr) {
563 		int fd;
564 
565 		if (dir == NULL)
566 			dir = ".";
567 		if ((fd = open(RECORD, O_RDWR)) < 0) {
568 			printf("Warning: cannot write %s/%s", dir, RECORD);
569 			getret();
570 		} else
571 			(void) close(fd);
572 	}
573 }
574 #endif
575 
576 void
577 stop_occupation(void)
578 {
579 	if (occupation) {
580 		pline("You stop %s.", occtxt);
581 		occupation = 0;
582 	}
583 }
584