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