xref: /dragonfly/games/phantasia/main.c (revision c69bf40f)
1 /*
2  * Phantasia 3.3.2 -- Interterminal fantasy game
3  *
4  * Edward A. Estes
5  * AT&T, March 12, 1986
6  *
7  * $FreeBSD: src/games/phantasia/main.c,v 1.8 1999/11/16 02:57:34 billf Exp $
8  * $DragonFly: src/games/phantasia/main.c,v 1.3 2005/05/31 00:06:26 swildner Exp $
9  */
10 
11 /* DISCLAIMER:
12  *
13  * This game is distributed for free as is.  It is not guaranteed to work
14  * in every conceivable environment.  It is not even guaranteed to work
15  * in ANY environment.
16  *
17  * This game is distributed without notice of copyright, therefore it
18  * may be used in any manner the recipient sees fit.  However, the
19  * author assumes no responsibility for maintaining or revising this
20  * game, in its original form, or any derivitives thereof.
21  *
22  * The author shall not be responsible for any loss, cost, or damage,
23  * including consequential damage, caused by reliance on this material.
24  *
25  * The author makes no warranties, express or implied, including warranties
26  * of merchantability or fitness for a particular purpose or use.
27  *
28  * AT&T is in no way connected with this game.
29  */
30 
31 #include <sys/types.h>
32 #include <pwd.h>
33 #include <string.h>
34 
35 /*
36  * The program allocates as much file space as it needs to store characters,
37  * so the possibility exists for the character file to grow without bound.
38  * The file is purged upon normal entry to try to avoid that problem.
39  * A similar problem exists for energy voids.  To alleviate the problem here,
40  * the void file is cleared with every new king, and a limit is placed
41  * on the size of the energy void file.
42  */
43 
44 /*
45  * The scoreboard file is updated when someone dies, and keeps track
46  * of the highest character to date for that login.
47  * Being purged from the character file does not cause the scoreboard
48  * to be updated.
49  */
50 
51 
52 #include "include.h"
53 
54 /* functions which we need to know about */
55 /* fight.c */
56 extern	void	encounter(int);
57 /* gamesupport.c */
58 extern	void	activelist(void);
59 extern	void	changestats(bool);
60 extern	void	monstlist(void);
61 extern	void	purgeoldplayers(void);
62 extern	void	scorelist(void);
63 /* interplayer.c */
64 extern	void	checkbattle(void);
65 extern	void	checktampered(void);
66 extern	void	dotampered(void);
67 extern	void	throneroom(void);
68 extern	void	userlist(bool);
69 /* io.c */
70 extern	int	getanswer(const char *, bool);
71 extern	void	getstring(char *, int);
72 extern	double	infloat(void);
73 extern	int	inputoption(void);
74 extern	void	more(int);
75 /* misc.c */
76 extern	void	adjuststats(void);
77 extern	long	allocrecord(void);
78 extern	void	allstatslist(void);
79 extern	void	altercoordinates(double, double, int);
80 extern	void	collecttaxes(double, double);
81 extern	void	death(const char *);
82 extern	void	displaystats(void);
83 extern	double	distance(double, double, double, double);
84 extern	void	error(const char *);
85 extern	long	findname(char *, struct player *);
86 extern	void	initplayer(struct player *);
87 extern	void	leavegame(void);
88 extern	void	readmessage(void);
89 extern	void	tradingpost(void);
90 extern	void	truncstring(char *);
91 extern	void	writerecord(struct player *, long);
92 /* phantglobs.c */
93 extern	double	drandom(void);
94 
95 void	cleanup(bool);
96 void	genchar(int);
97 void	initialstate(void);
98 void	neatstuff(void);
99 void	playinit(void);
100 void	procmain(void);
101 long	recallplayer(void);
102 long	rollnewplayer(void);
103 void	titlelist(void);
104 
105 /*
106  * FUNCTION: initialize state, and call main process
107  *
108  * GLOBAL INPUTS: *Login, Throne, Wizard, Player, *stdscr, Changed, Databuf[],
109  *	Fileloc, Stattable[]
110  *
111  * GLOBAL OUTPUTS: Wizard, Player, Changed, Fileloc, Timeout, *Statptr
112  *
113  * DESCRIPTION:
114  *	Process arguments, initialize program, and loop forever processing
115  *	player input.
116  */
117 
118 int
119 main(int argc, char **argv)
120 {
121 	bool noheader = FALSE;	/* set if don't want header */
122 	bool headeronly = FALSE; /* set if only want header */
123 	bool examine = FALSE;	/* set if examine a character */
124 	time_t seconds;		/* for time of day */
125 	double dtemp;		/* for temporary calculations */
126 
127 	initialstate();		/* init globals */
128 
129 	/* process arguments */
130 	while (--argc && (*++argv)[0] == '-')
131 		switch ((*argv)[1]) {
132 		case 's':	/* short */
133 			noheader = TRUE;
134 			break;
135 
136 		case 'H':	/* Header */
137 			headeronly = TRUE;
138 			break;
139 
140 		case 'a':	/* all users */
141 			activelist();
142 			cleanup(TRUE);
143 			/* NOTREACHED */
144 
145 		case 'p':	/* purge old players */
146 			purgeoldplayers();
147 			cleanup(TRUE);
148 			/* NOTREACHED */
149 
150 		case 'S':	/* set 'Wizard' */
151 			Wizard = !getuid();
152 			break;
153 
154 		case 'x':	/* examine */
155 			examine = TRUE;
156 			break;
157 
158 		case 'm':	/* monsters */
159 			monstlist();
160 			cleanup(TRUE);
161 			/* NOTREACHED */
162 
163 		case 'b':	/* scoreboard */
164 			scorelist();
165 			cleanup(TRUE);
166 			/* NOTREACHED */
167 		}
168 
169 	if (!isatty(0))		/* don't let non-tty's play */
170 		cleanup(TRUE);
171 	/* NOTREACHED */
172 
173 	playinit();		/* set up to catch signals, init curses */
174 
175 	if (examine) {
176 		changestats(FALSE);
177 		cleanup(TRUE);
178 		/* NOTREACHED */
179 	}
180 
181 	if (!noheader) {
182 		titlelist();
183 		purgeoldplayers();	/* clean up old characters */
184 	}
185 
186 	if (headeronly)
187 		cleanup(TRUE);
188 	/* NOTREACHED */
189 
190 	do {
191 		/* get the player structure filled */
192 		Fileloc = -1L;
193 
194 		mvaddstr(22, 17, "Do you have a character to run [Q = Quit] ? ");
195 
196 		switch (getanswer("NYQ", FALSE)) {
197 		case 'Y':
198 			Fileloc = recallplayer();
199 			break;
200 
201 		case 'Q':
202 			cleanup(TRUE);
203 			/* NOTREACHED */
204 
205 		default:
206 			Fileloc = rollnewplayer();
207 			break;
208 		}
209 		clear();
210 	} while (Fileloc < 0L);
211 
212 	if (Player.p_level > 5.0)
213 		/* low level players have long timeout */
214 		Timeout = TRUE;
215 
216 	/* update some important player statistics */
217 	strcpy(Player.p_login, Login);
218 	time(&seconds);
219 	Player.p_lastused = localtime(&seconds)->tm_yday;
220 	Player.p_status = S_PLAYING;
221 	writerecord(&Player, Fileloc);
222 
223 	Statptr = &Stattable[Player.p_type];	/* initialize pointer */
224 
225 	/* catch interrupts */
226 #ifdef  BSD41
227 	sigset(SIGINT, interrupt);
228 #endif
229 #ifdef  BSD42
230 	signal(SIGINT, interrupt);
231 #endif
232 #ifdef  SYS3
233 	signal(SIGINT, interrupt);
234 #endif
235 #ifdef  SYS5
236 	signal(SIGINT, interrupt);
237 #endif
238 
239 	altercoordinates(Player.p_x, Player.p_y, A_FORCED);	/* set some flags */
240 
241 	clear();
242 
243 	for (;;) {
244 		/* loop forever, processing input */
245 
246 		adjuststats();	/* cleanup stats */
247 
248 		if (Throne && Player.p_crowns == 0 && Player.p_specialtype != SC_KING) {
249 			/* not allowed on throne -- move */
250 			mvaddstr(5, 0, "You're not allowed in the Lord's Chamber without a crown.\n");
251 			altercoordinates(0.0, 0.0, A_NEAR);
252 		}
253 
254 		checktampered();	/* check for energy voids, etc. */
255 
256 		if (Player.p_status != S_CLOAKED
257 		/* not cloaked */
258 		    && (dtemp = fabs(Player.p_x)) == fabs(Player.p_y)
259 		/* |x| = |y| */
260 		    && !Throne) {
261 			/* not on throne */
262 			dtemp = sqrt(dtemp / 100.0);
263 			if (floor(dtemp) == dtemp) {
264 				/* |x| / 100 == n*n; at a trading post */
265 				tradingpost();
266 				clear();
267 			}
268 		}
269 
270 		checkbattle();	/* check for player to player battle */
271 		neatstuff();	/* gurus, medics, etc. */
272 
273 		if (Player.p_status == S_CLOAKED) {
274 			/* costs 3 mana per turn to be cloaked */
275 			if (Player.p_mana > 3.0)
276 				Player.p_mana -= 3.0;
277 			else {
278 				/* ran out of mana, uncloak */
279 				Player.p_status = S_PLAYING;
280 				Changed = TRUE;
281 			}
282 		}
283 
284 		if (Player.p_status != S_PLAYING && Player.p_status != S_CLOAKED) {
285 			/* change status back to S_PLAYING */
286 			Player.p_status = S_PLAYING;
287 			Changed = TRUE;
288 		}
289 
290 		if (Changed) {
291 			/* update file only if important stuff has changed */
292 			writerecord(&Player, Fileloc);
293 			Changed = FALSE;
294 			continue;
295 		}
296 
297 		readmessage();	/* read message, if any */
298 
299 		displaystats();	/* print statistics */
300 
301 		move(6, 0);
302 
303 		if (Throne)
304 			/* maybe make king, print prompt, etc. */
305 			throneroom();
306 
307 		/* print status line */
308 		addstr("1:Move  2:Players  3:Talk  4:Stats  5:Quit  ");
309 		if (Player.p_level >= MEL_CLOAK && Player.p_magiclvl >= ML_CLOAK)
310 			addstr("6:Cloak  ");
311 		if (Player.p_level >= MEL_TELEPORT && Player.p_magiclvl >= ML_TELEPORT)
312 			addstr("7:Teleport  ");
313 		if (Player.p_specialtype >= SC_COUNCIL || Wizard)
314 			addstr("8:Intervene  ");
315 
316 		procmain();	/* process input */
317 	}
318 }
319 
320 /*
321  * FUNCTION: initialize some important global variable
322  *
323  * GLOBAL OUTPUTS: *Energyvoidfp, Echo, Marsh, *Login, Users, Beyond,
324  *	Throne, Wizard, Changed, Okcount, Timeout, Windows, *Monstfp, *Messagefp,
325  *	*Playersfp
326  *
327  * DESCRIPTION:
328  *	Set global flags, and open files which remain open.
329  */
330 
331 void
332 initialstate(void)
333 {
334 	Beyond = FALSE;
335 	Marsh = FALSE;
336 	Throne = FALSE;
337 	Changed = FALSE;
338 	Wizard = FALSE;
339 	Timeout = FALSE;
340 	Users = 0;
341 	Windows = FALSE;
342 	Echo = TRUE;
343 
344 	/* setup login name */
345 	if ((Login = getlogin()) == NULL)
346 		Login = getpwuid(getuid())->pw_name;
347 
348 	/* open some files */
349 	if ((Playersfp = fopen(_PATH_PEOPLE, "r+")) == NULL)
350 		error(_PATH_PEOPLE);
351 	/* NOTREACHED */
352 
353 	if ((Monstfp = fopen(_PATH_MONST, "r+")) == NULL)
354 		error(_PATH_MONST);
355 	/* NOTREACHED */
356 
357 	if ((Messagefp = fopen(_PATH_MESS, "r")) == NULL)
358 		error(_PATH_MESS);
359 	/* NOTREACHED */
360 
361 	if ((Energyvoidfp = fopen(_PATH_VOID, "r+")) == NULL)
362 		error(_PATH_VOID);
363 	/* NOTREACHED */
364 
365 	srandomdev();
366 }
367 
368 /*
369  * FUNCTION: roll up a new character
370  *
371  * GLOBAL INPUTS: Other, Wizard, Player, *stdscr, Databuf[]
372  *
373  * GLOBAL OUTPUTS: Echo
374  *
375  * DESCRIPTION:
376  *	Prompt player, and roll up new character.
377  */
378 
379 long
380 rollnewplayer(void)
381 {
382 	int chartype;		/* character type */
383 	int ch;			/* input */
384 
385 	initplayer(&Player);	/* initialize player structure */
386 
387 	clear();
388 	mvaddstr(4, 21, "Which type of character do you want:");
389 	mvaddstr(8, 4, "1:Magic User  2:Fighter  3:Elf  4:Dwarf  5:Halfling  6:Experimento  ");
390 	if (Wizard) {
391 		addstr("7:Super  ? ");
392 		chartype = getanswer("1234567", FALSE);
393 	} else {
394 		addstr("?  ");
395 		chartype = getanswer("123456", FALSE);
396 	}
397 
398 	do {
399 		genchar(chartype);	/* roll up a character */
400 
401 		/* print out results */
402 		mvprintw(12, 14,
403 		    "Strength    :  %2.0f  Quickness:  %2.0f  Mana       :  %2.0f\n",
404 		    Player.p_strength, Player.p_quickness, Player.p_mana);
405 		mvprintw(13, 14,
406 		    "Energy Level:  %2.0f  Brains   :  %2.0f  Magic Level:  %2.0f\n",
407 		    Player.p_energy, Player.p_brains, Player.p_magiclvl);
408 
409 		if (Player.p_type == C_EXPER || Player.p_type == C_SUPER)
410 			break;
411 
412 		mvaddstr(14, 14, "Type '1' to keep >");
413 		ch = getanswer(" ", TRUE);
414 	} while (ch != '1');
415 
416 	if (Player.p_type == C_EXPER || Player.p_type == C_SUPER)
417 		/* get coordinates for experimento */
418 		for (;;) {
419 			mvaddstr(16, 0, "Enter the X Y coordinates of your experimento ? ");
420 			getstring(Databuf, SZ_DATABUF);
421 			sscanf(Databuf, "%lf %lf", &Player.p_x, &Player.p_y);
422 
423 			if (fabs(Player.p_x) > D_EXPER || fabs(Player.p_y) > D_EXPER)
424 				mvaddstr(17, 0, "Invalid coordinates.  Try again.\n");
425 			else
426 				break;
427 		}
428 
429 	for (;;) {
430 		/* name the new character */
431 		mvprintw(18, 0,
432 		    "Give your character a name [up to %d characters] ?  ", SZ_NAME - 1);
433 		getstring(Player.p_name, SZ_NAME);
434 		truncstring(Player.p_name);	/* remove trailing blanks */
435 
436 		if (Player.p_name[0] == '\0')
437 			/* no null names */
438 			mvaddstr(19, 0, "Invalid name.");
439 		else if (findname(Player.p_name, &Other) >= 0L)
440 			/* cannot have duplicate names */
441 			mvaddstr(19, 0, "Name already in use.");
442 		else
443 			/* name is acceptable */
444 			break;
445 
446 		addstr("  Pick another.\n");
447 	}
448 
449 	/* get a password for character */
450 	Echo = FALSE;
451 
452 	do {
453 		mvaddstr(20, 0, "Give your character a password [up to 8 characters] ? ");
454 		getstring(Player.p_password, SZ_PASSWORD);
455 		mvaddstr(21, 0, "One more time to verify ? ");
456 		getstring(Databuf, SZ_PASSWORD);
457 	} while (strcmp(Player.p_password, Databuf) != 0);
458 
459 	Echo = TRUE;
460 
461 	return (allocrecord());
462 }
463 
464 /*
465  * FUNCTION: process input from player
466  *
467  * GLOBAL INPUTS: Circle, Illcmd[], Throne, Wizard, Player, *stdscr,
468  *	Databuf[], Illmove[]
469  *
470  * GLOBAL OUTPUTS: Player, Changed
471  *
472  * DESCRIPTION:
473  *	Process main menu options.
474  */
475 
476 void
477 procmain(void)
478 {
479 	int ch;			/* input */
480 	double x;		/* desired new x coordinate */
481 	double y;		/* desired new y coordinate */
482 	double temp;		/* for temporary calculations */
483 	FILE *fp;		/* for opening files */
484 	int loop;		/* a loop counter */
485 	bool hasmoved = FALSE;	/* set if player has moved */
486 
487 	ch = inputoption();
488 	mvaddstr(4, 0, "\n\n");	/* clear status area */
489 
490 	move(7, 0);
491 	clrtobot();		/* clear data on bottom area of screen */
492 
493 	if (Player.p_specialtype == SC_VALAR && (ch == '1' || ch == '7'))
494 		/* valar cannot move */
495 		ch = ' ';
496 
497 	switch (ch) {
498 	case 'K':		/* move up/north */
499 	case 'N':
500 		x = Player.p_x;
501 		y = Player.p_y + MAXMOVE();
502 		hasmoved = TRUE;
503 		break;
504 
505 	case 'J':		/* move down/south */
506 	case 'S':
507 		x = Player.p_x;
508 		y = Player.p_y - MAXMOVE();
509 		hasmoved = TRUE;
510 		break;
511 
512 	case 'L':		/* move right/east */
513 	case 'E':
514 		x = Player.p_x + MAXMOVE();
515 		y = Player.p_y;
516 		hasmoved = TRUE;
517 		break;
518 
519 	case 'H':		/* move left/west */
520 	case 'W':
521 		x = Player.p_x - MAXMOVE();
522 		y = Player.p_y;
523 		hasmoved = TRUE;
524 		break;
525 
526 	default:		/* rest */
527 		Player.p_energy += (Player.p_maxenergy + Player.p_shield) / 15.0 +
528 		    Player.p_level / 3.0 + 2.0;
529 		Player.p_energy =
530 		    MIN(Player.p_energy, Player.p_maxenergy + Player.p_shield);
531 
532 		if (Player.p_status != S_CLOAKED) {
533 			/* cannot find mana if cloaked */
534 			Player.p_mana += (Circle + Player.p_level) / 4.0;
535 
536 			if (drandom() < 0.2 && Player.p_status == S_PLAYING && !Throne)
537 				/* wandering monster */
538 				encounter(-1);
539 		}
540 		break;
541 
542 	case 'X':		/* change/examine a character */
543 		changestats(TRUE);
544 		break;
545 
546 	case '1':		/* move */
547 		for (loop = 3; loop; --loop) {
548 			mvaddstr(4, 0, "X Y Coordinates ? ");
549 			getstring(Databuf, SZ_DATABUF);
550 
551 			if (sscanf(Databuf, "%lf %lf", &x, &y) != 2)
552 				mvaddstr(5, 0, "Try again\n");
553 			else if (distance(Player.p_x, x, Player.p_y, y) > MAXMOVE())
554 				ILLMOVE();
555 			else {
556 				hasmoved = TRUE;
557 				break;
558 			}
559 		}
560 		break;
561 
562 	case '2':		/* players */
563 		userlist(TRUE);
564 		break;
565 
566 	case '3':		/* message */
567 		mvaddstr(4, 0, "Message ? ");
568 		getstring(Databuf, SZ_DATABUF);
569 		/* we open the file for writing to erase any data which is already there */
570 		fp = fopen(_PATH_MESS, "w");
571 		if (Databuf[0] != '\0')
572 			fprintf(fp, "%s: %s", Player.p_name, Databuf);
573 		fclose(fp);
574 		break;
575 
576 	case '4':		/* stats */
577 		allstatslist();
578 		break;
579 
580 	case '5':		/* good-bye */
581 		leavegame();
582 		/* NOTREACHED */
583 
584 	case '6':		/* cloak */
585 		if (Player.p_level < MEL_CLOAK || Player.p_magiclvl < ML_CLOAK)
586 			ILLCMD();
587 		else if (Player.p_status == S_CLOAKED)
588 			Player.p_status = S_PLAYING;
589 		else if (Player.p_mana < MM_CLOAK)
590 			mvaddstr(5, 0, "No mana left.\n");
591 		else {
592 			Changed = TRUE;
593 			Player.p_mana -= MM_CLOAK;
594 			Player.p_status = S_CLOAKED;
595 		}
596 		break;
597 
598 	case '7':		/* teleport */
599 
600 		/*
601 		 * conditions for teleport
602 		 *	- 20 per (level plus magic level)
603 		 *	- OR council of the wise or valar or ex-valar
604 		 *	- OR transport from throne
605 		 * transports from throne cost no mana
606 		 */
607 		if (Player.p_level < MEL_TELEPORT || Player.p_magiclvl < ML_TELEPORT)
608 			ILLCMD();
609 		else
610 			for (loop = 3; loop; --loop) {
611 				mvaddstr(4, 0, "X Y Coordinates ? ");
612 				getstring(Databuf, SZ_DATABUF);
613 
614 				if (sscanf(Databuf, "%lf %lf", &x, &y) == 2) {
615 					temp = distance(Player.p_x, x, Player.p_y, y);
616 					if (!Throne
617 					/* can transport anywhere from throne */
618 					    && Player.p_specialtype <= SC_COUNCIL
619 					/* council, valar can transport anywhere */
620 					    && temp > (Player.p_level + Player.p_magiclvl) * 20.0)
621 						/* can only move 20 per exp. level + mag. level */
622 						ILLMOVE();
623 					else {
624 						temp = (temp / 75.0 + 1.0) * 20.0;	/* mana used */
625 
626 						if (!Throne && temp > Player.p_mana)
627 							mvaddstr(5, 0, "Not enough power for that distance.\n");
628 						else {
629 							if (!Throne)
630 								Player.p_mana -= temp;
631 							hasmoved = TRUE;
632 							break;
633 						}
634 					}
635 				}
636 			}
637 		break;
638 
639 	case 'C':
640 	case '9':		/* monster */
641 		if (Throne)
642 			/* no monsters while on throne */
643 			mvaddstr(5, 0, "No monsters in the chamber!\n");
644 		else if (Player.p_specialtype != SC_VALAR) {
645 			/* the valar cannot call monsters */
646 			Player.p_sin += 1e-6;
647 			encounter(-1);
648 		}
649 		break;
650 
651 	case '0':		/* decree */
652 		if (Wizard || (Player.p_specialtype == SC_KING && Throne))
653 			/* kings must be on throne to decree */
654 			dotampered();
655 		else
656 			ILLCMD();
657 		break;
658 
659 	case '8':		/* intervention */
660 		if (Wizard || Player.p_specialtype >= SC_COUNCIL)
661 			dotampered();
662 		else
663 			ILLCMD();
664 		break;
665 	}
666 
667 	if (hasmoved) {
668 		/* player has moved -- alter coordinates, and do random monster */
669 		altercoordinates(x, y, A_SPECIFIC);
670 
671 		if (drandom() < 0.2 && Player.p_status == S_PLAYING && !Throne)
672 			encounter(-1);
673 	}
674 }
675 
676 /*
677  * FUNCTION: print title page
678  *
679  * GLOBAL INPUTS: Lines, Other, *stdscr, Databuf[], *Playersfp
680  *
681  * GLOBAL OUTPUTS: Lines
682  *
683  * DESCRIPTION:
684  *	Print important information about game, players, etc.
685  */
686 
687 void
688 titlelist(void)
689 {
690 	FILE *fp;			/* used for opening various files */
691 	bool councilfound = FALSE;	/* set if we find a member of the council */
692 	bool kingfound = FALSE;		/* set if we find a king */
693 	double hiexp, nxtexp;		/* used for finding the two highest players */
694 	double hilvl, nxtlvl;		/* used for finding the two highest players */
695 	char hiname[21], nxtname[21];	/* used for finding the two highest players */
696 
697 	nxtexp = 0;
698 	mvaddstr(0, 14, "W e l c o m e   t o   P h a n t a s i a (vers. 3.3.2)!");
699 
700 	/* print message of the day */
701 	if ((fp = fopen(_PATH_MOTD, "r")) != NULL
702 	    && fgets(Databuf, SZ_DATABUF, fp) != NULL) {
703 		mvaddstr(2, 40 - strlen(Databuf) / 2, Databuf);
704 		fclose(fp);
705 	}
706 
707 	/* search for king */
708 	fseek(Playersfp, 0L, SEEK_SET);
709 	while (fread((char *)&Other, SZ_PLAYERSTRUCT, 1, Playersfp) == 1)
710 		if (Other.p_specialtype == SC_KING &&
711 		    Other.p_status != S_NOTUSED) {
712 			/* found the king */
713 			sprintf(Databuf, "The present ruler is %s  Level:%.0f",
714 			    Other.p_name, Other.p_level);
715 			mvaddstr(4, 40 - strlen(Databuf) / 2, Databuf);
716 			kingfound = TRUE;
717 			break;
718 		}
719 
720 	if (!kingfound)
721 		mvaddstr(4, 24, "There is no ruler at this time.");
722 
723 	/* search for valar */
724 	fseek(Playersfp, 0L, SEEK_SET);
725 	while (fread((char *)&Other, SZ_PLAYERSTRUCT, 1, Playersfp) == 1)
726 		if (Other.p_specialtype == SC_VALAR && Other.p_status != S_NOTUSED) {
727 			/* found the valar */
728 			sprintf(Databuf, "The Valar is %s   Login:  %s",
729 			    Other.p_name, Other.p_login);
730 			mvaddstr(6, 40 - strlen(Databuf) / 2, Databuf);
731 			break;
732 		}
733 
734 	/* search for council of the wise */
735 	fseek(Playersfp, 0L, SEEK_SET);
736 	Lines = 10;
737 	while (fread((char *)&Other, SZ_PLAYERSTRUCT, 1, Playersfp) == 1)
738 		if (Other.p_specialtype == SC_COUNCIL && Other.p_status != S_NOTUSED) {
739 			/* found a member of the council */
740 			if (!councilfound) {
741 				mvaddstr(8, 30, "Council of the Wise:");
742 				councilfound = TRUE;
743 			}
744 
745 			/* This assumes a finite (<=5) number of C.O.W.: */
746 			sprintf(Databuf, "%s   Login:  %s", Other.p_name, Other.p_login);
747 			mvaddstr(Lines++, 40 - strlen(Databuf) / 2, Databuf);
748 		}
749 
750 	/* search for the two highest players */
751 	nxtname[0] = hiname[0] = '\0';
752 	hiexp = 0.0;
753 	nxtlvl = hilvl = 0;
754 
755 	fseek(Playersfp, 0L, SEEK_SET);
756 	while (fread((char *)&Other, SZ_PLAYERSTRUCT, 1, Playersfp) == 1)
757 		if (Other.p_experience > hiexp && Other.p_specialtype <= SC_KING && Other.p_status != S_NOTUSED) {
758 			/* highest found so far */
759 			nxtexp = hiexp;
760 			hiexp = Other.p_experience;
761 			nxtlvl = hilvl;
762 			hilvl = Other.p_level;
763 			strcpy(nxtname, hiname);
764 			strcpy(hiname, Other.p_name);
765 		} else if (Other.p_experience > nxtexp &&
766 			   Other.p_specialtype <= SC_KING &&
767 			   Other.p_status != S_NOTUSED) {
768 			/* next highest found so far */
769 			nxtexp = Other.p_experience;
770 			nxtlvl = Other.p_level;
771 			strcpy(nxtname, Other.p_name);
772 		}
773 
774 	mvaddstr(15, 28, "Highest characters are:");
775 	sprintf(Databuf, "%s  Level:%.0f   and   %s  Level:%.0f",
776 	    hiname, hilvl, nxtname, nxtlvl);
777 	mvaddstr(17, 40 - strlen(Databuf) / 2, Databuf);
778 
779 	/* print last to die */
780 	if ((fp = fopen(_PATH_LASTDEAD, "r")) != NULL
781 	    && fgets(Databuf, SZ_DATABUF, fp) != NULL) {
782 		mvaddstr(19, 25, "The last character to die was:");
783 		mvaddstr(20, 40 - strlen(Databuf) / 2, Databuf);
784 		fclose(fp);
785 	}
786 
787 	refresh();
788 }
789 
790 /*
791  * FUNCTION: find a character on file
792  *
793  * GLOBAL INPUTS: Player, *stdscr, Databuf[]
794  *
795  * GLOBAL OUTPUTS: Echo, Player
796  *
797  * DESCRIPTION:
798  *	Search for a character of a certain name, and check password.
799  */
800 
801 long
802 recallplayer(void)
803 {
804 	long loc = 0L;		/* location in player file */
805 	int loop;		/* loop counter */
806 	int ch;			/* input */
807 
808 	clear();
809 	mvprintw(10, 0, "What was your character's name ? ");
810 	getstring(Databuf, SZ_NAME);
811 	truncstring(Databuf);
812 
813 	if ((loc = findname(Databuf, &Player)) >= 0L) {
814 		/* found character */
815 		Echo = FALSE;
816 
817 		for (loop = 0; loop < 2; ++loop) {
818 			/* prompt for password */
819 			mvaddstr(11, 0, "Password ? ");
820 			getstring(Databuf, SZ_PASSWORD);
821 			if (strcmp(Databuf, Player.p_password) == 0) {
822 				/* password good */
823 				Echo = TRUE;
824 
825 				if (Player.p_status != S_OFF) {
826 					/* player did not exit normally last time */
827 					clear();
828 					addstr("Your character did not exit normally last time.\n");
829 					addstr("If you think you have good cause to have your character saved,\n");
830 					printw("you may quit and mail your reason to 'root'.\n");
831 					addstr("Otherwise, continuing spells certain death.\n");
832 					addstr("Do you want to quit ? ");
833 					ch = getanswer("YN", FALSE);
834 					if (ch == 'Y') {
835 						Player.p_status = S_HUNGUP;
836 						writerecord(&Player, loc);
837 						cleanup(TRUE);
838 						/* NOTREACHED */
839 					}
840 					death("Stupidity");
841 					/* NOTREACHED */
842 				}
843 				return (loc);
844 			} else
845 				mvaddstr(12, 0, "No good.\n");
846 		}
847 
848 		Echo = TRUE;
849 	} else
850 		mvaddstr(11, 0, "Not found.\n");
851 
852 	more(13);
853 	return (-1L);
854 }
855 
856 /*
857  * FUNCTION: do random stuff
858  *
859  * GLOBAL INPUTS: Player, *stdscr, *Statptr
860  *
861  * GLOBAL OUTPUTS: Player
862  *
863  * DESCRIPTION:
864  *	Handle gurus, medics, etc.
865  */
866 
867 void
868 neatstuff(void)
869 {
870 	double temp;	/* for temporary calculations */
871 	int ch;		/* input */
872 
873 	switch ((int)ROLL(0.0, 100.0)) {
874 	case 1:
875 	case 2:
876 		if (Player.p_poison > 0.0) {
877 			mvaddstr(4, 0, "You've found a medic!  How much will you offer to be cured ? ");
878 			temp = floor(infloat());
879 			if (temp < 0.0 || temp > Player.p_gold) {
880 				/* negative gold, or more than available */
881 				mvaddstr(6, 0, "He was not amused, and made you worse.\n");
882 				Player.p_poison += 1.0;
883 			} else if (drandom() / 2.0 > (temp + 1.0) / MAX(Player.p_gold, 1))
884 				/* medic wants 1/2 of available gold */
885 				mvaddstr(5, 0, "Sorry, he wasn't interested.\n");
886 			else {
887 				mvaddstr(5, 0, "He accepted.");
888 				Player.p_poison = MAX(0.0, Player.p_poison - 1.0);
889 				Player.p_gold -= temp;
890 			}
891 		}
892 		break;
893 
894 	case 3:
895 		mvaddstr(4, 0, "You've been caught raping and pillaging!\n");
896 		Player.p_experience += 4000.0;
897 		Player.p_sin += 0.5;
898 		break;
899 
900 	case 4:
901 		temp = ROLL(10.0, 75.0);
902 		mvprintw(4, 0, "You've found %.0f gold pieces, want them ? ", temp);
903 		ch = getanswer("NY", FALSE);
904 
905 		if (ch == 'Y')
906 			collecttaxes(temp, 0.0);
907 		break;
908 
909 	case 5:
910 		if (Player.p_sin > 1.0) {
911 			mvaddstr(4, 0, "You've found a Holy Orb!\n");
912 			Player.p_sin -= 0.25;
913 		}
914 		break;
915 
916 	case 6:
917 		if (Player.p_poison < 1.0) {
918 			mvaddstr(4, 0, "You've been hit with a plague!\n");
919 			Player.p_poison += 1.0;
920 		}
921 		break;
922 
923 	case 7:
924 		mvaddstr(4, 0, "You've found some holy water.\n");
925 		++Player.p_holywater;
926 		break;
927 
928 	case 8:
929 		mvaddstr(4, 0, "You've met a Guru. . .");
930 		if (drandom() * Player.p_sin > 1.0)
931 			addstr("You disgusted him with your sins!\n");
932 		else if (Player.p_poison > 0.0) {
933 			addstr("He looked kindly upon you, and cured you.\n");
934 			Player.p_poison = 0.0;
935 		} else {
936 			addstr("He rewarded you for your virtue.\n");
937 			Player.p_mana += 50.0;
938 			Player.p_shield += 2.0;
939 		}
940 		break;
941 
942 	case 9:
943 		mvaddstr(4, 0, "You've found an amulet.\n");
944 		++Player.p_amulets;
945 		break;
946 
947 	case 10:
948 		if (Player.p_blindness) {
949 			mvaddstr(4, 0, "You've regained your sight!\n");
950 			Player.p_blindness = FALSE;
951 		}
952 		break;
953 
954 	default:		/* deal with poison */
955 		if (Player.p_poison > 0.0) {
956 			temp = Player.p_poison * Statptr->c_weakness
957 			    * Player.p_maxenergy / 600.0;
958 			if (Player.p_energy > Player.p_maxenergy / 10.0
959 			    && temp + 5.0 < Player.p_energy)
960 				Player.p_energy -= temp;
961 		}
962 		break;
963 	}
964 }
965 
966 /*
967  * FUNCTION: generate a random character
968  *
969  * ARGUMENTS:
970  *	int type - ASCII value of character type to generate
971  *
972  * GLOBAL INPUTS: Wizard, Player, Stattable[]
973  *
974  * GLOBAL OUTPUTS: Player
975  *
976  * DESCRIPTION:
977  *	Use the lookup table for rolling stats.
978  */
979 
980 void
981 genchar(int type)
982 {
983 	int subscript;			/* used for subscripting into Stattable */
984 	struct charstats *statptr;	/* for pointing into Stattable */
985 
986 	subscript = type - '1';
987 
988 	if (subscript < C_MAGIC || subscript > C_EXPER)
989 		if (subscript != C_SUPER || !Wizard)
990 			/* fighter is default */
991 			subscript = C_FIGHTER;
992 
993 	statptr = &Stattable[subscript];
994 
995 	Player.p_quickness =
996 	    ROLL(statptr->c_quickness.base, statptr->c_quickness.interval);
997 	Player.p_strength =
998 	    ROLL(statptr->c_strength.base, statptr->c_strength.interval);
999 	Player.p_mana =
1000 	    ROLL(statptr->c_mana.base, statptr->c_mana.interval);
1001 	Player.p_maxenergy =
1002 	    Player.p_energy =
1003 	    ROLL(statptr->c_energy.base, statptr->c_energy.interval);
1004 	Player.p_brains =
1005 	    ROLL(statptr->c_brains.base, statptr->c_brains.interval);
1006 	Player.p_magiclvl =
1007 	    ROLL(statptr->c_magiclvl.base, statptr->c_magiclvl.interval);
1008 
1009 	Player.p_type = subscript;
1010 
1011 	if (Player.p_type == C_HALFLING)
1012 		/* give halfling some experience */
1013 		Player.p_experience = ROLL(600.0, 200.0);
1014 }
1015 
1016 /*
1017  * FUNCTION: initialize for playing game
1018  *
1019  * GLOBAL INPUTS: *stdscr, ill_sig()
1020  *
1021  * GLOBAL OUTPUTS: Windows
1022  *
1023  * DESCRIPTION:
1024  *	Catch a bunch of signals, and turn on curses stuff.
1025  */
1026 
1027 void
1028 playinit(void)
1029 {
1030 	/* catch/ingnore signals */
1031 
1032 #ifdef	BSD41
1033 	sigignore(SIGQUIT);
1034 	sigignore(SIGALRM);
1035 	sigignore(SIGTERM);
1036 	sigignore(SIGTSTP);
1037 	sigignore(SIGTTIN);
1038 	sigignore(SIGTTOU);
1039 	sighold(SIGINT);
1040 	sigset(SIGHUP, ill_sig);
1041 	sigset(SIGTRAP, ill_sig);
1042 	sigset(SIGIOT, ill_sig);
1043 	sigset(SIGEMT, ill_sig);
1044 	sigset(SIGFPE, ill_sig);
1045 	sigset(SIGBUS, ill_sig);
1046 	sigset(SIGSEGV, ill_sig);
1047 	sigset(SIGSYS, ill_sig);
1048 	sigset(SIGPIPE, ill_sig);
1049 #endif
1050 #ifdef	BSD42
1051 	signal(SIGQUIT, ill_sig);
1052 	signal(SIGALRM, SIG_IGN);
1053 	signal(SIGTERM, SIG_IGN);
1054 	signal(SIGTSTP, SIG_IGN);
1055 	signal(SIGTTIN, SIG_IGN);
1056 	signal(SIGTTOU, SIG_IGN);
1057 	signal(SIGINT, ill_sig);
1058 	signal(SIGHUP, SIG_DFL);
1059 	signal(SIGTRAP, ill_sig);
1060 	signal(SIGIOT, ill_sig);
1061 	signal(SIGEMT, ill_sig);
1062 	signal(SIGFPE, ill_sig);
1063 	signal(SIGBUS, ill_sig);
1064 	signal(SIGSEGV, ill_sig);
1065 	signal(SIGSYS, ill_sig);
1066 	signal(SIGPIPE, ill_sig);
1067 #endif
1068 #ifdef	SYS3
1069 	signal(SIGINT, SIG_IGN);
1070 	signal(SIGQUIT, SIG_IGN);
1071 	signal(SIGTERM, SIG_IGN);
1072 	signal(SIGALRM, SIG_IGN);
1073 	signal(SIGHUP, ill_sig);
1074 	signal(SIGTRAP, ill_sig);
1075 	signal(SIGIOT, ill_sig);
1076 	signal(SIGEMT, ill_sig);
1077 	signal(SIGFPE, ill_sig);
1078 	signal(SIGBUS, ill_sig);
1079 	signal(SIGSEGV, ill_sig);
1080 	signal(SIGSYS, ill_sig);
1081 	signal(SIGPIPE, ill_sig);
1082 #endif
1083 #ifdef	SYS5
1084 	signal(SIGINT, SIG_IGN);
1085 	signal(SIGQUIT, SIG_IGN);
1086 	signal(SIGTERM, SIG_IGN);
1087 	signal(SIGALRM, SIG_IGN);
1088 	signal(SIGHUP, ill_sig);
1089 	signal(SIGTRAP, ill_sig);
1090 	signal(SIGIOT, ill_sig);
1091 	signal(SIGEMT, ill_sig);
1092 	signal(SIGFPE, ill_sig);
1093 	signal(SIGBUS, ill_sig);
1094 	signal(SIGSEGV, ill_sig);
1095 	signal(SIGSYS, ill_sig);
1096 	signal(SIGPIPE, ill_sig);
1097 #endif
1098 
1099 	initscr();		/* turn on curses */
1100 	noecho();		/* do not echo input */
1101 	cbreak();		/* do not process erase, kill */
1102 	clear();
1103 	refresh();
1104 	Windows = TRUE;		/* mark the state */
1105 }
1106 
1107 
1108 /*
1109  * FUNCTION: close some files, and maybe exit
1110  *
1111  * ARGUMENTS:
1112  *	bool doexit - exit flag
1113  *
1114  * GLOBAL INPUTS: *Energyvoidfp, LINES, *stdscr, Windows, *Monstfp,
1115  *	*Messagefp, *Playersfp
1116  *
1117  * DESCRIPTION:
1118  *	Close all open files.  If we are "in curses" terminate curses.
1119  *	If 'doexit' is set, exit, otherwise return.
1120  */
1121 
1122 void
1123 cleanup(bool doexit)
1124 {
1125 	if (Windows) {
1126 		move(LINES - 2, 0);
1127 		refresh();
1128 		nocbreak();
1129 		endwin();
1130 	}
1131 	if (Playersfp) {
1132 		fclose(Playersfp);
1133 		Playersfp = NULL;
1134 	}
1135 	if (Monstfp) {
1136 		fclose(Monstfp);
1137 		Monstfp = NULL;
1138 	}
1139 	if (Messagefp) {
1140 		fclose(Messagefp);
1141 		Messagefp = NULL;
1142 	}
1143 	if (Energyvoidfp) {
1144 		fclose(Energyvoidfp);
1145 		Energyvoidfp = NULL;
1146 	}
1147 
1148 	if (doexit)
1149 		exit(0);
1150 	/* NOTREACHED */
1151 }
1152