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