xref: /openbsd-src/games/hack/hack.main.c (revision d25013f2c0d15d97e2e6c140a80677c921ca98c6)
1 /*	$OpenBSD: hack.main.c,v 1.9 2003/03/16 21:22:35 camield 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 char rcsid[] = "$OpenBSD: hack.main.c,v 1.9 2003/03/16 21:22:35 camield Exp $";
66 #endif /* not lint */
67 
68 #include <stdio.h>
69 #include <signal.h>
70 #include "hack.h"
71 
72 #ifdef QUEST
73 #define	gamename	"quest"
74 #else
75 #define	gamename	"hack"
76 #endif
77 
78 extern char *getlogin(), *getenv();
79 extern char plname[PL_NSIZ], pl_character[PL_CSIZ];
80 extern struct permonst mons[CMNUM+2];
81 extern char genocided[], fut_geno[];
82 
83 int (*afternmv)();
84 int (*occupation)();
85 char *occtxt;			/* defined when occupation != NULL */
86 
87 void done1();
88 void hangup();
89 
90 int hackpid;				/* current pid */
91 int locknum;				/* max num of players */
92 #ifdef DEF_PAGER
93 char *catmore;				/* default pager */
94 #endif
95 char SAVEF[PL_NSIZ + 11] = "save/";	/* save/99999player */
96 char *hname;		/* name of the game (argv[0] of call) */
97 char obuf[BUFSIZ];	/* BUFSIZ is defined in stdio.h */
98 
99 extern char *nomovemsg;
100 extern long wailmsg;
101 
102 #ifdef CHDIR
103 static void chdirx();
104 #endif
105 
106 main(argc,argv)
107 int argc;
108 char *argv[];
109 {
110 	register int fd;
111 #ifdef CHDIR
112 	register 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 	{ register char *s;
155 
156 	  initoptions();
157 	  if(!*plname && (s = getenv("LOGNAME")))
158 		(void) strncpy(plname, s, sizeof(plname)-1);
159 	  if(!*plname && (s = getenv("USER")))
160 		(void) strncpy(plname, s, sizeof(plname)-1);
161 	  if(!*plname && (s = getlogin()))
162 		(void) strncpy(plname, s, sizeof(plname)-1);
163 	  if(*plname)
164 		plname[sizeof(plname)-1] = '\0';
165 	}
166 
167 	/*
168 	 * Now we know the directory containing 'record' and
169 	 * may do a prscore().
170 	 */
171 	if(argc > 1 && !strncmp(argv[1], "-s", 2)) {
172 #ifdef CHDIR
173 		chdirx(dir,0);
174 #endif
175 		prscore(argc, argv);
176 		exit(0);
177 	}
178 
179 	/*
180 	 * It seems he really wants to play.
181 	 * Remember tty modes, to be restored on exit.
182 	 */
183 	gettty();
184 	setbuf(stdout,obuf);
185 	umask(007);
186 	setrandom();
187 	startup();
188 	cls();
189 	u.uhp = 1;	/* prevent RIP on early quits */
190 	u.ux = FAR;	/* prevent nscr() */
191 	(void) signal(SIGHUP, hangup);
192 
193 	/*
194 	 * Find the creation date of this game,
195 	 * so as to avoid restoring outdated savefiles.
196 	 */
197 	gethdate(hname);
198 
199 	/*
200 	 * We cannot do chdir earlier, otherwise gethdate will fail.
201 	 */
202 #ifdef CHDIR
203 	chdirx(dir,1);
204 #endif
205 
206 	/*
207 	 * Process options.
208 	 */
209 	while(argc > 1 && argv[1][0] == '-'){
210 		argv++;
211 		argc--;
212 		switch(argv[0][1]){
213 #ifdef WIZARD
214 		case 'D':
215 /*			if(!strcmp(getlogin(), WIZARD)) */
216 				wizard = TRUE;
217 /*			else
218 				printf("Sorry.\n"); */
219 			break;
220 #endif
221 #ifdef NEWS
222 		case 'n':
223 			flags.nonews = TRUE;
224 			break;
225 #endif
226 		case 'u':
227 			if(argv[0][2]) {
228 			  (void) strncpy(plname, argv[0]+2, sizeof(plname)-1);
229 			  plname[sizeof(plname)-1] = '\0';
230 			} else if(argc > 1) {
231 			  argc--;
232 			  argv++;
233 			  (void) strncpy(plname, argv[0], sizeof(plname)-1);
234 			  plname[sizeof(plname)-1] = '\0';
235 			} else
236 				printf("Player name expected after -u\n");
237 			break;
238 		default:
239 			/* allow -T for Tourist, etc. */
240 			(void) strncpy(pl_character, argv[0]+1,
241 				sizeof(pl_character)-1);
242 			plname[sizeof(pl_character)-1] = '\0';
243 
244 			/* printf("Unknown option: %s\n", *argv); */
245 		}
246 	}
247 
248 	if(argc > 1)
249 		locknum = atoi(argv[1]);
250 #ifdef MAX_NR_OF_PLAYERS
251 	if(!locknum || locknum > MAX_NR_OF_PLAYERS)
252 		locknum = MAX_NR_OF_PLAYERS;
253 #endif
254 #ifdef DEF_PAGER
255 	if(!(catmore = getenv("HACKPAGER")) && !(catmore = getenv("PAGER")))
256 		catmore = DEF_PAGER;
257 #endif
258 #ifdef MAIL
259 	getmailstatus();
260 #endif
261 #ifdef WIZARD
262 	if(wizard) (void) strcpy(plname, "wizard"); else
263 #endif
264 	if(!*plname || !strncmp(plname, "player", 4)
265 		    || !strncmp(plname, "games", 4))
266 		askname();
267 	plnamesuffix();		/* strip suffix from name; calls askname() */
268 				/* again if suffix was whole name */
269 				/* accepts any suffix */
270 #ifdef WIZARD
271 	if(!wizard) {
272 #endif
273 		/*
274 		 * check for multiple games under the same name
275 		 * (if !locknum) or check max nr of players (otherwise)
276 		 */
277 		(void) signal(SIGQUIT,SIG_IGN);
278 		(void) signal(SIGINT,SIG_IGN);
279 		if(!locknum)
280 			(void) strcpy(lock,plname);
281 		getlock();	/* sets lock if locknum != 0 */
282 #ifdef WIZARD
283 	} else {
284 		register char *sfoo;
285 		(void) strcpy(lock,plname);
286 		if(sfoo = getenv("MAGIC"))
287 			while(*sfoo) {
288 				switch(*sfoo++) {
289 				case 'n': (void) srandom(*sfoo++);
290 					break;
291 				}
292 			}
293 		if(sfoo = getenv("GENOCIDED")){
294 			if(*sfoo == '!'){
295 				register struct permonst *pm = mons;
296 				register 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/%u%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 		{ register struct monst *mtmp;
341 		  if(mtmp = m_at(u.ux, u.uy)) mnexto(mtmp);	/* riv05!a3 */
342 		}
343 		seemons();
344 #ifdef NEWS
345 		if(flags.nonews || !readnews())
346 			/* after reading news we did docrt() already */
347 #endif
348 			docrt();
349 
350 		/* give welcome message before pickup messages */
351 		pline("Hello %s, welcome to %s!", plname, gamename);
352 
353 		pickup(1);
354 		read_engr_at(u.ux,u.uy);
355 		flags.move = 1;
356 	}
357 
358 	flags.moonphase = phase_of_the_moon();
359 	if(flags.moonphase == FULL_MOON) {
360 		pline("You are lucky! Full moon tonight.");
361 		u.uluck++;
362 	} else if(flags.moonphase == NEW_MOON) {
363 		pline("Be careful! New moon tonight.");
364 	}
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 				extern struct monst *makemon();
376 				movemon();
377 				if(!rn2(70))
378 				    (void) makemon((struct permonst *)0, 0, 0);
379 			}
380 			if(Glib) glibr();
381 			timeout();
382 			++moves;
383 			if(flags.time) flags.botl = 1;
384 			if(u.uhp < 1) {
385 				pline("You die...");
386 				done("died");
387 			}
388 			if(u.uhp*10 < u.uhpmax && moves-wailmsg > 50){
389 			    wailmsg = moves;
390 			    if(u.uhp == 1)
391 			    pline("You hear the wailing of the Banshee...");
392 			    else
393 			    pline("You hear the howling of the CwnAnnwn...");
394 			}
395 			if(u.uhp < u.uhpmax) {
396 				if(u.ulevel > 9) {
397 					if(Regeneration || !(moves%3)) {
398 					    flags.botl = 1;
399 					    u.uhp += rnd((int) u.ulevel-9);
400 					    if(u.uhp > u.uhpmax)
401 						u.uhp = u.uhpmax;
402 					}
403 				} else if(Regeneration ||
404 					(!(moves%(22-u.ulevel*2)))) {
405 					flags.botl = 1;
406 					u.uhp++;
407 				}
408 			}
409 			if(Teleportation && !rn2(85)) tele();
410 			if(Searching && multi >= 0) (void) dosearch();
411 			gethungry();
412 			invault();
413 			amulet();
414 		}
415 		if(multi < 0) {
416 			if(!++multi){
417 				pline(nomovemsg ? nomovemsg :
418 					"You can move again.");
419 				nomovemsg = 0;
420 				if(afternmv) (*afternmv)();
421 				afternmv = 0;
422 			}
423 		}
424 
425 		find_ac();
426 #ifndef QUEST
427 		if(!flags.mv || Blind)
428 #endif
429 		{
430 			seeobjs();
431 			seemons();
432 			nscr();
433 		}
434 		if(flags.botl || flags.botlx) bot();
435 
436 		flags.move = 1;
437 
438 		if(multi >= 0 && occupation) {
439 			if(monster_nearby())
440 				stop_occupation();
441 			else if ((*occupation)() == 0)
442 				occupation = 0;
443 			continue;
444 		}
445 
446 		if(multi > 0) {
447 #ifdef QUEST
448 			if(flags.run >= 4) finddir();
449 #endif
450 			lookaround();
451 			if(!multi) {	/* lookaround may clear multi */
452 				flags.move = 0;
453 				continue;
454 			}
455 			if(flags.mv) {
456 				if(multi < COLNO && !--multi)
457 					flags.mv = flags.run = 0;
458 				domove();
459 			} else {
460 				--multi;
461 				rhack(save_cm);
462 			}
463 		} else if(multi == 0) {
464 #ifdef MAIL
465 			ckmailstatus();
466 #endif
467 			rhack((char *) 0);
468 		}
469 		if(multi && multi%7 == 0)
470 			(void) fflush(stdout);
471 	}
472 }
473 
474 glo(foo)
475 register foo;
476 {
477 	/* construct the string  xlock.n  */
478 	register char *tf;
479 
480 	tf = lock;
481 	while(*tf && *tf != '.') tf++;
482 	(void) sprintf(tf, ".%d", foo);
483 }
484 
485 /*
486  * plname is filled either by an option (-u Player  or  -uPlayer) or
487  * explicitly (-w implies wizard) or by askname.
488  * It may still contain a suffix denoting pl_character.
489  */
490 askname(){
491 register int c,ct;
492 	printf("\nWho are you? ");
493 	(void) fflush(stdout);
494 	ct = 0;
495 	while((c = getchar()) != '\n'){
496 		if(c == EOF) error("End of input\n");
497 		/* some people get confused when their erase char is not ^H */
498 		if(c == '\010') {
499 			if(ct) ct--;
500 			continue;
501 		}
502 		if(c != '-')
503 		if(c < 'A' || (c > 'Z' && c < 'a') || c > 'z') c = '_';
504 		if(ct < sizeof(plname)-1) plname[ct++] = c;
505 	}
506 	plname[ct] = 0;
507 	if(ct == 0) askname();
508 }
509 
510 /*VARARGS1*/
511 impossible(s,x1,x2)
512 register char *s;
513 {
514 	pline(s,x1,x2);
515 	pline("Program in disorder - perhaps you'd better Quit.");
516 }
517 
518 #ifdef CHDIR
519 static void
520 chdirx(dir, wr)
521 char *dir;
522 boolean wr;
523 {
524 
525 #ifdef SECURE
526 	if(dir					/* User specified directory? */
527 #ifdef HACKDIR
528 	       && strcmp(dir, HACKDIR)		/* and not the default? */
529 #endif
530 		) {
531 		/* revoke */
532 		setegid(getgid());
533 		setgid(getgid());
534 	}
535 #endif
536 
537 #ifdef HACKDIR
538 	if(dir == NULL)
539 		dir = HACKDIR;
540 #endif
541 
542 	if(dir && chdir(dir) < 0) {
543 		perror(dir);
544 		error("Cannot chdir to %s.", dir);
545 	}
546 
547 	/* warn the player if he cannot write the record file */
548 	/* perhaps we should also test whether . is writable */
549 	/* unfortunately the access systemcall is worthless */
550 	if(wr) {
551 	    register fd;
552 
553 	    if(dir == NULL)
554 		dir = ".";
555 	    if((fd = open(RECORD, O_RDWR)) < 0) {
556 		printf("Warning: cannot write %s/%s", dir, RECORD);
557 		getret();
558 	    } else
559 		(void) close(fd);
560 	}
561 }
562 #endif
563 
564 stop_occupation()
565 {
566 	if(occupation) {
567 		pline("You stop %s.", occtxt);
568 		occupation = 0;
569 	}
570 }
571