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