xref: /netbsd-src/games/phantasia/misc.c (revision 9f4a9600be3013fd256265533fbb085e3c80d678)
1 /*	$NetBSD: misc.c,v 1.24 2022/05/24 06:27:59 andvar Exp $	*/
2 
3 /*
4  * misc.c  Phantasia miscellaneous support routines
5  */
6 
7 #include <errno.h>
8 #include <math.h>
9 #include <setjmp.h>
10 #include <signal.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 
16 #include "macros.h"
17 #include "phantdefs.h"
18 #include "phantstruct.h"
19 #include "phantglobs.h"
20 #include "pathnames.h"
21 
22 #undef bool
23 #include <curses.h>
24 
25 
26 static double explevel(double);
27 
28 static void
movelevel(void)29 movelevel(void)
30 {
31 	const struct charstats *statptr; /* for pointing into Stattable */
32 	double  new;		/* new level */
33 	double  inc;		/* increment between new and old levels */
34 
35 	Changed = TRUE;
36 
37 	if (Player.p_type == C_EXPER)
38 		/* roll a type to use for increment */
39 		statptr = &Stattable[(int) ROLL(C_MAGIC, C_HALFLING - C_MAGIC + 1)];
40 	else
41 		statptr = Statptr;
42 
43 	new = explevel(Player.p_experience);
44 	inc = new - Player.p_level;
45 	Player.p_level = new;
46 
47 	/* add increments to statistics */
48 	Player.p_strength += statptr->c_strength.increase * inc;
49 	Player.p_mana += statptr->c_mana.increase * inc;
50 	Player.p_brains += statptr->c_brains.increase * inc;
51 	Player.p_magiclvl += statptr->c_magiclvl.increase * inc;
52 	Player.p_maxenergy += statptr->c_energy.increase * inc;
53 
54 	/* rest to maximum upon reaching new level */
55 	Player.p_energy = Player.p_maxenergy + Player.p_shield;
56 
57 	if (Player.p_crowns > 0 && Player.p_level >= 1000.0)
58 		/* no longer able to be king -- turn crowns into cash */
59 	{
60 		Player.p_gold += ((double) Player.p_crowns) * 5000.0;
61 		Player.p_crowns = 0;
62 	}
63 	if (Player.p_level >= 3000.0 && Player.p_specialtype < SC_COUNCIL)
64 		/* make a member of the council */
65 	{
66 		mvaddstr(6, 0, "You have made it to the Council of the Wise.\n");
67 		addstr("Good Luck on your search for the Holy Grail.\n");
68 
69 		Player.p_specialtype = SC_COUNCIL;
70 
71 		/* no rings for council and above */
72 		Player.p_ring.ring_type = R_NONE;
73 		Player.p_ring.ring_duration = 0;
74 
75 		Player.p_lives = 3;	/* three extra lives */
76 	}
77 	if (Player.p_level > 9999.0 && Player.p_specialtype != SC_VALAR)
78 		death("Old age");
79 }
80 
81 const char   *
descrlocation(struct player * playerp,phbool shortflag)82 descrlocation(struct player *playerp, phbool shortflag)
83 {
84 	double  circle;		/* corresponding circle for coordinates */
85 	int     quadrant;	/* quandrant of grid */
86 	const char   *label;	/* pointer to place name */
87 	static const char *const nametable[4][4] =	/* names of places */
88 	{
89 		{"Anorien", "Ithilien", "Rohan", "Lorien"},
90 		{"Gondor", "Mordor", "Dunland", "Rovanion"},
91 		{"South Gondor", "Khand", "Eriador", "The Iron Hills"},
92 		{"Far Harad", "Near Harad", "The Northern Waste", "Rhun"}
93 	};
94 
95 	if (playerp->p_specialtype == SC_VALAR)
96 		return (" is in Valhala");
97 	else
98 		if ((circle = CIRCLE(playerp->p_x, playerp->p_y)) >= 1000.0) {
99 			if (MAX(fabs(playerp->p_x), fabs(playerp->p_y)) > D_BEYOND)
100 				label = "The Point of No Return";
101 			else
102 				label = "The Ashen Mountains";
103 		} else
104 			if (circle >= 55)
105 				label = "Morannon";
106 			else
107 				if (circle >= 35)
108 					label = "Kennaquahair";
109 				else
110 					if (circle >= 20)
111 						label = "The Dead Marshes";
112 					else
113 						if (circle >= 9)
114 							label = "The Outer Waste";
115 						else
116 							if (circle >= 5)
117 								label = "The Moors Adventurous";
118 							else {
119 								if (playerp->p_x == 0.0 && playerp->p_y == 0.0)
120 									label = "The Lord's Chamber";
121 								else {
122 									/* this
123 									 *
124 									 * expr
125 									 * essi
126 									 * on
127 									 * is
128 									 * spli
129 									 * t
130 									 * to
131 									 * prev
132 									 * ent
133 									 * comp
134 									 * iler
135 									 *
136 									 * loop
137 									 *
138 									 * with
139 									 *
140 									 * some
141 									 *
142 									 * comp
143 									 * iler
144 									 * s */
145 									quadrant = ((playerp->p_x > 0.0) ? 1 : 0);
146 									quadrant += ((playerp->p_y >= 0.0) ? 2 : 0);
147 									label = nametable[((int) circle) - 1][quadrant];
148 								}
149 							}
150 
151 	if (shortflag)
152 		snprintf(Databuf, SZ_DATABUF, "%.29s", label);
153 	else
154 		snprintf(Databuf, SZ_DATABUF,
155 			" is in %s  (%.0f,%.0f)",
156 			label, playerp->p_x, playerp->p_y);
157 
158 	return (Databuf);
159 }
160 
161 void
tradingpost(void)162 tradingpost(void)
163 {
164 	double  numitems;	/* number of items to purchase */
165 	double  cost;		/* cost of purchase */
166 	double  blessingcost;	/* cost of blessing */
167 	int     ch;		/* input */
168 	int     size;		/* size of the trading post */
169 	int     loop;		/* loop counter */
170 	int     cheat = 0;	/* number of times player has tried to cheat */
171 	bool    dishonest = FALSE;	/* set when merchant is dishonest */
172 
173 	Player.p_status = S_TRADING;
174 	writerecord(&Player, Fileloc);
175 
176 	clear();
177 	addstr("You are at a trading post. All purchases must be made with gold.");
178 
179 	size = sqrt(fabs(Player.p_x / 100)) + 1;
180 	size = MIN(7, size);
181 
182 	/* set up cost of blessing */
183 	blessingcost = 1000.0 * (Player.p_level + 5.0);
184 
185 	/* print Menu */
186 	move(7, 0);
187 	for (loop = 0; loop < size; ++loop)
188 		/* print Menu */
189 	{
190 		if (loop == 6)
191 			cost = blessingcost;
192 		else
193 			cost = Menu[loop].cost;
194 		printw("(%d) %-12s: %6.0f\n", loop + 1, Menu[loop].item, cost);
195 	}
196 
197 	mvprintw(5, 0, "L:Leave  P:Purchase  S:Sell Gems ? ");
198 
199 	for (;;) {
200 		adjuststats();	/* truncate any bad values */
201 
202 		/* print some important statistics */
203 		mvprintw(1, 0, "Gold:   %9.0f  Gems:  %9.0f  Level:   %6.0f  Charms: %6d\n",
204 		    Player.p_gold, Player.p_gems, Player.p_level, Player.p_charms);
205 		printw("Shield: %9.0f  Sword: %9.0f  Quicksilver:%3.0f  Blessed: %s\n",
206 		    Player.p_shield, Player.p_sword, Player.p_quksilver,
207 		    (Player.p_blessing ? " True" : "False"));
208 		printw("Brains: %9.0f  Mana:  %9.0f", Player.p_brains, Player.p_mana);
209 
210 		move(5, 36);
211 		ch = getanswer("LPS", FALSE);
212 		move(15, 0);
213 		clrtobot();
214 		switch (ch) {
215 		case 'L':	/* leave */
216 		case '\n':
217 			altercoordinates(0.0, 0.0, A_NEAR);
218 			return;
219 
220 		case 'P':	/* make purchase */
221 			mvaddstr(15, 0, "What what would you like to buy ? ");
222 			ch = getanswer(" 1234567", FALSE);
223 			move(15, 0);
224 			clrtoeol();
225 
226 			if (ch - '0' > size)
227 				addstr("Sorry, this merchant doesn't have that.");
228 			else
229 				switch (ch) {
230 				case '1':
231 					printw("Mana is one per %.0f gold piece.  How many do you want (%.0f max) ? ",
232 					    Menu[0].cost, floor(Player.p_gold / Menu[0].cost));
233 					cost = (numitems = floor(infloat())) * Menu[0].cost;
234 
235 					if (cost > Player.p_gold || numitems < 0)
236 						++cheat;
237 					else {
238 						cheat = 0;
239 						Player.p_gold -= cost;
240 						if (drandom() < 0.02)
241 							dishonest = TRUE;
242 						else
243 							Player.p_mana += numitems;
244 					}
245 					break;
246 
247 				case '2':
248 					printw("Shields are %.0f per +1.  How many do you want (%.0f max) ? ",
249 					    Menu[1].cost, floor(Player.p_gold / Menu[1].cost));
250 					cost = (numitems = floor(infloat())) * Menu[1].cost;
251 
252 					if (numitems == 0.0)
253 						break;
254 					else
255 						if (cost > Player.p_gold || numitems < 0)
256 							++cheat;
257 						else
258 							if (numitems < Player.p_shield)
259 								NOBETTER();
260 							else {
261 								cheat = 0;
262 								Player.p_gold -= cost;
263 								if (drandom() < 0.02)
264 									dishonest = TRUE;
265 								else
266 									Player.p_shield = numitems;
267 							}
268 					break;
269 
270 				case '3':
271 					printw("A book costs %.0f gp.  How many do you want (%.0f max) ? ",
272 					    Menu[2].cost, floor(Player.p_gold / Menu[2].cost));
273 					cost = (numitems = floor(infloat())) * Menu[2].cost;
274 
275 					if (cost > Player.p_gold || numitems < 0)
276 						++cheat;
277 					else {
278 						cheat = 0;
279 						Player.p_gold -= cost;
280 						if (drandom() < 0.02)
281 							dishonest = TRUE;
282 						else
283 							if (drandom() * numitems > Player.p_level / 10.0
284 							    && numitems != 1) {
285 								printw("\nYou blew your mind!\n");
286 								Player.p_brains /= 5;
287 							} else {
288 								Player.p_brains += floor(numitems) * ROLL(20, 8);
289 							}
290 					}
291 					break;
292 
293 				case '4':
294 					printw("Swords are %.0f gp per +1.  How many + do you want (%.0f max) ? ",
295 					    Menu[3].cost, floor(Player.p_gold / Menu[3].cost));
296 					cost = (numitems = floor(infloat())) * Menu[3].cost;
297 
298 					if (numitems == 0.0)
299 						break;
300 					else
301 						if (cost > Player.p_gold || numitems < 0)
302 							++cheat;
303 						else
304 							if (numitems < Player.p_sword)
305 								NOBETTER();
306 							else {
307 								cheat = 0;
308 								Player.p_gold -= cost;
309 								if (drandom() < 0.02)
310 									dishonest = TRUE;
311 								else
312 									Player.p_sword = numitems;
313 							}
314 					break;
315 
316 				case '5':
317 					printw("A charm costs %.0f gp.  How many do you want (%.0f max) ? ",
318 					    Menu[4].cost, floor(Player.p_gold / Menu[4].cost));
319 					cost = (numitems = floor(infloat())) * Menu[4].cost;
320 
321 					if (cost > Player.p_gold || numitems < 0)
322 						++cheat;
323 					else {
324 						cheat = 0;
325 						Player.p_gold -= cost;
326 						if (drandom() < 0.02)
327 							dishonest = TRUE;
328 						else
329 							Player.p_charms += numitems;
330 					}
331 					break;
332 
333 				case '6':
334 					printw("Quicksilver is %.0f gp per +1.  How many + do you want (%.0f max) ? ",
335 					    Menu[5].cost, floor(Player.p_gold / Menu[5].cost));
336 					cost = (numitems = floor(infloat())) * Menu[5].cost;
337 
338 					if (numitems == 0.0)
339 						break;
340 					else
341 						if (cost > Player.p_gold || numitems < 0)
342 							++cheat;
343 						else
344 							if (numitems < Player.p_quksilver)
345 								NOBETTER();
346 							else {
347 								cheat = 0;
348 								Player.p_gold -= cost;
349 								if (drandom() < 0.02)
350 									dishonest = TRUE;
351 								else
352 									Player.p_quksilver = numitems;
353 							}
354 					break;
355 
356 				case '7':
357 					if (Player.p_blessing) {
358 						addstr("You already have a blessing.");
359 						break;
360 					}
361 					printw("A blessing requires a %.0f gp donation.  Still want one ? ", blessingcost);
362 					ch = getanswer("NY", FALSE);
363 
364 					if (ch == 'Y') {
365 						if (Player.p_gold < blessingcost)
366 							++cheat;
367 						else {
368 							cheat = 0;
369 							Player.p_gold -= blessingcost;
370 							if (drandom() < 0.02)
371 								dishonest = TRUE;
372 							else
373 								Player.p_blessing = TRUE;
374 						}
375 					}
376 					break;
377 				}
378 			break;
379 
380 		case 'S':	/* sell gems */
381 			mvprintw(15, 0, "A gem is worth %.0f gp.  How many do you want to sell (%.0f max) ? ",
382 			    (double) N_GEMVALUE, Player.p_gems);
383 			numitems = floor(infloat());
384 
385 			if (numitems > Player.p_gems || numitems < 0)
386 				++cheat;
387 			else {
388 				cheat = 0;
389 				Player.p_gems -= numitems;
390 				Player.p_gold += numitems * N_GEMVALUE;
391 			}
392 		}
393 
394 		if (cheat == 1)
395 			mvaddstr(17, 0, "Come on, merchants aren't stupid.  Stop cheating.\n");
396 		else
397 			if (cheat == 2) {
398 				mvaddstr(17, 0, "You had your chance.  This merchant happens to be\n");
399 				printw("a %.0f level magic user, and you made %s mad!\n",
400 				    ROLL(Circle * 20.0, 40.0), (drandom() < 0.5) ? "him" : "her");
401 				altercoordinates(0.0, 0.0, A_FAR);
402 				Player.p_energy /= 2.0;
403 				++Player.p_sin;
404 				more(23);
405 				return;
406 			} else
407 				if (dishonest) {
408 					mvaddstr(17, 0, "The merchant stole your money!");
409 					refresh();
410 					altercoordinates(Player.p_x - Player.p_x / 10.0,
411 					    Player.p_y - Player.p_y / 10.0, A_SPECIFIC);
412 					sleep(2);
413 					return;
414 				}
415 	}
416 }
417 
418 void
displaystats(void)419 displaystats(void)
420 {
421 	mvprintw(0, 0, "%s%s\n", Player.p_name, descrlocation(&Player, FALSE));
422 	mvprintw(1, 0, "Level :%7.0f   Energy  :%9.0f(%9.0f)  Mana :%9.0f  Users:%3d\n",
423 	    Player.p_level, Player.p_energy, Player.p_maxenergy + Player.p_shield,
424 	    Player.p_mana, Users);
425 	mvprintw(2, 0, "Quick :%3.0f(%3.0f)  Strength:%9.0f(%9.0f)  Gold :%9.0f  %s\n",
426 	    Player.p_speed, Player.p_quickness + Player.p_quksilver, Player.p_might,
427 	    Player.p_strength + Player.p_sword, Player.p_gold, descrstatus(&Player));
428 }
429 
430 void
allstatslist(void)431 allstatslist(void)
432 {
433 	static const char *const flags[] = /* to print value of some bools */
434 	{
435 		"False",
436 		" True"
437 	};
438 
439 	mvprintw(8, 0, "Type: %s\n", descrtype(&Player, FALSE));
440 
441 	mvprintw(10, 0, "Experience: %9.0f", Player.p_experience);
442 	mvprintw(11, 0, "Brains    : %9.0f", Player.p_brains);
443 	mvprintw(12, 0, "Magic Lvl : %9.0f", Player.p_magiclvl);
444 	mvprintw(13, 0, "Sin       : %9.5f", Player.p_sin);
445 	mvprintw(14, 0, "Poison    : %9.5f", Player.p_poison);
446 	mvprintw(15, 0, "Gems      : %9.0f", Player.p_gems);
447 	mvprintw(16, 0, "Age       : %9ld", Player.p_age);
448 	mvprintw(10, 40, "Holy Water: %9d", Player.p_holywater);
449 	mvprintw(11, 40, "Amulets   : %9d", Player.p_amulets);
450 	mvprintw(12, 40, "Charms    : %9d", Player.p_charms);
451 	mvprintw(13, 40, "Crowns    : %9d", Player.p_crowns);
452 	mvprintw(14, 40, "Shield    : %9.0f", Player.p_shield);
453 	mvprintw(15, 40, "Sword     : %9.0f", Player.p_sword);
454 	mvprintw(16, 40, "Quickslver: %9.0f", Player.p_quksilver);
455 
456 	mvprintw(18, 0, "Blessing: %s   Ring: %s   Virgin: %s   Palantir: %s",
457 	    flags[(int)Player.p_blessing],
458 	    flags[Player.p_ring.ring_type != R_NONE],
459 	    flags[(int)Player.p_virgin],
460 	    flags[(int)Player.p_palantir]);
461 }
462 
463 const char   *
descrtype(struct player * playerp,phbool shortflag)464 descrtype(struct player *playerp, phbool shortflag)
465 {
466 	int     type;		/* for calculating result subscript */
467 	static const char *const results[] =/* description table */
468 	{
469 		" Magic User", " MU",
470 		" Fighter", " F ",
471 		" Elf", " E ",
472 		" Dwarf", " D ",
473 		" Halfling", " H ",
474 		" Experimento", " EX",
475 		" Super", " S ",
476 		" King", " K ",
477 		" Council of Wise", " CW",
478 		" Ex-Valar", " EV",
479 		" Valar", " V ",
480 		" ? ", " ? "
481 	};
482 
483 	type = playerp->p_type;
484 
485 	switch (playerp->p_specialtype) {
486 	case SC_NONE:
487 		type = playerp->p_type;
488 		break;
489 
490 	case SC_KING:
491 		type = 7;
492 		break;
493 
494 	case SC_COUNCIL:
495 		type = 8;
496 		break;
497 
498 	case SC_EXVALAR:
499 		type = 9;
500 		break;
501 
502 	case SC_VALAR:
503 		type = 10;
504 		break;
505 	}
506 
507 	type *= 2;		/* calculate offset */
508 
509 	if (type > 20)
510 		/* error */
511 		type = 22;
512 
513 	if (shortflag)
514 		/* use short descriptions */
515 		++type;
516 
517 	if (playerp->p_crowns > 0) {
518 		strcpy(Databuf, results[type]);
519 		Databuf[0] = '*';
520 		return (Databuf);
521 	} else
522 		return (results[type]);
523 }
524 
525 long
findname(const char * name,struct player * playerp)526 findname(const char *name, struct player *playerp)
527 {
528 	long    loc = 0;	/* location in the file */
529 
530 	fseek(Playersfp, 0L, SEEK_SET);
531 	while (fread((char *) playerp, SZ_PLAYERSTRUCT, 1, Playersfp) == 1) {
532 		if (strcmp(playerp->p_name, name) == 0) {
533 			if (playerp->p_status != S_NOTUSED || Wizard)
534 				/* found it */
535 				return (loc);
536 		}
537 		loc += SZ_PLAYERSTRUCT;
538 	}
539 
540 	return (-1);
541 }
542 
543 long
allocrecord(void)544 allocrecord(void)
545 {
546 	long    loc = 0L;	/* location in file */
547 
548 	fseek(Playersfp, 0L, SEEK_SET);
549 	while (fread((char *) &Other, SZ_PLAYERSTRUCT, 1, Playersfp) == 1) {
550 		if (Other.p_status == S_NOTUSED)
551 			/* found an empty record */
552 			return (loc);
553 		else
554 			loc += SZ_PLAYERSTRUCT;
555 	}
556 
557 	/* make a new record */
558 	initplayer(&Other);
559 	Player.p_status = S_OFF;
560 	writerecord(&Other, loc);
561 
562 	return (loc);
563 }
564 
565 void
freerecord(struct player * playerp,long loc)566 freerecord(struct player *playerp, long loc)
567 {
568 	playerp->p_name[0] = CH_MARKDELETE;
569 	playerp->p_status = S_NOTUSED;
570 	writerecord(playerp, loc);
571 }
572 
573 void
leavegame(void)574 leavegame(void)
575 {
576 
577 	if (Player.p_level < 1.0)
578 		/* delete character */
579 		freerecord(&Player, Fileloc);
580 	else {
581 		Player.p_status = S_OFF;
582 		writerecord(&Player, Fileloc);
583 	}
584 
585 	cleanup(TRUE);
586 	/* NOTREACHED */
587 }
588 
589 void
death(const char * how)590 death(const char *how)
591 {
592 	FILE   *fp;		/* for updating various files */
593 	int     ch;		/* input */
594 	static const char *const deathmesg[] =
595 	/* add more messages here, if desired */
596 	{
597 		"You have been wounded beyond repair.  ",
598 		"You have been disemboweled.  ",
599 		"You've been mashed, mauled, and spit upon.  (You're dead.)\n",
600 		"You died!  ",
601 		"You're a complete failure -- you've died!!\n",
602 		"You have been dealt a fatal blow!  "
603 	};
604 
605 	clear();
606 
607 	if (strcmp(how, "Stupidity") != 0) {
608 		if (Player.p_level > 9999.0)
609 			/* old age */
610 			addstr("Characters must be retired upon reaching level 10000.  Sorry.");
611 		else
612 			if (Player.p_lives > 0)
613 				/* extra lives */
614 			{
615 				addstr("You should be more cautious.  You've been killed.\n");
616 				printw("You only have %d more chance(s).\n", --Player.p_lives);
617 				more(3);
618 				Player.p_energy = Player.p_maxenergy;
619 				return;
620 			} else
621 				if (Player.p_specialtype == SC_VALAR) {
622 					addstr("You had your chances, but Valar aren't totally\n");
623 					addstr("immortal.  You are now left to wither and die . . .\n");
624 					more(3);
625 					Player.p_brains = Player.p_level / 25.0;
626 					Player.p_energy = Player.p_maxenergy /= 5.0;
627 					Player.p_quksilver = Player.p_sword = 0.0;
628 					Player.p_specialtype = SC_COUNCIL;
629 					return;
630 				} else
631 					if (Player.p_ring.ring_inuse &&
632 					    (Player.p_ring.ring_type == R_DLREG || Player.p_ring.ring_type == R_NAZREG))
633 						/* good ring in use - saved
634 						 * from death */
635 					{
636 						mvaddstr(4, 0, "Your ring saved you from death!\n");
637 						refresh();
638 						Player.p_ring.ring_type = R_NONE;
639 						Player.p_energy = Player.p_maxenergy / 12.0 + 1.0;
640 						if (Player.p_crowns > 0)
641 							--Player.p_crowns;
642 						return;
643 					} else
644 						if (Player.p_ring.ring_type == R_BAD
645 						    || Player.p_ring.ring_type == R_SPOILED)
646 							/* bad ring in
647 							 * possession; name
648 							 * idiot after player */
649 						{
650 							mvaddstr(4, 0,
651 							    "Your ring has taken control of you and turned you into a monster!\n");
652 							fseek(Monstfp, 13L * SZ_MONSTERSTRUCT, SEEK_SET);
653 							fread((char *) &Curmonster, SZ_MONSTERSTRUCT, 1, Monstfp);
654 							strcpy(Curmonster.m_name, Player.p_name);
655 							fseek(Monstfp, 13L * SZ_MONSTERSTRUCT, SEEK_SET);
656 							fwrite((char *) &Curmonster, SZ_MONSTERSTRUCT, 1, Monstfp);
657 							fflush(Monstfp);
658 						}
659 	}
660 	enterscore();		/* update score board */
661 
662 	/* put info in last dead file */
663 	fp = fopen(_PATH_LASTDEAD, "w");
664 	fprintf(fp, "%s (%s, run by %s, level %.0f, killed by %s)",
665 	    Player.p_name, descrtype(&Player, TRUE),
666 	    Player.p_login, Player.p_level, how);
667 	fclose(fp);
668 
669 	/* let other players know */
670 	fp = fopen(_PATH_MESS, "w");
671 	fprintf(fp, "%s was killed by %s.", Player.p_name, how);
672 	fclose(fp);
673 
674 	freerecord(&Player, Fileloc);
675 
676 	clear();
677 	move(10, 0);
678 	addstr(deathmesg[(int) ROLL(0.0, (double) sizeof(deathmesg) / sizeof(char *))]);
679 	addstr("Care to give it another try ? ");
680 	ch = getanswer("NY", FALSE);
681 
682 	if (ch == 'Y') {
683 		cleanup(FALSE);
684 		execl(_PATH_GAMEPROG, "phantasia", "-s",
685 		    (Wizard ? "-S" : (char *) NULL), (char *) NULL);
686 		exit(0);
687 		/* NOTREACHED */
688 	}
689 	cleanup(TRUE);
690 	/* NOTREACHED */
691 }
692 
693 void
writerecord(struct player * playerp,long place)694 writerecord(struct player *playerp, long place)
695 {
696 	fseek(Playersfp, place, SEEK_SET);
697 	fwrite((char *) playerp, SZ_PLAYERSTRUCT, 1, Playersfp);
698 	fflush(Playersfp);
699 }
700 
701 static double
explevel(double experience)702 explevel(double experience)
703 {
704 	if (experience < 1.1e7)
705 		return (floor(pow((experience / 1000.0), 0.4875)));
706 	else
707 		return (floor(pow((experience / 1250.0), 0.4865)));
708 }
709 
710 void
truncstring(char * string)711 truncstring(char *string)
712 {
713 	int     length;		/* length of string */
714 
715 	length = strlen(string);
716 	while (string[--length] == ' ')
717 		string[length] = '\0';
718 }
719 
720 void
altercoordinates(double xnew,double ynew,int operation)721 altercoordinates(double xnew, double ynew, int operation)
722 {
723 	switch (operation) {
724 	case A_FORCED:		/* move with no checks */
725 		break;
726 
727 	case A_NEAR:		/* pick random coordinates near */
728 		xnew = Player.p_x + ROLL(1.0, 5.0);
729 		ynew = Player.p_y - ROLL(1.0, 5.0);
730 		/* fall through for check */
731 
732 		/* FALLTHROUGH */
733 	case A_SPECIFIC:	/* just move player */
734 		if (Beyond && fabs(xnew) < D_BEYOND && fabs(ynew) < D_BEYOND)
735 			/*
736 			 * cannot move back from point of no return
737 			 * pick the largest coordinate to remain unchanged
738 			 */
739 		{
740 			if (fabs(xnew) > fabs(ynew))
741 				xnew = SGN(Player.p_x) * MAX(fabs(Player.p_x), D_BEYOND);
742 			else
743 				ynew = SGN(Player.p_y) * MAX(fabs(Player.p_y), D_BEYOND);
744 		}
745 		break;
746 
747 	case A_FAR:		/* pick random coordinates far */
748 		xnew = Player.p_x + SGN(Player.p_x) * ROLL(50 * Circle, 250 * Circle);
749 		ynew = Player.p_y + SGN(Player.p_y) * ROLL(50 * Circle, 250 * Circle);
750 		break;
751 	}
752 
753 	/* now set location flags and adjust coordinates */
754 	Circle = CIRCLE(Player.p_x = floor(xnew), Player.p_y = floor(ynew));
755 
756 	/* set up flags based upon location */
757 	Throne = Marsh = Beyond = FALSE;
758 
759 	if (Player.p_x == 0.0 && Player.p_y == 0.0)
760 		Throne = TRUE;
761 	else
762 		if (Circle < 35 && Circle >= 20)
763 			Marsh = TRUE;
764 		else
765 			if (MAX(fabs(Player.p_x), fabs(Player.p_y)) >= D_BEYOND)
766 				Beyond = TRUE;
767 
768 	Changed = TRUE;
769 }
770 
771 void
readrecord(struct player * playerp,long loc)772 readrecord(struct player *playerp, long loc)
773 {
774 	fseek(Playersfp, loc, SEEK_SET);
775 	fread((char *) playerp, SZ_PLAYERSTRUCT, 1, Playersfp);
776 }
777 
778 void
adjuststats(void)779 adjuststats(void)
780 {
781 	double  dtemp;		/* for temporary calculations */
782 
783 	if (explevel(Player.p_experience) > Player.p_level)
784 		/* move one or more levels */
785 	{
786 		movelevel();
787 		if (Player.p_level > 5.0)
788 			Timeout = TRUE;
789 	}
790 	if (Player.p_specialtype == SC_VALAR)
791 		/* valar */
792 		Circle = Player.p_level / 5.0;
793 
794 	/* calculate effective quickness */
795 	dtemp = ((Player.p_gold + Player.p_gems / 2.0) - 1000.0) / Statptr->c_goldtote
796 	    - Player.p_level;
797 	dtemp = MAX(0.0, dtemp);/* gold slows player down */
798 	Player.p_speed = Player.p_quickness + Player.p_quksilver - dtemp;
799 
800 	/* calculate effective strength */
801 	if (Player.p_poison > 0.0)
802 		/* poison makes player weaker */
803 	{
804 		dtemp = 1.0 - Player.p_poison * Statptr->c_weakness / 800.0;
805 		dtemp = MAX(0.1, dtemp);
806 	} else
807 		dtemp = 1.0;
808 	Player.p_might = dtemp * Player.p_strength + Player.p_sword;
809 
810 	/* insure that important things are within limits */
811 	Player.p_quksilver = MIN(99.0, Player.p_quksilver);
812 	Player.p_mana = MIN(Player.p_mana,
813 	    Player.p_level * Statptr->c_maxmana + 1000.0);
814 	Player.p_brains = MIN(Player.p_brains,
815 	    Player.p_level * Statptr->c_maxbrains + 200.0);
816 	Player.p_charms = MIN(Player.p_charms, Player.p_level + 10.0);
817 
818 	/*
819          * some implementations have problems with floating point compare
820          * we work around it with this stuff
821          */
822 	Player.p_gold = floor(Player.p_gold) + 0.1;
823 	Player.p_gems = floor(Player.p_gems) + 0.1;
824 	Player.p_mana = floor(Player.p_mana) + 0.1;
825 
826 	if (Player.p_ring.ring_type != R_NONE)
827 		/* do ring things */
828 	{
829 		/* rest to max */
830 		Player.p_energy = Player.p_maxenergy + Player.p_shield;
831 
832 		if (Player.p_ring.ring_duration <= 0)
833 			/* clean up expired rings */
834 			switch (Player.p_ring.ring_type) {
835 			case R_BAD:	/* ring drives player crazy */
836 				Player.p_ring.ring_type = R_SPOILED;
837 				Player.p_ring.ring_duration = (short) ROLL(10.0, 25.0);
838 				break;
839 
840 			case R_NAZREG:	/* ring disappears */
841 				Player.p_ring.ring_type = R_NONE;
842 				break;
843 
844 			case R_SPOILED:	/* ring kills player */
845 				death("A cursed ring");
846 				break;
847 
848 			case R_DLREG:	/* this ring doesn't expire */
849 				Player.p_ring.ring_duration = 0;
850 				break;
851 			}
852 	}
853 	if (Player.p_age / N_AGE > Player.p_degenerated)
854 		/* age player slightly */
855 	{
856 		++Player.p_degenerated;
857 		if (Player.p_quickness > 23.0)
858 			Player.p_quickness *= 0.99;
859 		Player.p_strength *= 0.97;
860 		Player.p_brains *= 0.95;
861 		Player.p_magiclvl *= 0.97;
862 		Player.p_maxenergy *= 0.95;
863 		Player.p_quksilver *= 0.95;
864 		Player.p_sword *= 0.93;
865 		Player.p_shield *= 0.93;
866 	}
867 }
868 
869 void
initplayer(struct player * playerp)870 initplayer(struct player *playerp)
871 {
872 	playerp->p_experience =
873 	    playerp->p_level =
874 	    playerp->p_strength =
875 	    playerp->p_sword =
876 	    playerp->p_might =
877 	    playerp->p_energy =
878 	    playerp->p_maxenergy =
879 	    playerp->p_shield =
880 	    playerp->p_quickness =
881 	    playerp->p_quksilver =
882 	    playerp->p_speed =
883 	    playerp->p_magiclvl =
884 	    playerp->p_mana =
885 	    playerp->p_brains =
886 	    playerp->p_poison =
887 	    playerp->p_gems =
888 	    playerp->p_sin =
889 	    playerp->p_1scratch =
890 	    playerp->p_2scratch = 0.0;
891 
892 	playerp->p_gold = ROLL(50.0, 75.0) + 0.1;	/* give some gold */
893 
894 	playerp->p_x = ROLL(-125.0, 251.0);
895 	playerp->p_y = ROLL(-125.0, 251.0);	/* give random x, y */
896 
897 	/* clear ring */
898 	playerp->p_ring.ring_type = R_NONE;
899 	playerp->p_ring.ring_duration = 0;
900 	playerp->p_ring.ring_inuse = FALSE;
901 
902 	playerp->p_age = 0L;
903 
904 	playerp->p_degenerated = 1;	/* don't degenerate initially */
905 
906 	playerp->p_type = C_FIGHTER;	/* default */
907 	playerp->p_specialtype = SC_NONE;
908 	playerp->p_lives =
909 	    playerp->p_crowns =
910 	    playerp->p_charms =
911 	    playerp->p_amulets =
912 	    playerp->p_holywater =
913 	    playerp->p_lastused = 0;
914 	playerp->p_status = S_NOTUSED;
915 	playerp->p_tampered = T_OFF;
916 	playerp->p_istat = I_OFF;
917 
918 	playerp->p_palantir =
919 	    playerp->p_blessing =
920 	    playerp->p_virgin =
921 	    playerp->p_blindness = FALSE;
922 
923 	playerp->p_name[0] =
924 	    playerp->p_password[0] =
925 	    playerp->p_login[0] = '\0';
926 }
927 
928 void
readmessage(void)929 readmessage(void)
930 {
931 	move(3, 0);
932 	clrtoeol();
933 	fseek(Messagefp, 0L, SEEK_SET);
934 	if (fgets(Databuf, SZ_DATABUF, Messagefp) != NULL)
935 		addstr(Databuf);
936 }
937 
938 void
error(const char * whichfile)939 error(const char *whichfile)
940 {
941 	int     (*funcp)(const char *,...);
942 
943 	if (Windows) {
944 		funcp = printw;
945 		clear();
946 	} else
947 		funcp = printf;
948 
949 	(*funcp) ("An unrecoverable error has occurred reading %s.  (%s)\n", whichfile, strerror(errno));
950 	(*funcp) ("Please run 'setup' to determine the problem.\n");
951 	cleanup(TRUE);
952 	/* NOTREACHED */
953 }
954 
955 double
distance(double x_1,double x_2,double y_1,double y_2)956 distance(double x_1, double x_2, double y_1, double y_2)
957 {
958 	double  deltax, deltay;
959 
960 	deltax = x_1 - x_2;
961 	deltay = y_1 - y_2;
962 	return (sqrt(deltax * deltax + deltay * deltay));
963 }
964 
965 void
ill_sig(int whichsig)966 ill_sig(int whichsig)
967 {
968 	clear();
969 	if (!(whichsig == SIGINT || whichsig == SIGQUIT))
970 		printw("Error: caught signal # %d.\n", whichsig);
971 	cleanup(TRUE);
972 	/* NOTREACHED */
973 }
974 
975 const char *
descrstatus(struct player * playerp)976 descrstatus(struct player *playerp)
977 {
978 	switch (playerp->p_status) {
979 	case S_PLAYING:
980 		if (playerp->p_energy < 0.2 * (playerp->p_maxenergy + playerp->p_shield))
981 			return ("Low Energy");
982 		else
983 			if (playerp->p_blindness)
984 				return ("Blind");
985 			else
986 				return ("In game");
987 
988 	case S_CLOAKED:
989 		return ("Cloaked");
990 
991 	case S_INBATTLE:
992 		return ("In Battle");
993 
994 	case S_MONSTER:
995 		return ("Encounter");
996 
997 	case S_TRADING:
998 		return ("Trading");
999 
1000 	case S_OFF:
1001 		return ("Off");
1002 
1003 	case S_HUNGUP:
1004 		return ("Hung up");
1005 
1006 	default:
1007 		return ("");
1008 	}
1009 }
1010 
1011 double
drandom(void)1012 drandom(void)
1013 {
1014 	if (sizeof(int) != 2)
1015 		/* use only low bits */
1016 		return ((double) (random() & 0x7fff) / 32768.0);
1017 	else
1018 		return ((double) random() / 32768.0);
1019 }
1020 
1021 void
collecttaxes(double gold,double gems)1022 collecttaxes(double gold, double gems)
1023 {
1024 	FILE   *fp;		/* to update Goldfile */
1025 	double  dtemp;		/* for temporary calculations */
1026 	double  taxes;		/* tax liability */
1027 
1028 	/* add to cache */
1029 	Player.p_gold += gold;
1030 	Player.p_gems += gems;
1031 
1032 	/* calculate tax liability */
1033 	taxes = N_TAXAMOUNT / 100.0 * (N_GEMVALUE * gems + gold);
1034 
1035 	if (Player.p_gold < taxes)
1036 		/* not enough gold to pay taxes, must convert some gems to
1037 		 * gold */
1038 	{
1039 		dtemp = floor(taxes / N_GEMVALUE + 1.0);	/* number of gems to
1040 								 * convert */
1041 
1042 		if (Player.p_gems >= dtemp)
1043 			/* player has enough to convert */
1044 		{
1045 			Player.p_gems -= dtemp;
1046 			Player.p_gold += dtemp * N_GEMVALUE;
1047 		} else
1048 			/* take everything; this should never happen */
1049 		{
1050 			Player.p_gold += Player.p_gems * N_GEMVALUE;
1051 			Player.p_gems = 0.0;
1052 			taxes = Player.p_gold;
1053 		}
1054 	}
1055 	Player.p_gold -= taxes;
1056 
1057 	if ((fp = fopen(_PATH_GOLD, "r+")) != NULL)
1058 		/* update taxes */
1059 	{
1060 		dtemp = 0.0;
1061 		fread((char *) &dtemp, sizeof(double), 1, fp);
1062 		dtemp += floor(taxes);
1063 		fseek(fp, 0L, SEEK_SET);
1064 		fwrite((char *) &dtemp, sizeof(double), 1, fp);
1065 		fclose(fp);
1066 	}
1067 }
1068