1 /* File: scores.c */
2 
3 /* Purpose: Highscores handling */
4 
5 /*
6  * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke
7  *
8  * This software may be copied and distributed for educational, research, and
9  * not for profit purposes provided that this copyright and statement are
10  * included in all such copies.
11  */
12 
13 #include "angband.h"
14 
15 
16 /*
17  * The "highscore" file descriptor, if available.
18  */
19 int highscore_fd = -1;
20 
21 
22 /*
23  * Seek score 'i' in the highscore file
24  */
highscore_seek(int i)25 static int highscore_seek(int i)
26 {
27 	/* Seek for the requested record */
28 	return (fd_seek(highscore_fd, (huge) (i) * sizeof(high_score)));
29 }
30 
31 
32 /*
33  * Read one score from the highscore file
34  */
highscore_read(high_score * score)35 static errr highscore_read(high_score *score)
36 {
37 	/* Read the record, note failure */
38 	return (fd_read(highscore_fd, (char *)(score), sizeof(high_score)));
39 }
40 
41 
42 /*
43  * Write one score to the highscore file
44  */
highscore_write(const high_score * score)45 static int highscore_write(const high_score *score)
46 {
47 	/* Write the record, note failure */
48 	return (fd_write(highscore_fd, (char *)(score), sizeof(high_score)));
49 }
50 
51 
52 /*
53  * Just determine where a new score *would* be placed
54  * Return the location (0 is best) or -1 on failure
55  */
highscore_where(const high_score * score)56 static int highscore_where(const high_score *score)
57 {
58 	int i;
59 
60 	high_score the_score;
61 
62 	/* Paranoia -- it may not have opened */
63 	if (highscore_fd < 0) return (-1);
64 
65 	/* Go to the start of the highscore file */
66 	if (highscore_seek(0)) return (-1);
67 
68 	/* Read until we get to a higher score */
69 	for (i = 0; i < MAX_HISCORES; i++)
70 	{
71 		if (highscore_read(&the_score)) return (i);
72 		if (strcmp(the_score.pts, score->pts) < 0) return (i);
73 	}
74 
75 	/* The "last" entry is always usable */
76 	return (MAX_HISCORES);
77 }
78 
79 
80 /*
81  * Actually place an entry into the high score file
82  * Return the location (0 is best) or -1 on "failure"
83  */
highscore_add(const high_score * score)84 static int highscore_add(const high_score *score)
85 {
86 	int i, slot;
87 	bool done = FALSE;
88 
89 	high_score the_score, tmpscore;
90 
91 
92 	/* Paranoia -- it may not have opened */
93 	if (highscore_fd < 0) return (-1);
94 
95 	/* Determine where the score should go */
96 	slot = highscore_where(score);
97 
98 	/* Hack -- Not on the list */
99 	if (slot < 0) return (-1);
100 
101 	/* Hack -- prepare to dump the new score */
102 	the_score = (*score);
103 
104 	/* Slide all the scores down one */
105 	for (i = slot; !done && (i <= MAX_HISCORES); i++)
106 	{
107 		/* Read the old guy, note errors */
108 		if (highscore_seek(i)) return (-1);
109 		if (highscore_read(&tmpscore)) done = TRUE;
110 
111 		/* Back up and dump the score we were holding */
112 		if (highscore_seek(i)) return (-1);
113 		if (highscore_write(&the_score)) return (-1);
114 
115 		/* Hack -- Save the old score, for the next pass */
116 		the_score = tmpscore;
117 	}
118 
119 	/* Return location used */
120 	return (slot);
121 }
122 
123 
124 /*
125  * How much valuable stuff do we carry. Returns total cost of all found
126  * items currently in equipment (ie. those that haven't been storebought
127  * or started with)
128  */
equip_value(void)129 static long equip_value(void)
130 {
131 	object_type *o_ptr;
132 	long total = 0L;
133 	int i;
134 
135 	/* Scan equipment */
136 	for (i = 0; i < EQUIP_MAX; i++)
137 	{
138 		o_ptr = &p_ptr->equipment[i];
139 
140 		if (o_ptr->info & OB_NO_EXP) continue;
141 		if (!(o_ptr->info & OB_KNOWN)) continue;
142 
143 		total += object_value(o_ptr);
144 	}
145 
146 	/* Scan inventory */
147 	OBJ_ITT_START (p_ptr->inventory, o_ptr)
148 	{
149 		if (o_ptr->info & OB_NO_EXP) continue;
150 		if (!(o_ptr->info & OB_KNOWN)) continue;
151 
152 		total += object_value(o_ptr);
153 	}
154 	OBJ_ITT_END;
155 
156 	return (total);
157 }
158 
159 
160 /*
161  * Hack -- Calculates the total number of points earned         -JWT-
162  * Now with up to 80% penalty for having mutations & other extra things  -GSN-
163  * Fixed this up to be "fairer" -CK-
164  */
total_points(void)165 static long total_points(void)
166 {
167 	long temp;
168 	long mult = 85;
169 
170 	/* AI is not that big a deal (yet) */
171 	if (stupid_monsters) mult -= 20;
172 
173 	/* Penalize preserve mode */
174 	if (preserve_mode) mult -= 10;
175 
176 	/* Vanilla town is harder than normal */
177 	if (vanilla_town) mult += 5;
178 
179 	/* Not too much of a reward since some people like playing with this. */
180 	if (ironman_small_levels) mult += 5;
181 
182 	/* More ironman options */
183 	if (ironman_nightmare) mult += 20;
184 
185 	if (mult < 5) mult = 5;		/* At least 5% of the original score */
186 
187 	temp = p_ptr->max_exp;
188 
189 	temp = (temp * mult) / race_info[p_ptr->rp.prace].r_exp;
190 
191 	temp += equip_value() / 10;
192 
193 	if (ironman_downward) temp *= 2;
194 
195 	return (temp);
196 }
197 
198 
199 /*
200  * Hack - save index of player's high score
201  */
202 static int score_idx = -1;
203 
204 /* Hack - save from-to for resizing purposes */
205 static int score_from = -1;
206 static int score_to = -1;
207 static high_score *score_score = NULL;
208 static bool score_resize = FALSE;
209 
210 
211 /* find out how many score entries you can have on a page */
entries_on_page(void)212 static int entries_on_page(void)
213 {
214 	int wid, hgt;
215 
216 	/* Get size */
217 	Term_get_size(&wid, &hgt);
218 
219 	/* determine how many entries that is on a page */
220 	return (hgt / 4 - 1);
221 }
222 
223 
224 /* Find out what range is nice on the current page size */
determine_scores_page(int * from,int * to,int note)225 static void determine_scores_page(int *from, int *to, int note)
226 {
227 	int entries;
228 
229 	/* Determine how many entries that is on a page */
230 	entries = entries_on_page();
231 
232 	/* If the note belongs on the first or second page */
233 	if (note <= 3 * entries / 2)
234 	{
235 		/* Show the second page */
236 		*from = entries;
237 		*to = 2 * entries;
238 
239 		return;
240 	}
241 
242 	/* A page with the score nicely in the middle */
243 	*from = note - entries / 2;
244 	*to = note + entries / 2;
245 
246 	/* even entries lead to too large, odd range */
247 	if (!(entries % 2)) *to -= 1;
248 
249 	/* If the range overshoots  the last page */
250 	if (*to >= MAX_HISCORES)
251 	{
252 		*from = MAX_HISCORES - entries;
253 		*to = MAX_HISCORES;
254 
255 		/* This entry does not get saved, only displayed */
256 		if (note == MAX_HISCORES)
257 		{
258 			*from += 1;
259 			*to += 1;
260 		}
261 	}
262 }
263 
264 /*
265  * Display the scores in a given range.
266  * Assumes the high score list is already open.
267  *
268  * Mega-Hack -- allow "fake" entry at the given position.
269  */
display_scores_aux2(int from,int to,int note,const high_score * score,bool no_wait)270 static bool display_scores_aux2(int from, int to, int note,
271 						 const high_score *score, bool no_wait)
272 {
273 	int i, j, k, n, place;
274 	byte attr;
275 
276 	high_score the_score;
277 
278 	char out_val[256];
279 
280 	int len;
281 	int entries;
282 
283 	/* Paranoia -- it may not have opened */
284 	if (highscore_fd < 0) return (FALSE);
285 
286 	/* Seek to the beginning */
287 	if (highscore_seek(0)) return (FALSE);
288 
289 	/* Determine how many entries that is on a page */
290 	entries = entries_on_page();
291 
292 	/* Dimensions of the first page */
293 	if (to < entries)
294 	{
295 		from = 0;
296 		to = entries;
297 	}
298 
299 	/* Remember ending of the list */
300 	score_to = to;
301 
302 	/* Hack -- Count the high scores */
303 	for (i = 0; i < MAX_HISCORES; i++)
304 	{
305 		if (highscore_read(&the_score)) break;
306 	}
307 
308 	/* Hack -- allow "fake" entry to be last */
309 	if (note == i) i++;
310 
311 	/* Forget about the last entries */
312 	if (i > to) i = to;
313 
314 	/* Show 'entries' per page, until "done" */
315 	for (k = from, place = k + 1; k < i; k += entries)
316 	{
317 		/* Clear screen */
318 		Term_clear();
319 
320 		/* Title */
321 		put_fstr(0, 0, "                %s Hall of Fame", VERSION_NAME);
322 
323 		/* Indicate non-top scores */
324 		if (k > 0)
325 		{
326 			put_fstr(40, 0, "(from position %d)", k + 1);
327 		}
328 
329 		/* Dump entries */
330 		for (j = k, n = 0; j < i && n < entries; place++, j++, n++)
331 		{
332 			int pr, pc, clev, mlev, cdun, mdun;
333 
334 			cptr user, gold, when, aged;
335 
336 			/* Remember the current starting point */
337 			score_from = k;
338 
339 			/* Hack -- indicate death in yellow */
340 			attr = (j == note) ? TERM_YELLOW : TERM_WHITE;
341 
342 
343 			/* Mega-Hack -- insert a "fake" record */
344 			if ((note == j) && score)
345 			{
346 				the_score = (*score);
347 				attr = TERM_L_GREEN;
348 				score = NULL;
349 				note = -1;
350 				j--;
351 			}
352 
353 			/* Read a normal record */
354 			else
355 			{
356 				/* Read the proper record */
357 				if (highscore_seek(j)) break;
358 				if (highscore_read(&the_score)) break;
359 			}
360 
361 			/* Extract the race/class */
362 			pr = atoi(the_score.p_r);
363 			pc = atoi(the_score.p_c);
364 
365 			/* Extract the level info */
366 			clev = atoi(the_score.cur_lev);
367 			mlev = atoi(the_score.max_lev);
368 			cdun = atoi(the_score.cur_dun);
369 			mdun = atoi(the_score.max_dun);
370 
371 			/* Hack -- extract the gold and such */
372 			for (user = the_score.uid; isspace(*user); user++) /* loop */ ;
373 			for (when = the_score.day; isspace(*when); when++) /* loop */ ;
374 			for (gold = the_score.gold; isspace(*gold); gold++) /* loop */ ;
375 			for (aged = the_score.turns; isspace(*aged); aged++) /* loop */ ;
376 
377 			/* Dump some info */
378 			len = strnfmt(out_val, 256, "%3d.%9s  %s the %s %s, Level %d",
379 					place, the_score.pts, the_score.who,
380 					race_info[pr].title, class_info[pc].title, clev);
381 
382 			/* Append a "maximum level" */
383 			if (mlev > clev) strnfcat(out_val, 256, &len, " (Max %d)", mlev);
384 
385 			/* Dump the first line */
386 			put_fstr(0, n * 4 + 2, "%s%s", color_seq[attr], out_val);
387 
388 			/* Some people die outside of the dungeon */
389 			if (cdun)
390 			{
391 				/* Another line of info */
392 				len = strnfmt(out_val, 256, "               Killed by %s on %s %d",
393 						the_score.how, "Dungeon Level", cdun);
394 
395 			}
396 			else
397 			{
398 				/* Died outside */
399 				len = strnfmt(out_val, 256,
400 						"               Killed by %s in the outside world.",
401 						the_score.how);
402 			}
403 
404 			/* Append a "maximum level" */
405 			if (mdun > cdun) strnfcat(out_val, 256, &len, " (Max %d)", mdun);
406 
407 			/* Dump the info */
408 			put_fstr(0, n * 4 + 3, "%s%s", color_seq[attr], out_val);
409 
410 			/* And still another line of info */
411 			put_fstr(0, n * 4 + 4,
412 				"%s               (User %s, Date %s, Gold %s, Turn %s).",
413 					 color_seq[attr], user, when, gold, aged);
414 		}
415 
416 
417 		/* Wait for response */
418 		prtf(15, entries * 4 + 3, "[Press ESC to quit, any other key to continue.]");
419 
420 		/* No keystrokes needed during resizing */
421 		if (no_wait) return (TRUE);
422 
423 		j = inkey();
424 		clear_row(entries * 4 + 3);
425 
426 		/* Check for resize, entries may have changed */
427 		if (score_resize)
428 		{
429 			/* set back to default */
430 			score_resize = FALSE;
431 
432 			/* Not all of the previous range was shown yet */
433 			if (score_to < to)
434 			{
435 				/* Show rest of the range */
436 				return (display_scores_aux2(score_from + entries_on_page(),
437 											to,
438 											score_idx,
439 											score_score,
440 											FALSE));
441 			}
442 		}
443 
444 		/* Hack -- notice Escape */
445 		if (j == ESCAPE) return (FALSE);
446 	}
447 
448 	return (TRUE);
449 }
450 
451 /* Make sense of the display range and redraw the screen */
resize_scores(void)452 static void resize_scores(void)
453 {
454 	int from, to;
455 
456 	/* Alert the public */
457 	score_resize = TRUE;
458 
459 	/* Displaying the whole list? */
460 	if (score_idx == -1 || score_to < score_idx)
461 	{
462 		/* Display the list */
463 		(void)display_scores_aux2(score_from, score_from + entries_on_page(),
464 								  -1, NULL, TRUE);
465 	}
466 	else
467 	/* So this page has score_idx at its center */
468 	{
469 		/* Make new range for the new page */
470 		determine_scores_page(&from, &to, score_idx);
471 
472 		/* Display the list */
473 		(void)display_scores_aux2(from, to, score_idx, score_score, TRUE);
474 	}
475 }
476 
477 
display_scores_aux(int from,int to,int note,const high_score * score)478 bool display_scores_aux(int from, int to, int note, const high_score *score)
479 {
480 	bool outcome;
481 	void (*hook) (void);
482 
483 	/* Remember the old hook */
484 	hook = angband_term[0]->resize_hook
485 		;
486 	/* set the resize hook to scores */
487 	angband_term[0]->resize_hook = resize_scores;
488 
489 	/* Display the scores */
490 	outcome = display_scores_aux2(from, to, note, score, FALSE);
491 
492 	/* Restore the old resize hook */
493 	angband_term[0]->resize_hook = hook;
494 
495 	/* The size may have changed during the scores display */
496 	angband_term[0]->resize_hook();
497 
498 	/* Hack - Flush it */
499 	Term_fresh();
500 
501 	/* Allow another call depending on the outcome of this call */
502 	return (outcome);
503 }
504 
505 
506 /*
507  * Hack -- Display the scores in a given range and quit.
508  *
509  * This function is only called from "main.c" when the user asks
510  * to see the "high scores".
511  */
display_scores(int from,int to)512 void display_scores(int from, int to)
513 {
514 	char buf[1024];
515 
516 	/* Build the filename */
517 	path_make(buf, ANGBAND_DIR_APEX, "scores.raw");
518 
519 	/* Open the binary high score file, for reading */
520 	highscore_fd = fd_open(buf, O_RDONLY);
521 
522 	/* Paranoia -- No score file */
523 	if (highscore_fd < 0) quit("Score file unavailable.");
524 
525 	/* Clear screen */
526 	Term_clear();
527 
528 	/* Display the scores */
529 	(void)display_scores_aux(from, to, -1, NULL);
530 
531 	/* Shut the high score file */
532 	(void)fd_close(highscore_fd);
533 
534 	/* Forget the high score fd */
535 	highscore_fd = -1;
536 
537 	/* Quit */
538 	quit(NULL);
539 }
540 
541 
542 /*
543  * Enters a players name on a hi-score table, if "legal".
544  *
545  * Assumes "signals_ignore_tstp()" has been called.
546  */
enter_score(void)547 void enter_score(void)
548 {
549 	int i;
550 	high_score the_score;
551 
552 #ifndef HIGHSCORE_DATE_HACK
553 	char long_day[12];
554 #endif
555 
556 	time_t ct = time((time_t *) 0);
557 
558 	/* No score file */
559 	if (highscore_fd < 0)
560 	{
561 		return;
562 	}
563 
564 #ifndef SCORE_WIZARDS
565 	/* Wizard-mode pre-empts scoring */
566 	if (p_ptr->state.noscore & 0x003F)
567 	{
568 		msgf("Score not registered for wizards.");
569 		message_flush();
570 		score_idx = -1;
571 		return;
572 	}
573 #endif
574 
575 #ifndef SCORE_BORGS
576 	/* Borg-mode pre-empts scoring */
577 	if (p_ptr->state.noscore & 0x00C0)
578 	{
579 		msgf("Score not registered for borgs.");
580 		message_flush();
581 		score_idx = -1;
582 		return;
583 	}
584 #endif
585 
586 #ifndef SCORE_CHEATERS
587 	/* Cheaters are not scored */
588 	if (p_ptr->state.noscore & 0xFF00)
589 	{
590 		msgf("Score not registered for cheaters.");
591 		message_flush();
592 		score_idx = -1;
593 		return;
594 	}
595 #endif
596 
597 	/* Interupted */
598 	if (!p_ptr->state.total_winner && streq(p_ptr->state.died_from, "Interrupting"))
599 	{
600 		msgf("Score not registered due to interruption.");
601 		message_flush();
602 		score_idx = -1;
603 		return;
604 	}
605 
606 	/* Quitter */
607 	if (!p_ptr->state.total_winner && streq(p_ptr->state.died_from, "Quitting"))
608 	{
609 		msgf("Score not registered due to quitting.");
610 		message_flush();
611 		score_idx = -1;
612 		return;
613 	}
614 
615 
616 	/* Clear the record */
617 	(void)WIPE(&the_score, high_score);
618 
619 	/* Save the version */
620 	strnfmt(the_score.what, 8, "%u.%u.%u",
621 			VER_MAJOR, VER_MINOR, VER_PATCH);
622 
623 	/* Calculate and save the points */
624 	strnfmt(the_score.pts, 10, "%9lu", (unsigned long)total_points());
625 	the_score.pts[9] = '\0';
626 
627 	/* Save the current gold */
628 	strnfmt(the_score.gold, 10, "%9lu", (unsigned long)p_ptr->au);
629 	the_score.gold[9] = '\0';
630 
631 	/* Save the current turn */
632 	strnfmt(the_score.turns, 10, "%9lu", (unsigned long)turn);
633 	the_score.turns[9] = '\0';
634 
635 #ifdef HIGHSCORE_DATE_HACK
636 	/* Save the date in a hacked up form (9 chars) */
637 	(void)strnfmt(the_score.day, 10, "%-.6s %-.2s", ctime(&ct) + 4,
638 				  ctime(&ct) + 22);
639 #else  /* HIGHSCORE_DATE_HACK */
640 	/* Get the date with a 4-digit year */
641 	(void)strftime(long_day, 11, "%m/%d/%Y", localtime(&ct));
642 
643 	/* Remove the century */
644 	i = 7;
645 	while (1)
646 	{
647 		i++;
648 		long_day[i - 2] = long_day[i];
649 
650 		/* Exit if get a zero */
651 		if (!long_day[i] || (i == 11)) break;
652 	}
653 
654 	/* Save the date in standard form (8 chars) */
655 	(void)strnfmt(the_score.day, 9, "%s", long_day);
656 #endif /* HIGHSCORE_DATE_HACK */
657 
658 	/* Save the player name (15 chars) */
659 	strnfmt(the_score.who, 16, "%-.15s", player_name);
660 
661 	/* Save the player info XXX XXX XXX */
662 	strnfmt(the_score.uid, 8, "%7u", (uint)player_uid);
663 	strnfmt(the_score.sex, 2, "%c", (p_ptr->rp.psex ? 'm' : 'f'));
664 	strnfmt(the_score.p_r, 3, "%2d", (int)p_ptr->rp.prace);
665 	strnfmt(the_score.p_c, 3, "%2d", (int)p_ptr->rp.pclass);
666 
667 	/* Save the level and such */
668 	strnfmt(the_score.cur_lev, 4, "%3d", p_ptr->lev);
669 	strnfmt(the_score.cur_dun, 4, "%3d", p_ptr->depth);
670 	strnfmt(the_score.max_lev, 4, "%3d", p_ptr->max_lev);
671 	strnfmt(the_score.max_dun, 4, "%3d", max_dun_level_reached());
672 
673 	/* Save the cause of death (31 chars) */
674 	strnfmt(the_score.how, 32, "%-.31s", p_ptr->state.died_from);
675 
676 	/* Grab permissions */
677 	safe_setuid_grab();
678 
679 	/* Lock (for writing) the highscore file, or fail */
680 	if (fd_lock(highscore_fd, F_WRLCK)) return;
681 
682 	/* Drop permissions */
683 	safe_setuid_drop();
684 
685 	/* Add a new entry to the score list, see where it went */
686 	score_idx = highscore_add(&the_score);
687 
688 	/* Grab permissions */
689 	safe_setuid_grab();
690 
691 	/* Unlock the highscore file, or fail */
692 	if (fd_lock(highscore_fd, F_UNLCK)) return;
693 
694 	/* Drop permissions */
695 	safe_setuid_drop();
696 
697 	/* Success */
698 	return;
699 }
700 
701 
702 
703 /*
704  * Displays some relevant portion of the high score list.
705  */
top_twenty(void)706 static void top_twenty(void)
707 {
708 	int from, to;
709 	bool cont;
710 
711 	/* Clear screen */
712 	Term_clear();
713 
714 	/* No score file */
715 	if (highscore_fd < 0)
716 	{
717 		msgf("Score file unavailable.");
718 		message_flush();
719 		return;
720 	}
721 
722 	/* Show the first page of the highscore */
723 	cont = display_scores_aux(0, 5, score_idx, NULL);
724 
725 	/* If the user didn't press ESC, show the second page too */
726 	if (cont)
727 	{
728 		/* Determine what the second page will be */
729 		determine_scores_page(&from, &to, score_idx);
730 
731 		/* Show the second page */
732 		(void)display_scores_aux(from, to, score_idx, NULL);
733 	}
734 
735 	/* Success */
736 	return;
737 }
738 
739 
740 /*
741  * Predict the players location, and display it.
742  */
predict_score(void)743 void predict_score(void)
744 {
745 	int j, from, to;
746 	bool cont;
747 
748 	high_score the_score;
749 
750 	/* No score file */
751 	if (highscore_fd < 0)
752 	{
753 		msgf("Score file unavailable.");
754 		message_flush();
755 		return;
756 	}
757 
758 
759 	/* Save the version */
760 	strnfmt(the_score.what, 8, "%u.%u.%u",
761 			VER_MAJOR, VER_MINOR, VER_PATCH);
762 
763 	/* Calculate and save the points */
764 	strnfmt(the_score.pts, 10, "%9lu", (unsigned long)total_points());
765 
766 	/* Save the current gold */
767 	strnfmt(the_score.gold, 10, "%9lu", (unsigned long)p_ptr->au);
768 
769 	/* Save the current turn */
770 	strnfmt(the_score.turns, 10, "%9lu", (unsigned long)turn);
771 
772 	/* Hack -- no time needed */
773 	strcpy(the_score.day, "TODAY");
774 
775 	/* Save the player name (15 chars) */
776 	strnfmt(the_score.who, 16, "%-.15s", player_name);
777 
778 	/* Save the player info XXX XXX XXX */
779 	strnfmt(the_score.uid, 8, "%7u", (uint)player_uid);
780 	strnfmt(the_score.sex, 2, "%c", (p_ptr->rp.psex ? 'm' : 'f'));
781 	strnfmt(the_score.p_r, 3, "%2d", (int)p_ptr->rp.prace);
782 	strnfmt(the_score.p_c, 3, "%2d", (int)p_ptr->rp.pclass);
783 
784 	/* Save the level and such */
785 	strnfmt(the_score.cur_lev, 4, "%3d", p_ptr->lev);
786 	strnfmt(the_score.cur_dun, 4, "%3d", p_ptr->depth);
787 	strnfmt(the_score.max_lev, 4, "%3d", p_ptr->max_lev);
788 	strnfmt(the_score.max_dun, 4, "%3d", max_dun_level_reached());
789 
790 	/* Hack -- no cause of death */
791 	strcpy(the_score.how, "nobody (yet!)");
792 
793 
794 	/* See where the entry would be placed */
795 	j = highscore_where(&the_score);
796 
797 	/* Keep it for resizing */
798 	score_idx = j;
799 	score_score = &the_score;
800 
801 	/* Show the first page of the highscore */
802 	cont = display_scores_aux(0, 5, score_idx, &the_score);
803 
804 	/* If the user didn't press ESC, show the second page too */
805 	if (cont)
806 	{
807 		/* Determine what the second page will be */
808 		determine_scores_page(&from, &to, score_idx);
809 
810 		/* Show the second page */
811 		(void)display_scores_aux(from, to, score_idx, &the_score);
812 	}
813 }
814 
815 
816 
817 /*
818  * Selectively list highscores based on class -KMW-
819  */
show_highclass(void)820 void show_highclass(void)
821 {
822 	int i = 0, j, m = 0;
823 	int pr, pc, clev /*, al */ ;
824 	high_score the_score;
825 	char buf[1024];
826 
827 	/* Build the filename */
828 	path_make(buf, ANGBAND_DIR_APEX, "scores.raw");
829 
830 	highscore_fd = fd_open(buf, O_RDONLY);
831 
832 	if (highscore_fd < 0)
833 	{
834 		msgf("Score file unavailable.");
835 		message_flush();
836 		return;
837 	}
838 
839 	if (highscore_seek(0)) return;
840 
841 	for (i = 0; i < MAX_HISCORES; i++)
842 		if (highscore_read(&the_score)) break;
843 
844 	m = 0;
845 	j = 0;
846 	clev = 0;
847 
848 	while ((m < 9) || (j < MAX_HISCORES))
849 	{
850 		if (highscore_seek(j)) break;
851 		if (highscore_read(&the_score)) break;
852 		pr = atoi(the_score.p_r);
853 		pc = atoi(the_score.p_c);
854 		clev = atoi(the_score.cur_lev);
855 
856 		prtf(0, m + 7, "%3d) %s the %s (Level %2d)",
857 				(m + 1), the_score.who, race_info[pr].title, clev);
858 		m++;
859 		j++;
860 	}
861 
862 	prtf(0, m + 8, "You) %s the %s (Level %2d)",
863 			player_name, race_info[p_ptr->rp.prace].title, p_ptr->lev);
864 
865 	(void)fd_close(highscore_fd);
866 	highscore_fd = -1;
867 	msgf("Hit any key to continue");
868 	message_flush();
869 
870 	clear_region(0, 5, 17);
871 }
872 
873 
874 /*
875  * Race Legends
876  * -KMW-
877  */
race_score(int race_num)878 void race_score(int race_num)
879 {
880 	int i = 0, j, m = 0;
881 	int pr, pc, clev, lastlev;
882 	high_score the_score;
883 	char buf[1024];
884 
885 	lastlev = 0;
886 
887 	/* rr9: TODO - pluralize the race */
888 	prtf(15, 5, "The Greatest of all the %s", race_info[race_num].title);
889 
890 	/* Build the filename */
891 	path_make(buf, ANGBAND_DIR_APEX, "scores.raw");
892 
893 	highscore_fd = fd_open(buf, O_RDONLY);
894 
895 	if (highscore_fd < 0)
896 	{
897 		msgf("Score file unavailable.");
898 		message_flush();
899 		return;
900 	}
901 
902 	if (highscore_seek(0)) return;
903 
904 	for (i = 0; i < MAX_HISCORES; i++)
905 	{
906 		if (highscore_read(&the_score)) break;
907 	}
908 
909 	m = 0;
910 	j = 0;
911 
912 	while ((m < 10) || (j < MAX_HISCORES))
913 	{
914 		if (highscore_seek(j)) break;
915 		if (highscore_read(&the_score)) break;
916 		pr = atoi(the_score.p_r);
917 		pc = atoi(the_score.p_c);
918 		clev = atoi(the_score.cur_lev);
919 
920 		if (pr == race_num)
921 		{
922 			prtf(0, m + 7, "%3d) %s the %s (Level %3d)",
923 					(m + 1), the_score.who, race_info[pr].title, clev);
924 			m++;
925 			lastlev = clev;
926 		}
927 		j++;
928 	}
929 
930 	/* add player if qualified */
931 	if ((p_ptr->rp.prace == race_num) && (p_ptr->lev >= lastlev))
932 	{
933 		prtf(0, m + 8, "You) %s the %s (Level %3d)",
934 				player_name, race_info[p_ptr->rp.prace].title, p_ptr->lev);
935 	}
936 
937 	(void)fd_close(highscore_fd);
938 	highscore_fd = -1;
939 }
940 
941 
942 /*
943  * Race Legends
944  * -KMW-
945  */
race_legends(void)946 void race_legends(void)
947 {
948 	int i;
949 
950 	for (i = 0; i < MAX_RACES; i++)
951 	{
952 		race_score(i);
953 		msgf("Hit any key to continue");
954 		message_flush();
955         clear_region(0, 5, 18);
956 	}
957 }
958 
959 
960 /*
961  * Change the player into a King!			-RAK-
962  */
kingly(void)963 static void kingly(void)
964 {
965 	/* Hack -- retire in town */
966 	p_ptr->depth = 0;
967 
968 	/* Fake death */
969 	(void)strcpy(p_ptr->state.died_from, "Ripe Old Age");
970 
971 	/* Restore the experience */
972 	p_ptr->exp = p_ptr->max_exp;
973 
974 	/* Restore the level */
975 	p_ptr->lev = p_ptr->max_lev;
976 
977 	/* Hack -- Instant Gold */
978 	p_ptr->au += 10000000L;
979 
980 	/* Clear screen */
981 	Term_clear();
982 
983 	/* Display a crown */
984 	put_fstr(22, 1, "             %%%\n"
985 					"             %@%\n"
986 					"     ___      %      ___\n"
987 					"  _=$$###\\   #####	 /###$$=_\n"
988 					" $$_______##$$_____$$##_______$$\n"
989 					"(**   **   **   **   **   **)\n"
990 					"(**   **   **   **   **   **)\n"
991 					" TTTTTTTTTTTTTTTTTTTTTTTTTTT\n"
992 					" \\. $$$$$$ .\\.  $$$$$$  ./. $$$$$$ ./\n"
993 					"  \\  *    |   *   |    *  /\n"
994 					"   \\.    .|.     .|.    ./\n"
995 					"    ##H##H##H###H##H##H##\n"
996 					"    ##H##H##H###H##H##H##\n");
997 
998 	/* Display a message */
999 	put_fstr(26, 15, "Veni, Vidi, Vici!");
1000 	put_fstr(21, 16, "I came, I saw, I conquered!");
1001 	put_fstr(22, 17, "All Hail the Mighty %s!", sp_ptr->winner);
1002 
1003 	/* Flush input */
1004 	flush();
1005 
1006 	/* Wait for response */
1007 	pause_line(23);
1008 }
1009 
ingame_score(bool * initialized,bool game_in_progress)1010 void ingame_score(bool *initialized, bool game_in_progress)
1011 {
1012 	char buf[1024];
1013 
1014 	/* Build the filename */
1015 	path_make(buf, ANGBAND_DIR_APEX, "scores.raw");
1016 
1017 	/* Open the binary high score file, for reading */
1018 	highscore_fd = fd_open(buf, O_RDONLY);
1019 
1020 	/* Paranoia -- No score file */
1021 	if (highscore_fd < 0)
1022 	{
1023 		msgf("Score file unavailable.");
1024 	}
1025 	else
1026 	{
1027 		/* Prevent various functions */
1028 		*initialized = FALSE;
1029 
1030 		/* Display the scores */
1031 		if (game_in_progress && character_generated)
1032 		{
1033 			/* Show a selection of the score list */
1034 			predict_score();
1035 		}
1036 		else
1037 		{
1038 			/* Save Screen */
1039 			screen_save();
1040 
1041 			/* Show all the scores */
1042 			(void)display_scores_aux(0, MAX_HISCORES, -1, NULL);
1043 
1044 			/* Load screen */
1045 			screen_load();
1046 
1047 			/* Hack - Flush it */
1048 			Term_fresh();
1049 		}
1050 
1051 		/* Shut the high score file */
1052 		(void)fd_close(highscore_fd);
1053 
1054 		/* Forget the high score fd */
1055 		highscore_fd = -1;
1056 
1057 		/* We are ready again */
1058 		*initialized = TRUE;
1059 	}
1060 }
1061 
1062 
1063 /*
1064  * Display a "tomb-stone"
1065  */
print_tomb(void)1066 static void print_tomb(void)
1067 {
1068 	bool done = FALSE;
1069 
1070 	/* Print the text-tombstone */
1071 	if (!done)
1072 	{
1073 		char buf[1024];
1074 
1075 		FILE *fp;
1076 
1077 		time_t ct = time((time_t) 0);
1078 
1079 		/* Clear screen */
1080 		Term_clear();
1081 
1082 		/* Build the filename */
1083 		path_make(buf, ANGBAND_DIR_FILE, "dead.txt");
1084 
1085 		/* Open the News file */
1086 		fp = my_fopen(buf, "r");
1087 
1088 		/* Dump */
1089 		if (fp)
1090 		{
1091 			int i = 0;
1092 
1093 			/* Dump the file to the screen */
1094 			while (0 == my_fgets(fp, buf, 1024))
1095 			{
1096 				/* Display and advance */
1097 				put_fstr(0, i++, buf);
1098 			}
1099 
1100 			/* Close */
1101 			my_fclose(fp);
1102 		}
1103 
1104 
1105 		put_fstr(11, 6, "%v", center_string, 31, player_name);
1106 
1107 		put_fstr(11, 7, "%v", center_string, 31, "the");
1108 
1109 
1110 		/* King or Queen */
1111 		if (p_ptr->state.total_winner || (p_ptr->lev > PY_MAX_LEVEL))
1112 		{
1113 			put_fstr(11, 8, "%v", center_string, 31, "Magnificent");
1114 		}
1115 
1116 		/* Normal */
1117 		else
1118 		{
1119 			put_fstr(11, 8, "%v", center_string, 31,
1120 					 player_title[p_ptr->rp.pclass][(p_ptr->lev - 1) / 5]);
1121 		}
1122 
1123 		put_fstr(11, 10, "%v", center_string, 31, cp_ptr->title);
1124 
1125 		put_fstr(11, 11, "%v", center_string, 31, "Level: %d", (int)p_ptr->lev);
1126 
1127 		put_fstr(11, 12, "%v", center_string, 31, "Exp: %ld", (long)p_ptr->exp);
1128 
1129 		put_fstr(11, 13, "%v", center_string, 31, "AU: %ld", (long)p_ptr->au);
1130 
1131 		put_fstr(11, 14, "%v", center_string, 31, "Killed on Level %d", p_ptr->depth);
1132 
1133 
1134 		if (strlen(p_ptr->state.died_from) > 24)
1135 		{
1136 			put_fstr(11, 15, "%v", center_string, 31, "by %.24s.", p_ptr->state.died_from);
1137 		}
1138 		else
1139 		{
1140 			put_fstr(11, 15, "%v", center_string, 31, "by %s.", p_ptr->state.died_from);
1141 		}
1142 
1143 		put_fstr(11, 17, "%v", center_string, 31, "%-.24s", ctime(&ct));
1144 	}
1145 }
1146 
1147 
1148 /*
1149  * Display some character info
1150  */
show_info(void)1151 static void show_info(void)
1152 {
1153 	int i, j, l;
1154 	object_type *o_ptr;
1155 	store_type *st_ptr;
1156 
1157 	/* Hack -- Know everything in the equipment */
1158 	for (i = 0; i < EQUIP_MAX; i++)
1159 	{
1160 		o_ptr = &p_ptr->equipment[i];
1161 
1162 		/* Skip non-objects */
1163 		if (!o_ptr->k_idx) continue;
1164 
1165 		/* Aware and Known */
1166 		object_aware(o_ptr);
1167 		object_known(o_ptr);
1168 		object_mental(o_ptr);
1169 
1170 		/* Save all the known flags */
1171 		o_ptr->kn_flags[0] = o_ptr->flags[0];
1172 		o_ptr->kn_flags[1] = o_ptr->flags[1];
1173 		o_ptr->kn_flags[2] = o_ptr->flags[2];
1174 		o_ptr->kn_flags[3] = o_ptr->flags[3];
1175 	}
1176 
1177 	/* Hack -- Know everything in the inventory */
1178 	OBJ_ITT_START (p_ptr->inventory, o_ptr)
1179 	{
1180 		/* Aware and Known */
1181 		object_aware(o_ptr);
1182 		object_known(o_ptr);
1183 		object_mental(o_ptr);
1184 
1185 		/* Save all the known flags */
1186 		o_ptr->kn_flags[0] = o_ptr->flags[0];
1187 		o_ptr->kn_flags[1] = o_ptr->flags[1];
1188 		o_ptr->kn_flags[2] = o_ptr->flags[2];
1189 		o_ptr->kn_flags[3] = o_ptr->flags[3];
1190 	}
1191 	OBJ_ITT_END;
1192 
1193 	for (i = 1; i < z_info->wp_max; i++)
1194 	{
1195 		for (j = 0; j < place[i].numstores; j++)
1196 		{
1197 			st_ptr = &place[i].store[j];
1198 
1199 			if (st_ptr->type == BUILD_STORE_HOME)
1200 			{
1201 				/* Hack -- Know everything in the home */
1202 				OBJ_ITT_START (st_ptr->stock, o_ptr)
1203 				{
1204 					/* Aware and Known */
1205 					object_aware(o_ptr);
1206 					object_known(o_ptr);
1207 					object_mental(o_ptr);
1208 
1209 					/* Save all the known flags */
1210 					o_ptr->kn_flags[0] = o_ptr->flags[0];
1211 					o_ptr->kn_flags[1] = o_ptr->flags[1];
1212 					o_ptr->kn_flags[2] = o_ptr->flags[2];
1213 					o_ptr->kn_flags[3] = o_ptr->flags[3];
1214 				}
1215 				OBJ_ITT_END;
1216 			}
1217 		}
1218 	}
1219 
1220 	/* Hack -- Recalculate bonuses */
1221 	p_ptr->update |= (PU_BONUS);
1222 
1223 	/* Handle stuff */
1224 	handle_stuff();
1225 
1226 	/* Display player */
1227 	display_player(DISPLAY_PLAYER_STANDARD);
1228 
1229 	/* Prompt for inventory */
1230 	prtf(0, 23, "Hit any key to see more information (ESC to abort): ");
1231 
1232 	/* Flush keys */
1233 	flush();
1234 
1235 	/* Allow abort at this point */
1236 	if (inkey() == ESCAPE) return;
1237 
1238 
1239 	/* Show equipment */
1240 	Term_clear();
1241 
1242 	/* Equipment -- if any */
1243 	item_tester_full = TRUE;
1244 	show_equip(FALSE);
1245 
1246 	prtf(0, 0, "You are using: -more-");
1247 
1248 	/* Flush keys */
1249 	flush();
1250 
1251 	if (inkey() == ESCAPE) return;
1252 
1253 
1254 	/* Show inventory */
1255 	Term_clear();
1256 
1257 	/* Inventory -- if any */
1258 	item_tester_full = TRUE;
1259 	show_list(p_ptr->inventory, FALSE);
1260 
1261 	prtf(0, 0, "You are carrying: -more-");
1262 
1263 	/* Flush keys */
1264 	flush();
1265 
1266 	if (inkey() == ESCAPE) return;
1267 
1268 	for (i = 1; i < z_info->wp_max; i++)
1269 	{
1270 		for (l = 0; l < place[i].numstores; l++)
1271 		{
1272 			st_ptr = &place[i].store[l];
1273 
1274 			if (st_ptr->type == BUILD_STORE_HOME)
1275 			{
1276 				/* Home -- if anything there */
1277 				if (st_ptr->stock)
1278 				{
1279 					/* Initialise counter */
1280 					j = 0;
1281 
1282 					/* Clear screen */
1283 					Term_clear();
1284 
1285 					/* Display contents of the home */
1286 					OBJ_ITT_START (st_ptr->stock, o_ptr)
1287 					{
1288 						/* Print header, clear line */
1289 						prtf(4, j + 2, "%c) %s%v", I2A(j),
1290 							 color_seq[tval_to_attr[o_ptr->tval]],
1291 							 OBJECT_FMT(o_ptr, TRUE, 3));
1292 
1293 						/* Show 12 items at a time */
1294 						if (j == 12)
1295 						{
1296 							/* Caption */
1297 							prtf(0, 0, "Your home in %s: -more-",
1298 								 place[i].name);
1299 
1300 							/* Flush keys */
1301 							flush();
1302 
1303 							/* Wait for it */
1304 							if (inkey() == ESCAPE) return;
1305 
1306 							/* Restart counter */
1307 							j = 0;
1308 
1309 							/* Clear screen */
1310 							Term_clear();
1311 						}
1312 					}
1313 					OBJ_ITT_END;
1314 				}
1315 			}
1316 		}
1317 	}
1318 }
1319 
1320 
1321 
close_game_handle_death(void)1322 static void close_game_handle_death(void)
1323 {
1324 	char ch;
1325 
1326 	/* Handle retirement */
1327 	if (p_ptr->state.total_winner)
1328 	{
1329 		/* Save winning message to notes file. */
1330 		if (take_notes)
1331 		{
1332 			add_note_type(NOTE_WINNER);
1333 		}
1334 
1335 		kingly();
1336 	}
1337 
1338 	/* Save memories */
1339 	if (!munchkin_death || get_check("Save death? "))
1340 	{
1341 		if (!save_player()) msgf("death save failed!");
1342 	}
1343 
1344 #if 0
1345 	/* Dump bones file */
1346 	make_bones();
1347 #endif
1348 
1349 	/* Inform notes file that you are dead */
1350 	if (take_notes)
1351 	{
1352 		char long_day[30];
1353 		time_t ct = time((time_t *) NULL);
1354 
1355 		/* Get the date */
1356 		(void)strftime(long_day, 30, "%Y-%m-%d at %H:%M:%S", localtime(&ct));
1357 
1358 		/* Output to the notes file */
1359 		output_note("\n%s was killed by %s on %s\n", player_name,
1360 				p_ptr->state.died_from, long_day);
1361 	}
1362 
1363 	/* Enter player in high score list */
1364 	enter_score();
1365 
1366 	/* You are dead */
1367 	print_tomb();
1368 
1369 	/* Describe options */
1370 	prtf(0, 23, "(D) Dump char record  (C) Show char info  (T) Show top scores  (ESC) Exit");
1371 
1372 	/* Flush messages */
1373 	message_flush();
1374 
1375 	/* Flush all input keys */
1376 	flush();
1377 
1378 	/* Player selection */
1379 	while (TRUE)
1380 	{
1381 		/* Save screen */
1382 		/* Note that Term_save() and Term_load() must match in pairs */
1383 		Term_save();
1384 
1385 		/* Flush all input keys */
1386 		flush();
1387 
1388 		ch = inkey();
1389 
1390 		switch (ch)
1391 		{
1392 			case ESCAPE:
1393 			{
1394 				/* Flush the keys */
1395 				flush();
1396 
1397 				if (get_check("Do you really want to exit? "))
1398 				{
1399 					/* Save dead player */
1400 					if (!save_player())
1401 					{
1402 						msgf("Death save failed!");
1403 						message_flush();
1404 					}
1405 
1406 #if 0
1407 					/* Dump bones file */
1408 					make_bones();
1409 #endif
1410 
1411 					/* XXX We now have an unmatched Term_save() */
1412 					Term_load();
1413 
1414 					/* Go home, we're done */
1415 					return;
1416 				}
1417 				else
1418 				{
1419 					break;
1420 				}
1421 			}
1422 
1423 			case 'd':
1424 			case 'D':
1425 			{
1426 				/* Dump char file */
1427 				char tmp[160] = "";
1428 
1429 				/* Clear this line first */
1430 				clear_row(23);
1431 
1432 				/* Prompt */
1433 				put_fstr(0, 23, "Filename: ");
1434 
1435 				/* Ask for filename (or abort) */
1436 				if (!askfor_aux(tmp, 60)) break;
1437 
1438 				/* Ignore Return */
1439 				if (!tmp[0]) break;
1440 
1441 				/* Dump a character file */
1442 				(void)file_character(tmp, FALSE);
1443 
1444 				break;
1445 			}
1446 
1447 			case 'c':
1448 			case 'C':
1449 			{
1450 				/* Remove options line, so we don't dump it */
1451 				clear_row(23);
1452 
1453 				/* Show char info */
1454 				show_info();
1455 				break;
1456 			}
1457 
1458 			case 't':
1459 			case 'T':
1460 			{
1461 				/* Show top twenty */
1462 				top_twenty();
1463 				break;
1464 			}
1465 		}
1466 
1467 		/* Restore the screen */
1468 		Term_load();
1469 	}
1470 }
1471 
1472 
1473 /*
1474  * Close up the current game (player may or may not be dead)
1475  *
1476  * This function is called only from "main.c" and "signals.c".
1477  */
close_game(void)1478 void close_game(void)
1479 {
1480 	char buf[1024];
1481 
1482 	/* Handle stuff */
1483 	handle_stuff();
1484 
1485 	/* Flush the messages */
1486 	message_flush();
1487 
1488 	/* Flush the input */
1489 	flush();
1490 
1491 	/* No suspending now */
1492 	signals_ignore_tstp();
1493 
1494 	/* Hack -- Character is now "icky" */
1495 	screen_save();
1496 
1497 	/* Build the filename */
1498 	path_make(buf, ANGBAND_DIR_APEX, "scores.raw");
1499 
1500 	/* Grab permissions */
1501 	safe_setuid_grab();
1502 
1503 	/* Open the high score file, for reading/writing */
1504 	highscore_fd = fd_open(buf, O_RDWR);
1505 
1506 	/* Drop permissions */
1507 	safe_setuid_drop();
1508 
1509 	if (p_ptr->state.is_dead)
1510 	{
1511 		/* Handle death */
1512 		close_game_handle_death();
1513 	}
1514 
1515 	/* Still alive */
1516 	else
1517 	{
1518 		int wid, hgt;
1519 
1520 		/* Save the game */
1521 		do_cmd_save_game(FALSE);
1522 
1523 		/* If note-taking enabled, write session end to notes file */
1524 		if (take_notes)
1525 		{
1526 			add_note_type(NOTE_SAVE_GAME);
1527 		}
1528 
1529 		/* Get size */
1530 		Term_get_size(&wid, &hgt);
1531 
1532 		/* Prompt for scores XXX XXX XXX */
1533 		prtf(0, hgt - 1, "Press Return (or Escape).");
1534 
1535 		/* Predict score (or ESCAPE) */
1536 		if (inkey() != ESCAPE) predict_score();
1537 	}
1538 
1539 
1540 	/* Shut the high score file */
1541 	(void)fd_close(highscore_fd);
1542 
1543 	/* Forget the high score fd */
1544 	highscore_fd = -1;
1545 
1546 	/* No longer icky */
1547 	screen_load();
1548 
1549 	/* Allow suspending now */
1550 	signals_handle_tstp();
1551 }
1552 
1553 
1554 /*
1555  * Handle abrupt death of the visual system
1556  *
1557  * This routine is called only in very rare situations, and only
1558  * by certain visual systems, when they experience fatal errors.
1559  *
1560  * XXX XXX Hack -- clear the death flag when creating a HANGUP
1561  * save file so that player can see tombstone when restart.
1562  */
exit_game_panic(void)1563 void exit_game_panic(void)
1564 {
1565 	/* If nothing important has happened, just quit */
1566 	if (!character_generated || character_saved) quit("panic");
1567 
1568 	/* Mega-Hack -- see "msgf()" */
1569 	msg_flag = FALSE;
1570 
1571 	/* Clear the top line */
1572 	clear_msg();
1573 
1574 	/* Hack -- turn off some things */
1575 	disturb(TRUE);
1576 
1577 	/* Mega-Hack -- Delay death */
1578 	if (p_ptr->chp < 0) p_ptr->state.is_dead = FALSE;
1579 
1580 	/* Hardcode panic save */
1581 	p_ptr->state.panic_save = 1;
1582 
1583 	/* Forbid suspend */
1584 	signals_ignore_tstp();
1585 
1586 	/* Indicate panic save */
1587 	(void)strcpy(p_ptr->state.died_from, "(panic save)");
1588 
1589 	/* Panic save, or get worried */
1590 	if (!save_player()) quit("panic save failed!");
1591 
1592 	/* Successful panic save */
1593 	quit("panic save succeeded!");
1594 }
1595