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