nrelease - fix/improve livecd
[dragonfly.git] / games / phantasia / misc.c
CommitLineData
4f91f2b6 1/* $NetBSD: misc.c,v 1.21 2011/09/01 07:18:50 plunky Exp $ */
2
984263bc
MD
3/*
4 * misc.c Phantasia miscellaneous support routines
984263bc
MD
5 */
6
7#include <string.h>
8#include "include.h"
9
4f91f2b6 10static double explevel(double);
984263bc 11
6693db17
SW
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 */
984263bc 26
4f91f2b6 27static void
313fa7d1 28movelevel(void)
984263bc 29{
4f91f2b6 30 const struct charstats *statptr; /* for pointing into Stattable */
6693db17
SW
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;
984263bc
MD
60 }
61
6693db17
SW
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");
984263bc 66
6693db17 67 Player.p_specialtype = SC_COUNCIL;
984263bc 68
6693db17
SW
69 /* no rings for council and above */
70 Player.p_ring.ring_type = R_NONE;
71 Player.p_ring.ring_duration = 0;
984263bc 72
6693db17 73 Player.p_lives = 3; /* three extra lives */
984263bc
MD
74 }
75
6693db17
SW
76 if (Player.p_level > 9999.0 && Player.p_specialtype != SC_VALAR)
77 death("Old age");
984263bc 78}
6693db17
SW
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 */
984263bc 95
313fa7d1
SW
96const char *
97descrlocation(struct player *playerp, bool shortflag)
984263bc 98{
6693db17
SW
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 */
984263bc 103 {
6693db17
SW
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" }
984263bc
MD
108 };
109
6693db17
SW
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 }
984263bc
MD
136 }
137
6693db17
SW
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);
984263bc 142
6693db17 143 return (Databuf);
984263bc 144}
6693db17
SW
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 */
984263bc 175
313fa7d1
SW
176void
177tradingpost(void)
984263bc 178{
6693db17
SW
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);
984263bc 190
6693db17
SW
191 clear();
192 addstr("You are at a trading post. All purchases must be made with gold.");
984263bc 193
6693db17
SW
194 size = sqrt(fabs(Player.p_x / 100)) + 1;
195 size = MIN(7, size);
984263bc 196
6693db17
SW
197 /* set up cost of blessing */
198 blessingcost = 1000.0 * (Player.p_level + 5.0);
984263bc 199
6693db17
SW
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 }
984263bc 210
6693db17 211 mvprintw(5, 0, "L:Leave P:Purchase S:Sell Gems ? ");
984263bc 212
6693db17
SW
213 for (;;) {
214 adjuststats(); /* truncate any bad values */
984263bc 215
6693db17
SW
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);
984263bc 223
6693db17
SW
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;
984263bc 385 }
6693db17 386 break;
984263bc 387
6693db17
SW
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());
984263bc 392
6693db17 393 if (numitems > Player.p_gems || numitems < 0)
984263bc 394 ++cheat;
6693db17 395 else {
984263bc 396 cheat = 0;
6693db17
SW
397 Player.p_gems -= numitems;
398 Player.p_gold += numitems * N_GEMVALUE;
984263bc 399 }
6693db17 400 }
984263bc 401
6693db17
SW
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 }
984263bc
MD
421 }
422}
6693db17
SW
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 */
984263bc 432
313fa7d1
SW
433void
434displaystats(void)
984263bc 435{
6693db17
SW
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));
984263bc 443}
6693db17
SW
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 */
984263bc 453
313fa7d1
SW
454void
455allstatslist(void)
984263bc 456{
6693db17
SW
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]);
984263bc 483}
6693db17
SW
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 */
984263bc 505
313fa7d1
SW
506const char *
507descrtype(struct player *playerp, bool shortflag)
984263bc 508{
6693db17
SW
509 int type; /* for caluculating result subscript */
510 static const char *results[] = /* description table */
984263bc 511 {
6693db17
SW
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) {
984263bc 529 case SC_NONE:
6693db17
SW
530 type = playerp->p_type;
531 break;
984263bc
MD
532
533 case SC_KING:
6693db17
SW
534 type = 7;
535 break;
984263bc
MD
536
537 case SC_COUNCIL:
6693db17
SW
538 type = 8;
539 break;
984263bc
MD
540
541 case SC_EXVALAR:
6693db17
SW
542 type = 9;
543 break;
984263bc
MD
544
545 case SC_VALAR:
6693db17
SW
546 type = 10;
547 break;
984263bc
MD
548 }
549
6693db17 550 type *= 2; /* calculate offset */
984263bc 551
6693db17
SW
552 if (type > 20)
553 /* error */
554 type = 22;
984263bc 555
6693db17
SW
556 if (shortflag)
557 /* use short descriptions */
558 ++type;
984263bc 559
6693db17
SW
560 if (playerp->p_crowns > 0) {
561 strcpy(Databuf, results[type]);
562 Databuf[0] = '*';
563 return (Databuf);
564 } else
565 return (results[type]);
984263bc 566}
6693db17
SW
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 */
984263bc
MD
583
584long
4f91f2b6 585findname(const char *name, struct player *playerp)
984263bc 586{
6693db17
SW
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;
984263bc
MD
597 }
598
6693db17 599 return (-1);
984263bc 600}
6693db17
SW
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 */
984263bc
MD
615
616long
313fa7d1 617allocrecord(void)
984263bc 618{
6693db17 619 long loc = 0L; /* location in file */
984263bc 620
6693db17
SW
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;
984263bc
MD
628 }
629
6693db17
SW
630 /* make a new record */
631 initplayer(&Other);
632 Player.p_status = S_OFF;
633 writerecord(&Other, loc);
984263bc 634
6693db17 635 return (loc);
984263bc 636}
6693db17
SW
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 */
984263bc 648
313fa7d1
SW
649void
650freerecord(struct player *playerp, long loc)
984263bc 651{
6693db17
SW
652 playerp->p_name[0] = CH_MARKDELETE;
653 playerp->p_status = S_NOTUSED;
654 writerecord(playerp, loc);
984263bc 655}
6693db17
SW
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 */
984263bc 668
313fa7d1
SW
669void
670leavegame(void)
984263bc 671{
6693db17
SW
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);
984263bc
MD
678 }
679
6693db17
SW
680 cleanup(TRUE);
681 /* NOTREACHED */
4f91f2b6 682 exit(1);
984263bc 683}
6693db17
SW
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 */
984263bc 702
313fa7d1
SW
703void
704death(const char *how)
984263bc 705{
6693db17
SW
706 FILE *fp; /* for updating various files */
707 int ch; /* input */
4f91f2b6 708 static const char *const deathmesg[] =
984263bc
MD
709 /* add more messages here, if desired */
710 {
6693db17
SW
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! "
984263bc
MD
717 };
718
6693db17 719 clear();
984263bc 720
6693db17
SW
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 }
984263bc
MD
763 }
764
6693db17 765 enterscore(); /* update score board */
984263bc 766
6693db17
SW
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);
984263bc 773
6693db17
SW
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);
984263bc 778
6693db17 779 freerecord(&Player, Fileloc);
984263bc 780
6693db17
SW
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 */
984263bc
MD
793 }
794
6693db17
SW
795 cleanup(TRUE);
796 /* NOTREACHED */
984263bc 797}
6693db17
SW
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 */
984263bc 811
313fa7d1
SW
812void
813writerecord(struct player *playerp, long place)
984263bc 814{
6693db17
SW
815 fseek(Playersfp, place, SEEK_SET);
816 fwrite((char *)playerp, SZ_PLAYERSTRUCT, 1, Playersfp);
817 fflush(Playersfp);
984263bc 818}
6693db17
SW
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 */
984263bc 832
4f91f2b6 833static double
313fa7d1 834explevel(double experience)
984263bc 835{
6693db17
SW
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)));
984263bc 840}
6693db17
SW
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 */
984263bc 851
313fa7d1
SW
852void
853truncstring(char *string)
984263bc 854{
6693db17 855 size_t length; /* length of string */
984263bc 856
6693db17
SW
857 length = strlen(string);
858 while (string[--length] == ' ')
859 string[length] = '\0';
984263bc 860}
6693db17
SW
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 */
984263bc 878
313fa7d1
SW
879void
880altercoordinates(double xnew, double ynew, int operation)
984263bc 881{
6693db17
SW
882 switch (operation) {
883 case A_FORCED: /* move with no checks */
884 break;
984263bc 885
6693db17
SW
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);
4f91f2b6 889 /* FALLTHROUGH */
984263bc
MD
890
891 case A_SPECIFIC: /* just move player */
6693db17
SW
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);
984263bc 901 }
6693db17 902 break;
984263bc 903
6693db17
SW
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;
984263bc
MD
908 }
909
6693db17
SW
910 /* now set location flags and adjust coordinates */
911 Circle = CIRCLE(Player.p_x = floor(xnew), Player.p_y = floor(ynew));
984263bc 912
6693db17
SW
913 /* set up flags based upon location */
914 Throne = Marsh = Beyond = FALSE;
984263bc 915
6693db17
SW
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;
984263bc 922
6693db17 923 Changed = TRUE;
984263bc 924}
6693db17
SW
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 */
984263bc 938
313fa7d1
SW
939void
940readrecord(struct player *playerp, long loc)
984263bc 941{
6693db17
SW
942 fseek(Playersfp, loc, SEEK_SET);
943 fread((char *)playerp, SZ_PLAYERSTRUCT, 1, Playersfp);
984263bc 944}
6693db17
SW
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 */
984263bc 956
313fa7d1
SW
957void
958adjuststats(void)
984263bc 959{
6693db17 960 double dtemp; /* for temporary calculations */
984263bc 961
6693db17
SW
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;
984263bc
MD
967 }
968
6693db17
SW
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;
984263bc 1016
6693db17
SW
1017 case R_NAZREG: /* ring disappears */
1018 Player.p_ring.ring_type = R_NONE;
1019 break;
984263bc 1020
6693db17
SW
1021 case R_SPOILED: /* ring kills player */
1022 death("A cursed ring");
1023 break;
984263bc 1024
6693db17
SW
1025 case R_DLREG: /* this ring doesn't expire */
1026 Player.p_ring.ring_duration = 0;
1027 break;
1028 }
984263bc
MD
1029 }
1030
6693db17
SW
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;
984263bc
MD
1043 }
1044}
6693db17
SW
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 */
984263bc 1055
313fa7d1
SW
1056void
1057initplayer(struct player *playerp)
984263bc 1058{
6693db17
SW
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';
984263bc 1113}
6693db17
SW
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 */
984263bc 1123
313fa7d1
SW
1124void
1125readmessage(void)
984263bc 1126{
6693db17
SW
1127 move(3, 0);
1128 clrtoeol();
1129 fseek(Messagefp, 0L, SEEK_SET);
1130 if (fgets(Databuf, SZ_DATABUF, Messagefp) != NULL)
1131 addstr(Databuf);
984263bc 1132}
6693db17
SW
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 */
984263bc 1145
313fa7d1
SW
1146void
1147error(const char *whichfile)
984263bc 1148{
b58f1e66 1149 int (*funcp)(const char *, ...) __printflike(1, 2);
6693db17
SW
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 */
984263bc 1161}
6693db17
SW
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 */
984263bc
MD
1176
1177double
313fa7d1 1178distance(double x_1, double x_2, double y_1, double y_2)
984263bc 1179{
6693db17 1180 double deltax, deltay;
984263bc 1181
6693db17
SW
1182 deltax = x_1 - x_2;
1183 deltay = y_1 - y_2;
1184 return (sqrt(deltax * deltax + deltay * deltay));
984263bc
MD
1185}
1186
6693db17
SW
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 */
984263bc 1199
313fa7d1
SW
1200void
1201ill_sig(int whichsig)
984263bc 1202{
6693db17
SW
1203 clear();
1204 if (!(whichsig == SIGINT || whichsig == SIGQUIT))
1205 printw("Error: caught signal # %d.\n", whichsig);
1206 cleanup(TRUE);
1207 /* NOTREACHED */
984263bc 1208}
6693db17
SW
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 */
984263bc 1222
313fa7d1
SW
1223const char *
1224descrstatus(struct player *playerp)
984263bc 1225{
6693db17 1226 switch (playerp->p_status) {
984263bc 1227 case S_PLAYING:
6693db17
SW
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");
984263bc
MD
1234
1235 case S_CLOAKED:
6693db17 1236 return ("Cloaked");
984263bc
MD
1237
1238 case S_INBATTLE:
6693db17 1239 return ("In Battle");
984263bc
MD
1240
1241 case S_MONSTER:
6693db17 1242 return ("Encounter");
984263bc
MD
1243
1244 case S_TRADING:
6693db17 1245 return ("Trading");
984263bc
MD
1246
1247 case S_OFF:
6693db17 1248 return ("Off");
984263bc
MD
1249
1250 case S_HUNGUP:
6693db17 1251 return ("Hung up");
984263bc
MD
1252
1253 default:
6693db17 1254 return ("");
984263bc
MD
1255 }
1256}
6693db17
SW
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 */
984263bc 1275
313fa7d1
SW
1276void
1277collecttaxes(double gold, double gems)
984263bc 1278{
6693db17
SW
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 }
984263bc
MD
1305 }
1306
6693db17 1307 Player.p_gold -= taxes;
984263bc 1308
6693db17
SW
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);
984263bc
MD
1317 }
1318}