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