drm: Pass CRTC ID in userspace vblank events
[dragonfly.git] / games / phantasia / misc.c
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
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 *
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
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
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
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 *
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
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 }