xref: /openbsd/games/phantasia/main.c (revision ce7279d8)
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 
105 int
main(int argc,char ** argv)106 main(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 
313 void
initialstate(void)314 initialstate(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 
394 long
rollnewplayer(void)395 rollnewplayer(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 
511 void
procmain(void)512 procmain(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 
744 void
titlelist(void)745 titlelist(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 
878 long
recallplayer(void)879 recallplayer(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 
959 void
neatstuff(void)960 neatstuff(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 
1083 void
genchar(int type)1084 genchar(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 
1143 void
playinit(void)1144 playinit(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 
1180 void
cleanup(int doexit)1181 cleanup(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