xref: /dragonfly/games/phantasia/misc.c (revision 4f91f2b658e9de751719e25f0c9507f2e9afec27)
1 /*        $NetBSD: misc.c,v 1.21 2011/09/01 07:18:50 plunky Exp $     */
2 
3 /*
4  * misc.c  Phantasia miscellaneous support routines
5  */
6 
7 #include <string.h>
8 #include "include.h"
9 
10 static double explevel(double);
11 
12 /*
13  * FUNCTION: move player to new level
14  *
15  * GLOBAL INPUTS: Player, *stdscr, *Statptr, Stattable[]
16  *
17  * GLOBAL OUTPUTS: Player, Changed
18  *
19  * DESCRIPTION:
20  *        Use lookup table to increment important statistics when
21  *        progressing to new experience level.
22  *        Players are rested to maximum as a bonus for making a new
23  *        level.
24  *        Check for council of wise, and being too big to be king.
25  */
26 
27 static void
movelevel(void)28 movelevel(void)
29 {
30           const struct charstats *statptr; /* for pointing into Stattable */
31           double new;                   /* new level */
32           double inc;                   /* increment between new and old levels */
33 
34           Changed = TRUE;
35 
36           if (Player.p_type == C_EXPER)
37                     /* roll a type to use for increment */
38                     statptr = &Stattable[(int)ROLL(C_MAGIC, C_HALFLING - C_MAGIC + 1)];
39           else
40                     statptr = Statptr;
41 
42           new = explevel(Player.p_experience);
43           inc = new - Player.p_level;
44           Player.p_level = new;
45 
46           /* add increments to statistics */
47           Player.p_strength += statptr->c_strength.increase * inc;
48           Player.p_mana += statptr->c_mana.increase * inc;
49           Player.p_brains += statptr->c_brains.increase * inc;
50           Player.p_magiclvl += statptr->c_magiclvl.increase * inc;
51           Player.p_maxenergy += statptr->c_energy.increase * inc;
52 
53           /* rest to maximum upon reaching new level */
54           Player.p_energy = Player.p_maxenergy + Player.p_shield;
55 
56           if (Player.p_crowns > 0 && Player.p_level >= 1000.0) {
57                     /* no longer able to be king -- turn crowns into cash */
58                     Player.p_gold += ((double)Player.p_crowns) * 5000.0;
59                     Player.p_crowns = 0;
60           }
61 
62           if (Player.p_level >= 3000.0 && Player.p_specialtype < SC_COUNCIL) {
63                     /* make a member of the council */
64                     mvaddstr(6, 0, "You have made it to the Council of the Wise.\n");
65                     addstr("Good Luck on your search for the Holy Grail.\n");
66 
67                     Player.p_specialtype = SC_COUNCIL;
68 
69                     /* no rings for council and above */
70                     Player.p_ring.ring_type = R_NONE;
71                     Player.p_ring.ring_duration = 0;
72 
73                     Player.p_lives = 3; /* three extra lives */
74           }
75 
76           if (Player.p_level > 9999.0 && Player.p_specialtype != SC_VALAR)
77                     death("Old age");
78 }
79 
80 /*
81  * FUNCTION: return a formatted description of location
82  *
83  * ARGUMENTS:
84  *        struct player playerp - pointer to player structure
85  *        bool shortflag - set if short form is desired
86  *
87  * RETURN VALUE: pointer to string containing result
88  *
89  * GLOBAL INPUTS: Databuf[]
90  *
91  * DESCRIPTION:
92  *        Look at coordinates and return an appropriately formatted
93  *        string.
94  */
95 
96 const char *
descrlocation(struct player * playerp,bool shortflag)97 descrlocation(struct player *playerp, bool shortflag)
98 {
99           double circle;                /* corresponding circle for coordinates */
100           int quadrant;                 /* quadrant of grid */
101           const char *label;  /* pointer to place name */
102           static const char *nametable[4][4] =    /* names of places */
103           {
104                     { "Anorien",          "Ithilien",       "Rohan",        "Lorien"         },
105                     { "Gondor",           "Mordor",         "Dunland",                "Rovanion"       },
106                     { "South Gondor", "Khand",    "Eriador",                "The Iron Hills" },
107                     { "Far Harad",        "Near Harad",     "The Northern Waste", "Rhun"           }
108           };
109 
110           if (playerp->p_specialtype == SC_VALAR)
111                     return (" is in Valhala");
112           else if ((circle = CIRCLE(playerp->p_x, playerp->p_y)) >= 1000.0) {
113                     if (MAX(fabs(playerp->p_x), fabs(playerp->p_y)) > D_BEYOND)
114                               label = "The Point of No Return";
115                     else
116                               label = "The Ashen Mountains";
117           } else if (circle >= 55)
118                     label = "Morannon";
119           else if (circle >= 35)
120                     label = "Kennaquahair";
121           else if (circle >= 20)
122                     label = "The Dead Marshes";
123           else if (circle >= 9)
124                     label = "The Outer Waste";
125           else if (circle >= 5)
126                     label = "The Moors Adventurous";
127           else {
128                     if (playerp->p_x == 0.0 && playerp->p_y == 0.0)
129                               label = "The Lord's Chamber";
130                     else {
131                               /* this expression is split to prevent compiler loop with some compilers */
132                               quadrant = ((playerp->p_x > 0.0) ? 1 : 0);
133                               quadrant += ((playerp->p_y >= 0.0) ? 2 : 0);
134                               label = nametable[((int)circle) - 1][quadrant];
135                     }
136           }
137 
138           if (shortflag)
139                     sprintf(Databuf, "%.29s", label);
140           else
141                     sprintf(Databuf, " is in %s  (%.0f,%.0f)", label, playerp->p_x, playerp->p_y);
142 
143           return (Databuf);
144 }
145 
146 /*
147  * FUNCTION: do trading post stuff
148  *
149  * GLOBAL INPUTS: Menu[], Circle, Player, *stdscr, Fileloc, Nobetter[]
150  *
151  * GLOBAL OUTPUTS: Player
152  *
153  * DESCRIPTION:
154  *        Different trading posts have different items.
155  *        Merchants cannot be cheated, but they can be dishonest
156  *        themselves.
157  *
158  *        Shields, swords, and quicksilver are not cumulative.  This is
159  *        one major area of complaint, but there are two reasons for this:
160  *                  1) It becomes MUCH too easy to make very large versions
161  *                     of these items.
162  *                  2) In the real world, one cannot simply weld two swords
163  *                     together to make a bigger one.
164  *
165  *        At one time, it was possible to sell old weapons at half the purchase
166  *        price.  This resulted in huge amounts of gold floating around,
167  *        and the game lost much of its challenge.
168  *
169  *        Also, purchasing gems defeats the whole purpose of gold.  Gold
170  *        is small change for lower level players.  They really shouldn't
171  *        be able to accumulate more than enough gold for a small sword or
172  *        a few books.  Higher level players shouldn't even bother to pick
173  *        up gold, except maybe to buy mana once in a while.
174  */
175 
176 void
tradingpost(void)177 tradingpost(void)
178 {
179           double numitems;    /* number of items to purchase */
180           double cost;                  /* cost of purchase */
181           double blessingcost;          /* cost of blessing */
182           int ch;                       /* input */
183           int size;           /* size of the trading post */
184           int loop;           /* loop counter */
185           int cheat = 0;                /* number of times player has tried to cheat */
186           bool dishonest = FALSE;       /* set when merchant is dishonest */
187 
188           Player.p_status = S_TRADING;
189           writerecord(&Player, Fileloc);
190 
191           clear();
192           addstr("You are at a trading post. All purchases must be made with gold.");
193 
194           size = sqrt(fabs(Player.p_x / 100)) + 1;
195           size = MIN(7, size);
196 
197           /* set up cost of blessing */
198           blessingcost = 1000.0 * (Player.p_level + 5.0);
199 
200           /* print Menu */
201           move(7, 0);
202           for (loop = 0; loop < size; ++loop) {
203                     /* print Menu */
204                     if (loop == 6)
205                               cost = blessingcost;
206                     else
207                               cost = Menu[loop].cost;
208                     printw("(%d) %-12s: %6.0f\n", loop + 1, Menu[loop].item, cost);
209           }
210 
211           mvprintw(5, 0, "L:Leave  P:Purchase  S:Sell Gems ? ");
212 
213           for (;;) {
214                     adjuststats();      /* truncate any bad values */
215 
216                     /* print some important statistics */
217                     mvprintw(1, 0, "Gold:   %9.0f  Gems:  %9.0f  Level:   %6.0f  Charms: %6d\n",
218                         Player.p_gold, Player.p_gems, Player.p_level, Player.p_charms);
219                     printw("Shield: %9.0f  Sword: %9.0f  Quicksilver:%3.0f  Blessed: %s\n",
220                         Player.p_shield, Player.p_sword, Player.p_quksilver,
221                         (Player.p_blessing ? " True" : "False"));
222                     printw("Brains: %9.0f  Mana:  %9.0f", Player.p_brains, Player.p_mana);
223 
224                     move(5, 36);
225                     ch = getanswer("LPS", FALSE);
226                     move(15, 0);
227                     clrtobot();
228                     switch (ch) {
229                     case 'L': /* leave */
230                     case '\n':
231                               altercoordinates(0.0, 0.0, A_NEAR);
232                               return;
233 
234                     case 'P': /* make purchase */
235                               mvaddstr(15, 0, "What what would you like to buy ? ");
236                               ch = getanswer(" 1234567", FALSE);
237                               move(15, 0);
238                               clrtoeol();
239 
240                               if (ch - '0' > size)
241                                         addstr("Sorry, this merchant doesn't have that.");
242                               else
243                                         switch (ch) {
244                                         case '1':
245                                                   printw("Mana is one per %.0f gold piece.  How many do you want (%.0f max) ? ",
246                                                       Menu[0].cost, floor(Player.p_gold / Menu[0].cost));
247                                                   cost = (numitems = floor(infloat())) * Menu[0].cost;
248 
249                                                   if (cost > Player.p_gold || numitems < 0)
250                                                             ++cheat;
251                                                   else {
252                                                             cheat = 0;
253                                                             Player.p_gold -= cost;
254                                                             if (drandom() < 0.02)
255                                                                       dishonest = TRUE;
256                                                             else
257                                                                       Player.p_mana += numitems;
258                                                   }
259                                                   break;
260 
261                                         case '2':
262                                                   printw("Shields are %.0f per +1.  How many do you want (%.0f max) ? ",
263                                                       Menu[1].cost, floor(Player.p_gold / Menu[1].cost));
264                                                   cost = (numitems = floor(infloat())) * Menu[1].cost;
265 
266                                                   if (numitems == 0.0)
267                                                             break;
268                                                   else if (cost > Player.p_gold || numitems < 0)
269                                                             ++cheat;
270                                                   else if (numitems < Player.p_shield)
271                                                             NOBETTER();
272                                                   else {
273                                                             cheat = 0;
274                                                             Player.p_gold -= cost;
275                                                             if (drandom() < 0.02)
276                                                                       dishonest = TRUE;
277                                                             else
278                                                                       Player.p_shield = numitems;
279                                                   }
280                                                   break;
281 
282                                         case '3':
283                                                   printw("A book costs %.0f gp.  How many do you want (%.0f max) ? ",
284                                                       Menu[2].cost, floor(Player.p_gold / Menu[2].cost));
285                                                   cost = (numitems = floor(infloat())) * Menu[2].cost;
286 
287                                                   if (cost > Player.p_gold || numitems < 0)
288                                                             ++cheat;
289                                                   else {
290                                                             cheat = 0;
291                                                             Player.p_gold -= cost;
292                                                             if (drandom() < 0.02)
293                                                                       dishonest = TRUE;
294                                                             else if (drandom() * numitems > Player.p_level / 10.0 &&
295                                                                 numitems != 1) {
296                                                                       printw("\nYou blew your mind!\n");
297                                                                       Player.p_brains /= 5;
298                                                             } else {
299                                                                       Player.p_brains += floor(numitems) * ROLL(20, 8);
300                                                             }
301                                                   }
302                                                   break;
303 
304                                         case '4':
305                                                   printw("Swords are %.0f gp per +1.  How many + do you want (%.0f max) ? ",
306                                                       Menu[3].cost, floor(Player.p_gold / Menu[3].cost));
307                                                   cost = (numitems = floor(infloat())) * Menu[3].cost;
308 
309                                                   if (numitems == 0.0)
310                                                             break;
311                                                   else if (cost > Player.p_gold || numitems < 0)
312                                                             ++cheat;
313                                                   else if (numitems < Player.p_sword)
314                                                             NOBETTER();
315                                                   else {
316                                                             cheat = 0;
317                                                             Player.p_gold -= cost;
318                                                             if (drandom() < 0.02)
319                                                                       dishonest = TRUE;
320                                                             else
321                                                                       Player.p_sword = numitems;
322                                                   }
323                                                   break;
324 
325                                         case '5':
326                                                   printw("A charm costs %.0f gp.  How many do you want (%.0f max) ? ",
327                                                       Menu[4].cost, floor(Player.p_gold / Menu[4].cost));
328                                                   cost = (numitems = floor(infloat())) * Menu[4].cost;
329 
330                                                   if (cost > Player.p_gold || numitems < 0)
331                                                             ++cheat;
332                                                   else {
333                                                             cheat = 0;
334                                                             Player.p_gold -= cost;
335                                                             if (drandom() < 0.02)
336                                                                       dishonest = TRUE;
337                                                             else
338                                                                       Player.p_charms += numitems;
339                                                   }
340                                                   break;
341 
342                                         case '6':
343                                                   printw("Quicksilver is %.0f gp per +1.  How many + do you want (%.0f max) ? ",
344                                                       Menu[5].cost, floor(Player.p_gold / Menu[5].cost));
345                                                   cost = (numitems = floor(infloat())) * Menu[5].cost;
346 
347                                                   if (numitems == 0.0)
348                                                             break;
349                                                   else if (cost > Player.p_gold || numitems < 0)
350                                                             ++cheat;
351                                                   else if (numitems < Player.p_quksilver)
352                                                             NOBETTER();
353                                                   else {
354                                                             cheat = 0;
355                                                             Player.p_gold -= cost;
356                                                             if (drandom() < 0.02)
357                                                                       dishonest = TRUE;
358                                                             else
359                                                                       Player.p_quksilver = numitems;
360                                                   }
361                                                   break;
362 
363                                         case '7':
364                                                   if (Player.p_blessing) {
365                                                             addstr("You already have a blessing.");
366                                                             break;
367                                                   }
368 
369                                                   printw("A blessing requires a %.0f gp donation.  Still want one ? ", blessingcost);
370                                                   ch = getanswer("NY", FALSE);
371 
372                                                   if (ch == 'Y') {
373                                                             if (Player.p_gold < blessingcost)
374                                                                       ++cheat;
375                                                             else {
376                                                                       cheat = 0;
377                                                                       Player.p_gold -= blessingcost;
378                                                                       if (drandom() < 0.02)
379                                                                                 dishonest = TRUE;
380                                                                       else
381                                                                                 Player.p_blessing = TRUE;
382                                                             }
383                                                   }
384                                                   break;
385                                         }
386                               break;
387 
388                     case 'S': /* sell gems */
389                               mvprintw(15, 0, "A gem is worth %.0f gp.  How many do you want to sell (%.0f max) ? ",
390                                   (double)N_GEMVALUE, Player.p_gems);
391                               numitems = floor(infloat());
392 
393                               if (numitems > Player.p_gems || numitems < 0)
394                                         ++cheat;
395                               else {
396                                         cheat = 0;
397                                         Player.p_gems -= numitems;
398                                         Player.p_gold += numitems * N_GEMVALUE;
399                               }
400                     }
401 
402                     if (cheat == 1)
403                               mvaddstr(17, 0, "Come on, merchants aren't stupid.  Stop cheating.\n");
404                     else if (cheat == 2) {
405                               mvaddstr(17, 0, "You had your chance.  This merchant happens to be\n");
406                               printw("a %.0f level magic user, and you made %s mad!\n",
407                                   ROLL(Circle * 20.0, 40.0), (drandom() < 0.5) ? "him" : "her");
408                               altercoordinates(0.0, 0.0, A_FAR);
409                               Player.p_energy /= 2.0;
410                               ++Player.p_sin;
411                               more(23);
412                               return;
413                     } else if (dishonest) {
414                               mvaddstr(17, 0, "The merchant stole your money!");
415                               refresh();
416                               altercoordinates(Player.p_x - Player.p_x / 10.0,
417                                   Player.p_y - Player.p_y / 10.0, A_SPECIFIC);
418                               sleep(2);
419                               return;
420                     }
421           }
422 }
423 
424 /*
425  * FUNCTION: print out important player statistics
426  *
427  * GLOBAL INPUTS: Users, Player
428  *
429  * DESCRIPTION:
430  *        Important player statistics are printed on the screen.
431  */
432 
433 void
displaystats(void)434 displaystats(void)
435 {
436           mvprintw(0, 0, "%s%s\n", Player.p_name, descrlocation(&Player, FALSE));
437           mvprintw(1, 0, "Level :%7.0f   Energy  :%9.0f(%9.0f)  Mana :%9.0f  Users:%3d\n",
438               Player.p_level, Player.p_energy, Player.p_maxenergy + Player.p_shield,
439               Player.p_mana, Users);
440           mvprintw(2, 0, "Quick :%3.0f(%3.0f)  Strength:%9.0f(%9.0f)  Gold :%9.0f  %s\n",
441               Player.p_speed, Player.p_quickness + Player.p_quksilver, Player.p_might,
442               Player.p_strength + Player.p_sword, Player.p_gold, descrstatus(&Player));
443 }
444 
445 /*
446  * FUNCTION: show player items
447  *
448  * GLOBAL INPUTS: Player
449  *
450  * DESCRIPTION:
451  *        Print out some player statistics of lesser importance.
452  */
453 
454 void
allstatslist(void)455 allstatslist(void)
456 {
457           static const char *flags[] =  /* to print value of some bools */
458           {
459                     "False",
460                     " True"
461           };
462 
463           mvprintw(8, 0, "Type: %s\n", descrtype(&Player, FALSE));
464 
465           mvprintw(10, 0, "Experience: %9.0f", Player.p_experience);
466           mvprintw(11, 0, "Brains    : %9.0f", Player.p_brains);
467           mvprintw(12, 0, "Magic Lvl : %9.0f", Player.p_magiclvl);
468           mvprintw(13, 0, "Sin       : %9.5f", Player.p_sin);
469           mvprintw(14, 0, "Poison    : %9.5f", Player.p_poison);
470           mvprintw(15, 0, "Gems      : %9.0f", Player.p_gems);
471           mvprintw(16, 0, "Age       : %9d", Player.p_age);
472           mvprintw(10, 40, "Holy Water: %9d", Player.p_holywater);
473           mvprintw(11, 40, "Amulets   : %9d", Player.p_amulets);
474           mvprintw(12, 40, "Charms    : %9d", Player.p_charms);
475           mvprintw(13, 40, "Crowns    : %9d", Player.p_crowns);
476           mvprintw(14, 40, "Shield    : %9.0f", Player.p_shield);
477           mvprintw(15, 40, "Sword     : %9.0f", Player.p_sword);
478           mvprintw(16, 40, "Quickslver: %9.0f", Player.p_quksilver);
479 
480           mvprintw(18, 0, "Blessing: %s   Ring: %s   Virgin: %s   Palantir: %s",
481               flags[Player.p_blessing], flags[Player.p_ring.ring_type != R_NONE],
482               flags[Player.p_virgin], flags[Player.p_palantir]);
483 }
484 
485 /*
486  * FUNCTION: return a string specifying player type
487  *
488  * ARGUMENTS:
489  *        struct player playerp - pointer to structure for player
490  *        bool shortflag - set if short form is desired
491  *
492  * RETURN VALUE: pointer to string describing player type
493  *
494  * GLOBAL INPUTS: Databuf[]
495  *
496  * GLOBAL OUTPUTS: Databuf[]
497  *
498  * DESCRIPTION:
499  *        Return a string describing the player type.
500  *        King, council, valar, supersedes other types.
501  *        The first character of the string is '*' if the player
502  *        has a crown.
503  *        If 'shortflag' is TRUE, return a 3 character string.
504  */
505 
506 const char *
descrtype(struct player * playerp,bool shortflag)507 descrtype(struct player *playerp, bool shortflag)
508 {
509           int type;           /* for caluculating result subscript */
510           static const char *results[] =          /* description table */
511           {
512                     " Magic User",      " MU",
513                     " Fighter",         " F ",
514                     " Elf",             " E ",
515                     " Dwarf",           " D ",
516                     " Halfling",        " H ",
517                     " Experimento",     " EX",
518                     " Super",           " S ",
519                     " King",            " K ",
520                     " Council of Wise", " CW",
521                     " Ex-Valar",        " EV",
522                     " Valar",           " V ",
523                     " ? ",              " ? "
524           };
525 
526           type = playerp->p_type;
527 
528           switch (playerp->p_specialtype) {
529           case SC_NONE:
530                     type = playerp->p_type;
531                     break;
532 
533           case SC_KING:
534                     type = 7;
535                     break;
536 
537           case SC_COUNCIL:
538                     type = 8;
539                     break;
540 
541           case SC_EXVALAR:
542                     type = 9;
543                     break;
544 
545           case SC_VALAR:
546                     type = 10;
547                     break;
548           }
549 
550           type *= 2;                    /* calculate offset */
551 
552           if (type > 20)
553                     /* error */
554                     type = 22;
555 
556           if (shortflag)
557                     /* use short descriptions */
558                     ++type;
559 
560           if (playerp->p_crowns > 0) {
561                     strcpy(Databuf, results[type]);
562                     Databuf[0] = '*';
563                     return (Databuf);
564           } else
565                     return (results[type]);
566 }
567 
568 /*
569  * FUNCTION: find location in player file of given name
570  *
571  * ARGUMENTS:
572  *        char *name - name of character to look for
573  *        struct player *playerp - pointer of structure to fill
574  *
575  * RETURN VALUE: location of player if found, -1 otherwise
576  *
577  * GLOBAL INPUTS: Wizard, *Playersfp
578  *
579  * DESCRIPTION:
580  *        Search the player file for the player of the given name.
581  *        If player is found, fill structure with player data.
582  */
583 
584 long
findname(const char * name,struct player * playerp)585 findname(const char *name, struct player *playerp)
586 {
587           long loc = 0;       /* location in the file */
588 
589           fseek(Playersfp, 0L, SEEK_SET);
590           while (fread((char *)playerp, SZ_PLAYERSTRUCT, 1, Playersfp) == 1) {
591                     if (strcmp(playerp->p_name, name) == 0) {
592                               if (playerp->p_status != S_NOTUSED || Wizard)
593                                         /* found it */
594                                         return (loc);
595                     }
596                     loc += SZ_PLAYERSTRUCT;
597           }
598 
599           return (-1);
600 }
601 
602 /*
603  * FUNCTION: find space in the player file for a new character
604  *
605  * RETURN VALUE: location of free space in file
606  *
607  * GLOBAL INPUTS: Other, *Playersfp
608  *
609  * GLOBAL OUTPUTS: Player
610  *
611  * DESCRIPTION:
612  *        Search the player file for an unused entry.  If none are found,
613  *        make one at the end of the file.
614  */
615 
616 long
allocrecord(void)617 allocrecord(void)
618 {
619           long loc = 0L;      /* location in file */
620 
621           fseek(Playersfp, 0L, SEEK_SET);
622           while (fread((char *)&Other, SZ_PLAYERSTRUCT, 1, Playersfp) == 1) {
623                     if (Other.p_status == S_NOTUSED)
624                               /* found an empty record */
625                               return (loc);
626                     else
627                               loc += SZ_PLAYERSTRUCT;
628           }
629 
630           /* make a new record */
631           initplayer(&Other);
632           Player.p_status = S_OFF;
633           writerecord(&Other, loc);
634 
635           return (loc);
636 }
637 
638 /*
639  * FUNCTION: free up a record on the player file
640  *
641  * ARGUMENTS:
642  *        struct player playerp - pointer to structure to free
643  *        long loc - location in file to free
644  *
645  * DESCRIPTION:
646  *        Mark structure as not used, and update player file.
647  */
648 
649 void
freerecord(struct player * playerp,long loc)650 freerecord(struct player *playerp, long loc)
651 {
652           playerp->p_name[0] = CH_MARKDELETE;
653           playerp->p_status = S_NOTUSED;
654           writerecord(playerp, loc);
655 }
656 
657 /*
658  * FUNCTION: leave game
659  *
660  * GLOBAL INPUTS: Player, Fileloc
661  *
662  * GLOBAL OUTPUTS: Player
663  *
664  * DESCRIPTION:
665  *        Mark player as inactive, and cleanup.
666  *        Do not save players below level 1.
667  */
668 
669 void
leavegame(void)670 leavegame(void)
671 {
672           if (Player.p_level < 1.0)
673                     /* delete character */
674                     freerecord(&Player, Fileloc);
675           else {
676                     Player.p_status = S_OFF;
677                     writerecord(&Player, Fileloc);
678           }
679 
680           cleanup(TRUE);
681           /* NOTREACHED */
682           exit(1);
683 }
684 
685 /*
686  * FUNCTION: death routine
687  *
688  * ARGUMENTS:
689  *        char *how - pointer to string describing cause of death
690  *
691  * GLOBAL INPUTS: Curmonster, Wizard, Player, *stdscr, Fileloc, *Monstfp
692  *
693  * GLOBAL OUTPUTS: Player
694  *
695  * DESCRIPTION:
696  *        Kill off current player.
697  *        Handle rings, and multiple lives.
698  *        Print an appropriate message.
699  *        Update scoreboard, lastdead, and let other players know about
700  *        the demise of their comrade.
701  */
702 
703 void
death(const char * how)704 death(const char *how)
705 {
706           FILE *fp; /* for updating various files */
707           int ch;             /* input */
708           static const char *const deathmesg[] =
709           /* add more messages here, if desired */
710           {
711                     "You have been wounded beyond repair.  ",
712                     "You have been disemboweled.  ",
713                     "You've been mashed, mauled, and spit upon.  (You're dead.)\n",
714                     "You died!  ",
715                     "You're a complete failure -- you've died!!\n",
716                     "You have been dealt a fatal blow!  "
717           };
718 
719           clear();
720 
721           if (strcmp(how, "Stupidity") != 0) {
722                     if (Player.p_level > 9999.0)
723                               /* old age */
724                               addstr("Characters must be retired upon reaching level 10000.  Sorry.");
725                     else if (Player.p_lives > 0) {
726                               /* extra lives */
727                               addstr("You should be more cautious.  You've been killed.\n");
728                               printw("You only have %d more chance(s).\n", --Player.p_lives);
729                               more(3);
730                               Player.p_energy = Player.p_maxenergy;
731                               return;
732                     } else if (Player.p_specialtype == SC_VALAR) {
733                               addstr("You had your chances, but Valar aren't totally\n");
734                               addstr("immortal.  You are now left to wither and die . . .\n");
735                               more(3);
736                               Player.p_brains = Player.p_level / 25.0;
737                               Player.p_energy = Player.p_maxenergy /= 5.0;
738                               Player.p_quksilver = Player.p_sword = 0.0;
739                               Player.p_specialtype = SC_COUNCIL;
740                               return;
741                     } else if (Player.p_ring.ring_inuse &&
742                                 (Player.p_ring.ring_type == R_DLREG || Player.p_ring.ring_type == R_NAZREG)) {
743                               /* good ring in use - saved from death */
744                               mvaddstr(4, 0, "Your ring saved you from death!\n");
745                               refresh();
746                               Player.p_ring.ring_type = R_NONE;
747                               Player.p_energy = Player.p_maxenergy / 12.0 + 1.0;
748                               if (Player.p_crowns > 0)
749                                         --Player.p_crowns;
750                               return;
751                     } else if (Player.p_ring.ring_type == R_BAD ||
752                                  Player.p_ring.ring_type == R_SPOILED) {
753                               /* bad ring in possession; name idiot after player */
754                               mvaddstr(4, 0,
755                                          "Your ring has taken control of you and turned you into a monster!\n");
756                               fseek(Monstfp, 13L * SZ_MONSTERSTRUCT, SEEK_SET);
757                               fread((char *)&Curmonster, SZ_MONSTERSTRUCT, 1, Monstfp);
758                               strcpy(Curmonster.m_name, Player.p_name);
759                               fseek(Monstfp, 13L * SZ_MONSTERSTRUCT, SEEK_SET);
760                               fwrite((char *)&Curmonster, SZ_MONSTERSTRUCT, 1, Monstfp);
761                               fflush(Monstfp);
762                     }
763           }
764 
765           enterscore();                 /* update score board */
766 
767           /* put info in last dead file */
768           fp = fopen(_PATH_LASTDEAD, "w");
769           fprintf(fp, "%s (%s, run by %s, level %.0f, killed by %s)",
770               Player.p_name, descrtype(&Player, TRUE),
771               Player.p_login, Player.p_level, how);
772           fclose(fp);
773 
774           /* let other players know */
775           fp = fopen(_PATH_MESS, "w");
776           fprintf(fp, "%s was killed by %s.", Player.p_name, how);
777           fclose(fp);
778 
779           freerecord(&Player, Fileloc);
780 
781           clear();
782           move(10, 0);
783           addstr(deathmesg[(int)ROLL(0.0, (double)sizeof(deathmesg) / sizeof(char *))]);
784           addstr("Care to give it another try ? ");
785           ch = getanswer("NY", FALSE);
786 
787           if (ch == 'Y') {
788                     cleanup(FALSE);
789                     execl(_PATH_GAMEPROG, "phantasia", "-s",
790                         (Wizard ? "-S" : NULL), NULL);
791                     exit(0);
792                     /* NOTREACHED */
793           }
794 
795           cleanup(TRUE);
796           /* NOTREACHED */
797 }
798 
799 /*
800  * FUNCTION: update structure in player file
801  *
802  * ARGUMENTS:
803  *        struct player *playerp - pointer to structure to write out
804  *        long place - location in file to updata
805  *
806  * GLOBAL INPUTS: *Playersfp
807  *
808  * DESCRIPTION:
809  *        Update location in player file with given structure.
810  */
811 
812 void
writerecord(struct player * playerp,long place)813 writerecord(struct player *playerp, long place)
814 {
815           fseek(Playersfp, place, SEEK_SET);
816           fwrite((char *)playerp, SZ_PLAYERSTRUCT, 1, Playersfp);
817           fflush(Playersfp);
818 }
819 
820 /*
821  * FUNCTION: calculate level based upon experience
822  *
823  * ARGUMENTS:
824  *        double experience - experience to calculate experience level from
825  *
826  * RETURN VALUE: experience level
827  *
828  * DESCRIPTION:
829  *        Experience level is a geometric progression.  This has been finely
830  *        tuned over the years, and probably should not be changed.
831  */
832 
833 static double
explevel(double experience)834 explevel(double experience)
835 {
836           if (experience < 1.1e7)
837                     return (floor(pow((experience / 1000.0), 0.4875)));
838           else
839                     return (floor(pow((experience / 1250.0), 0.4865)));
840 }
841 
842 /*
843  * FUNCTION: truncate trailing blanks off a string
844  *
845  * ARGUMENTS:
846  *        char *string - pointer to null terminated string
847  *
848  * DESCRIPTION:
849  *        Put nul characters in place of spaces at the end of the string.
850  */
851 
852 void
truncstring(char * string)853 truncstring(char *string)
854 {
855           size_t length;                /* length of string */
856 
857           length = strlen(string);
858           while (string[--length] == ' ')
859                     string[length] = '\0';
860 }
861 
862 /*
863  * FUNCTION: Alter x, y coordinates and set/check location flags
864  *
865  * ARGUMENTS:
866  *        double xnew, ynew - new x, y coordinates
867  *        int operation - operation to perform with coordinates
868  *
869  * GLOBAL INPUTS: Circle, Beyond, Player
870  *
871  * GLOBAL OUTPUTS: Marsh, Circle, Beyond, Throne, Player, Changed
872  *
873  * DESCRIPTION:
874  *        This module is called whenever the player's coordinates are altered.
875  *        If the player is beyond the point of no return, he/she is forced
876  *        to stay there.
877  */
878 
879 void
altercoordinates(double xnew,double ynew,int operation)880 altercoordinates(double xnew, double ynew, int operation)
881 {
882           switch (operation) {
883           case A_FORCED:                /* move with no checks */
884                     break;
885 
886           case A_NEAR:                  /* pick random coordinates near */
887                     xnew = Player.p_x + ROLL(1.0, 5.0);
888                     ynew = Player.p_y - ROLL(1.0, 5.0);
889                     /* FALLTHROUGH */
890 
891           case A_SPECIFIC:    /* just move player */
892                     if (Beyond && fabs(xnew) < D_BEYOND && fabs(ynew) < D_BEYOND) {
893                               /*
894                                * cannot move back from point of no return
895                                * pick the largest coordinate to remain unchanged
896                                */
897                               if (fabs(xnew) > fabs(ynew))
898                                         xnew = SGN(Player.p_x) * MAX(fabs(Player.p_x), D_BEYOND);
899                               else
900                                         ynew = SGN(Player.p_y) * MAX(fabs(Player.p_y), D_BEYOND);
901                     }
902                     break;
903 
904           case A_FAR:                   /* pick random coordinates far */
905                     xnew = Player.p_x + SGN(Player.p_x) * ROLL(50 * Circle, 250 * Circle);
906                     ynew = Player.p_y + SGN(Player.p_y) * ROLL(50 * Circle, 250 * Circle);
907                     break;
908           }
909 
910           /* now set location flags and adjust coordinates */
911           Circle = CIRCLE(Player.p_x = floor(xnew), Player.p_y = floor(ynew));
912 
913           /* set up flags based upon location */
914           Throne = Marsh = Beyond = FALSE;
915 
916           if (Player.p_x == 0.0 && Player.p_y == 0.0)
917                     Throne = TRUE;
918           else if (Circle < 35 && Circle >= 20)
919                     Marsh = TRUE;
920           else if (MAX(fabs(Player.p_x), fabs(Player.p_y)) >= D_BEYOND)
921                     Beyond = TRUE;
922 
923           Changed = TRUE;
924 }
925 
926 /*
927  * FUNCTION: read a player structure from file
928  *
929  * ARGUMENTS:
930  *        struct player *playerp - pointer to structure to fill
931  *        int loc - location of record to read
932  *
933  * GLOBAL INPUTS: *Playersfp
934  *
935  * DESCRIPTION:
936  *        Read structure information from player file.
937  */
938 
939 void
readrecord(struct player * playerp,long loc)940 readrecord(struct player *playerp, long loc)
941 {
942           fseek(Playersfp, loc, SEEK_SET);
943           fread((char *)playerp, SZ_PLAYERSTRUCT, 1, Playersfp);
944 }
945 
946 /*
947  * FUNCTION: adjust player statistics
948  *
949  * GLOBAL INPUTS: Player, *Statptr
950  *
951  * GLOBAL OUTPUTS: Circle, Player, Timeout
952  *
953  * DESCRIPTION:
954  *        Handle adjustment and maximums on various player characteristics.
955  */
956 
957 void
adjuststats(void)958 adjuststats(void)
959 {
960           double dtemp;                 /* for temporary calculations */
961 
962           if (explevel(Player.p_experience) > Player.p_level) {
963                     /* move one or more levels */
964                     movelevel();
965                     if (Player.p_level > 5.0)
966                               Timeout = TRUE;
967           }
968 
969           if (Player.p_specialtype == SC_VALAR)
970                     /* valar */
971                     Circle = Player.p_level / 5.0;
972 
973           /* calculate effective quickness */
974           dtemp = ((Player.p_gold + Player.p_gems / 2.0) - 1000.0) / Statptr->c_goldtote
975               - Player.p_level;
976           dtemp = MAX(0.0, dtemp);      /* gold slows player down */
977           Player.p_speed = Player.p_quickness + Player.p_quksilver - dtemp;
978 
979           /* calculate effective strength */
980           if (Player.p_poison > 0.0) {
981                     /* poison makes player weaker */
982                     dtemp = 1.0 - Player.p_poison * Statptr->c_weakness / 800.0;
983                     dtemp = MAX(0.1, dtemp);
984           } else
985                     dtemp = 1.0;
986           Player.p_might = dtemp * Player.p_strength + Player.p_sword;
987 
988           /* insure that important things are within limits */
989           Player.p_quksilver = MIN(99.0, Player.p_quksilver);
990           Player.p_mana = MIN(Player.p_mana,
991               Player.p_level * Statptr->c_maxmana + 1000.0);
992           Player.p_brains = MIN(Player.p_brains,
993               Player.p_level * Statptr->c_maxbrains + 200.0);
994           Player.p_charms = MIN(Player.p_charms, Player.p_level + 10.0);
995 
996           /*
997            * some implementations have problems with floating point compare
998            * we work around it with this stuff
999            */
1000           Player.p_gold = floor(Player.p_gold) + 0.1;
1001           Player.p_gems = floor(Player.p_gems) + 0.1;
1002           Player.p_mana = floor(Player.p_mana) + 0.1;
1003 
1004           if (Player.p_ring.ring_type != R_NONE) {
1005                     /* do ring things */
1006                     /* rest to max */
1007                     Player.p_energy = Player.p_maxenergy + Player.p_shield;
1008 
1009                     if (Player.p_ring.ring_duration <= 0)
1010                               /* clean up expired rings */
1011                               switch (Player.p_ring.ring_type) {
1012                               case R_BAD:         /* ring drives player crazy */
1013                                         Player.p_ring.ring_type = R_SPOILED;
1014                                         Player.p_ring.ring_duration = (short)ROLL(10.0, 25.0);
1015                                         break;
1016 
1017                               case R_NAZREG:      /* ring disappears */
1018                                         Player.p_ring.ring_type = R_NONE;
1019                                         break;
1020 
1021                               case R_SPOILED:     /* ring kills player */
1022                                         death("A cursed ring");
1023                                         break;
1024 
1025                               case R_DLREG:       /* this ring doesn't expire */
1026                                         Player.p_ring.ring_duration = 0;
1027                                         break;
1028                               }
1029           }
1030 
1031           if (Player.p_age / N_AGE > Player.p_degenerated) {
1032                     /* age player slightly */
1033                     ++Player.p_degenerated;
1034                     if (Player.p_quickness > 23.0)
1035                               Player.p_quickness *= 0.99;
1036                     Player.p_strength *= 0.97;
1037                     Player.p_brains *= 0.95;
1038                     Player.p_magiclvl *= 0.97;
1039                     Player.p_maxenergy *= 0.95;
1040                     Player.p_quksilver *= 0.95;
1041                     Player.p_sword *= 0.93;
1042                     Player.p_shield *= 0.93;
1043           }
1044 }
1045 
1046 /*
1047  * FUNCTION: initialize a character
1048  *
1049  * ARGUMENTS:
1050  *        struct player *playerp - pointer to structure to init
1051  *
1052  * DESCRIPTION:
1053  *        Put a bunch of default values in the given structure.
1054  */
1055 
1056 void
initplayer(struct player * playerp)1057 initplayer(struct player *playerp)
1058 {
1059           playerp->p_experience =
1060               playerp->p_level =
1061               playerp->p_strength =
1062               playerp->p_sword =
1063               playerp->p_might =
1064               playerp->p_energy =
1065               playerp->p_maxenergy =
1066               playerp->p_shield =
1067               playerp->p_quickness =
1068               playerp->p_quksilver =
1069               playerp->p_speed =
1070               playerp->p_magiclvl =
1071               playerp->p_mana =
1072               playerp->p_brains =
1073               playerp->p_poison =
1074               playerp->p_gems =
1075               playerp->p_sin =
1076               playerp->p_1scratch =
1077               playerp->p_2scratch = 0.0;
1078 
1079           playerp->p_gold = ROLL(50.0, 75.0) + 0.1;         /* give some gold */
1080 
1081           playerp->p_x = ROLL(-125.0, 251.0);
1082           playerp->p_y = ROLL(-125.0, 251.0);     /* give random x, y */
1083 
1084           /* clear ring */
1085           playerp->p_ring.ring_type = R_NONE;
1086           playerp->p_ring.ring_duration = 0;
1087           playerp->p_ring.ring_inuse = FALSE;
1088 
1089           playerp->p_age = 0L;
1090 
1091           playerp->p_degenerated = 1;   /* don't degenerate initially */
1092 
1093           playerp->p_type = C_FIGHTER;  /* default */
1094           playerp->p_specialtype = SC_NONE;
1095           playerp->p_lives =
1096               playerp->p_crowns =
1097               playerp->p_charms =
1098               playerp->p_amulets =
1099               playerp->p_holywater =
1100               playerp->p_lastused = 0;
1101           playerp->p_status = S_NOTUSED;
1102           playerp->p_tampered = T_OFF;
1103           playerp->p_istat = I_OFF;
1104 
1105           playerp->p_palantir =
1106               playerp->p_blessing =
1107               playerp->p_virgin =
1108               playerp->p_blindness = FALSE;
1109 
1110           playerp->p_name[0] =
1111               playerp->p_password[0] =
1112               playerp->p_login[0] = '\0';
1113 }
1114 
1115 /*
1116  * FUNCTION: read message from other players
1117  *
1118  * GLOBAL INPUTS: *stdscr, Databuf[], *Messagefp
1119  *
1120  * DESCRIPTION:
1121  *        If there is a message from other players, print it.
1122  */
1123 
1124 void
readmessage(void)1125 readmessage(void)
1126 {
1127           move(3, 0);
1128           clrtoeol();
1129           fseek(Messagefp, 0L, SEEK_SET);
1130           if (fgets(Databuf, SZ_DATABUF, Messagefp) != NULL)
1131                     addstr(Databuf);
1132 }
1133 
1134 /*
1135  * FUNCTION: process environment error
1136  *
1137  * ARGUMENTS:
1138  *        char *whichfile - pointer to name of file which caused error
1139  *
1140  * GLOBAL INPUTS: errno, *stdscr, printw(), printf(), Windows
1141  *
1142  * DESCRIPTION:
1143  *        Print message about offending file, and exit.
1144  */
1145 
1146 void
error(const char * whichfile)1147 error(const char *whichfile)
1148 {
1149           int (*funcp)(const char *, ...) __printflike(1, 2);
1150 
1151           if (Windows) {
1152                     funcp = (void *)printw;
1153                     clear();
1154           } else
1155                     funcp = printf;
1156 
1157           (*funcp)("An unrecoverable error has occurred reading %s.  (errno = %d)\n", whichfile, errno);
1158           (*funcp)("Please run 'setup' to determine the problem.\n");
1159           cleanup(TRUE);
1160           /* NOTREACHED */
1161 }
1162 
1163 /*
1164  * FUNCTION: calculate distance between two points
1165  *
1166  * ARGUMENTS:
1167  *        double x1, y1 - x, y coordinates of first point
1168  *        double x2, y2 - x, y coordinates of second point
1169  *
1170  * RETURN VALUE: distance between the two points
1171  *
1172  * DESCRIPTION:
1173  *        This function is provided because someone's hypot() library function
1174  *        fails if x1 == x2 && y1 == y2.
1175  */
1176 
1177 double
distance(double x_1,double x_2,double y_1,double y_2)1178 distance(double x_1, double x_2, double y_1, double y_2)
1179 {
1180           double deltax, deltay;
1181 
1182           deltax = x_1 - x_2;
1183           deltay = y_1 - y_2;
1184           return (sqrt(deltax * deltax + deltay * deltay));
1185 }
1186 
1187 
1188 /*
1189  * FUNCTION: exit upon trapping an illegal signal
1190  *
1191  * ARGUMENTS:
1192  *        int whichsig - signal which occurred to cause jump to here
1193  *
1194  * GLOBAL INPUTS: *stdscr
1195  *
1196  * DESCRIPTION:
1197  *        When an illegal signal is caught, print a message, and cleanup.
1198  */
1199 
1200 void
ill_sig(int whichsig)1201 ill_sig(int whichsig)
1202 {
1203           clear();
1204           if (!(whichsig == SIGINT || whichsig == SIGQUIT))
1205                     printw("Error: caught signal # %d.\n", whichsig);
1206           cleanup(TRUE);
1207           /* NOTREACHED */
1208 }
1209 
1210 /*
1211  * FUNCTION: return a string describing the player status
1212  *
1213  * ARGUMENTS:
1214  *        struct player playerp - pointer to player structure to describe
1215  *
1216  * RETURN VALUE: string describing player's status
1217  *
1218  * DESCRIPTION:
1219  *        Return verbal description of player status.
1220  *        If player status is S_PLAYING, check for low energy and blindness.
1221  */
1222 
1223 const char *
descrstatus(struct player * playerp)1224 descrstatus(struct player *playerp)
1225 {
1226           switch (playerp->p_status) {
1227           case S_PLAYING:
1228                     if (playerp->p_energy < 0.2 * (playerp->p_maxenergy + playerp->p_shield))
1229                               return ("Low Energy");
1230                     else if (playerp->p_blindness)
1231                               return ("Blind");
1232                     else
1233                               return ("In game");
1234 
1235           case S_CLOAKED:
1236                     return ("Cloaked");
1237 
1238           case S_INBATTLE:
1239                     return ("In Battle");
1240 
1241           case S_MONSTER:
1242                     return ("Encounter");
1243 
1244           case S_TRADING:
1245                     return ("Trading");
1246 
1247           case S_OFF:
1248                     return ("Off");
1249 
1250           case S_HUNGUP:
1251                     return ("Hung up");
1252 
1253           default:
1254                     return ("");
1255           }
1256 }
1257 
1258 /*
1259  * FUNCTION: collect taxes from current player
1260  *
1261  * ARGUMENTS:
1262  *        double gold - amount of gold to tax
1263  *        double gems - amount of gems to tax
1264  *
1265  * GLOBAL INPUTS: Player
1266  *
1267  * GLOBAL OUTPUTS: Player
1268  *
1269  * DESCRIPTION:
1270  *        Pay taxes on gold and gems.  If the player does not have enough
1271  *        gold to pay taxes on the added gems, convert some gems to gold.
1272  *        Add taxes to tax data base; add remaining gold and gems to
1273  *        player's cache.
1274  */
1275 
1276 void
collecttaxes(double gold,double gems)1277 collecttaxes(double gold, double gems)
1278 {
1279           FILE *fp;           /* to update Goldfile */
1280           double dtemp;                 /* for temporary calculations */
1281           double taxes;                 /* tax liability */
1282 
1283           /* add to cache */
1284           Player.p_gold += gold;
1285           Player.p_gems += gems;
1286 
1287           /* calculate tax liability */
1288           taxes = N_TAXAMOUNT / 100.0 * (N_GEMVALUE * gems + gold);
1289 
1290           if (Player.p_gold < taxes) {
1291                     /* not enough gold to pay taxes, must convert some gems to gold */
1292                     /* number of gems to convert */
1293                     dtemp = floor(taxes / N_GEMVALUE + 1.0);
1294 
1295                     if (Player.p_gems >= dtemp) {
1296                               /* player has enough to convert */
1297                               Player.p_gems -= dtemp;
1298                               Player.p_gold += dtemp * N_GEMVALUE;
1299                     } else {
1300                               /* take everything; this should never happen */
1301                               Player.p_gold += Player.p_gems * N_GEMVALUE;
1302                               Player.p_gems = 0.0;
1303                               taxes = Player.p_gold;
1304                     }
1305           }
1306 
1307           Player.p_gold -= taxes;
1308 
1309           if ((fp = fopen(_PATH_GOLD, "r+")) != NULL) {
1310                     /* update taxes */
1311                     dtemp = 0.0;
1312                     fread((char *)&dtemp, sizeof(double), 1, fp);
1313                     dtemp += floor(taxes);
1314                     fseek(fp, 0L, SEEK_SET);
1315                     fwrite((char *)&dtemp, sizeof(double), 1, fp);
1316                     fclose(fp);
1317           }
1318 }
1319