OSDN Git Service

Merge pull request #2122 from sikabane-works/release/3.0.0Alpha52
[hengbandforosx/hengbandosx.git] / src / core / scores.cpp
1 /*!
2  * @file scores.c
3  * @brief ハイスコア処理 / Highscores handling
4  * @date 2014/07/14
5  * @author
6  * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
7  * This software may be copied and distributed for educational, research,
8  * and not for profit purposes provided that this copyright and statement
9  * are included in all such copies.  Other copyrights may also apply.
10  * 2014 Deskull rearranged comment for Doxygen.
11  */
12
13 #include "core/scores.h"
14 #include "cmd-io/cmd-dump.h"
15 #include "core/asking-player.h"
16 #include "core/score-util.h"
17 #include "core/turn-compensator.h"
18 #include "dungeon/dungeon.h"
19 #include "game-option/birth-options.h"
20 #include "game-option/game-play-options.h"
21 #include "io/files-util.h"
22 #include "io/input-key-acceptor.h"
23 #include "io/report.h"
24 #include "io/signal-handlers.h"
25 #include "io/uid-checker.h"
26 #include "locale/japanese.h"
27 #include "player-info/class-info.h"
28 #include "player/player-personality.h"
29 #include "player/player-status.h"
30 #include "player/race-info-table.h"
31 #include "system/angband-version.h"
32 #include "system/floor-type-definition.h"
33 #include "system/player-type-definition.h"
34 #include "term/screen-processor.h"
35 #include "term/term-color-types.h"
36 #include "util/angband-files.h"
37 #include "util/enum-converter.h"
38 #include "util/int-char-converter.h"
39 #include "util/string-processor.h"
40 #include "view/display-messages.h"
41 #include "view/display-scores.h"
42 #include "world/world.h"
43
44 /*!
45  * @brief 所定ポインタへスコア情報を書き込む / Write one score to the highscore file
46  * @param score スコア情報参照ポインタ
47  * @return エラーコード(問題がなければ0を返す)
48  */
49 static int highscore_write(high_score *score)
50 {
51     /* Write the record, note failure */
52     return (fd_write(highscore_fd, (char *)(score), sizeof(high_score)));
53 }
54
55 /*!
56  * @brief スコア情報を全て得るまで繰り返し取得する / Just determine where a new score *would* be placed
57  * @param score スコア情報参照ポインタ
58  * @return 正常ならば(MAX_HISCORES - 1)、問題があれば-1を返す
59  */
60 static int highscore_where(high_score *score)
61 {
62     /* Paranoia -- it may not have opened */
63     if (highscore_fd < 0)
64         return -1;
65
66     /* Go to the start of the highscore file */
67     if (highscore_seek(0))
68         return -1;
69
70     /* Read until we get to a higher score */
71     high_score the_score;
72     int my_score = atoi(score->pts);
73     for (int i = 0; i < MAX_HISCORES; i++) {
74         int old_score;
75         if (highscore_read(&the_score))
76             return (i);
77         old_score = atoi(the_score.pts);
78         if (my_score > old_score)
79             return (i);
80     }
81
82     /* The "last" entry is always usable */
83     return MAX_HISCORES - 1;
84 }
85
86 /*!
87  * @brief スコア情報をバッファの末尾に追加する / Actually place an entry into the high score file
88  * @param score スコア情報参照ポインタ
89  * @return 正常ならば書き込んだスロット位置、問題があれば-1を返す / Return the location (0 is best) or -1 on "failure"
90  */
91 static int highscore_add(high_score *score)
92 {
93     /* Paranoia -- it may not have opened */
94     if (highscore_fd < 0)
95         return -1;
96
97     /* Determine where the score should go */
98     int slot = highscore_where(score);
99
100     /* Hack -- Not on the list */
101     if (slot < 0)
102         return -1;
103
104     /* Hack -- prepare to dump the new score */
105     high_score the_score = (*score);
106
107     /* Slide all the scores down one */
108     bool done = false;
109     high_score tmpscore;
110     for (int i = slot; !done && (i < MAX_HISCORES); i++) {
111         /* Read the old guy, note errors */
112         if (highscore_seek(i))
113             return -1;
114         if (highscore_read(&tmpscore))
115             done = true;
116
117         /* Back up and dump the score we were holding */
118         if (highscore_seek(i))
119             return -1;
120         if (highscore_write(&the_score))
121             return -1;
122
123         /* Hack -- Save the old score, for the next pass */
124         the_score = tmpscore;
125     }
126
127     /* Return location used */
128     return slot;
129 }
130
131 /*!
132  * @brief スコアサーバへの転送処理
133  * @param current_player_ptr プレイヤーへの参照ポインタ
134  * @param do_send 実際に転送ア処置を行うか否か
135  * @return 転送が成功したらTRUEを返す
136  */
137 bool send_world_score(PlayerType *current_player_ptr, bool do_send)
138 {
139 #ifdef WORLD_SCORE
140     if (!send_score || !do_send) {
141         return true;
142     }
143
144     auto is_registration = get_check_strict(
145         current_player_ptr, _("スコアをスコア・サーバに登録しますか? ", "Do you send score to the world score server? "), (CHECK_NO_ESCAPE | CHECK_NO_HISTORY));
146     if (!is_registration) {
147         return true;
148     }
149
150     prt("", 0, 0);
151     prt(_("送信中..", "Sending..."), 0, 0);
152     term_fresh();
153     screen_save();
154     auto successful_send = report_score(current_player_ptr);
155     screen_load();
156     if (!successful_send) {
157         return false;
158     }
159
160     prt(_("完了。何かキーを押してください。", "Completed.  Hit any key."), 0, 0);
161     (void)inkey();
162 #else
163     (void)current_player_ptr;
164     (void)do_send;
165 #endif
166     return true;
167 }
168
169 /*!
170  * @brief スコアの過去二十位内ランキングを表示する
171  * Enters a players name on a hi-score table, if "legal", and in any
172  * case, displays some relevant portion of the high score list.
173  * @param current_player_ptr プレイヤーへの参照ポインタ
174  * @return エラーコード
175  * @details
176  * Assumes "signals_ignore_tstp()" has been called.
177  */
178 errr top_twenty(PlayerType *current_player_ptr)
179 {
180     high_score the_score = {};
181     char buf[32];
182
183     /* Save the version */
184     sprintf(the_score.what, "%u.%u.%u", H_VER_MAJOR, H_VER_MINOR, H_VER_PATCH);
185
186     /* Calculate and save the points */
187     sprintf(the_score.pts, "%9ld", (long)calc_score(current_player_ptr));
188     the_score.pts[9] = '\0';
189
190     /* Save the current gold */
191     sprintf(the_score.gold, "%9lu", (long)current_player_ptr->au);
192     the_score.gold[9] = '\0';
193
194     /* Save the current turn */
195     sprintf(the_score.turns, "%9lu", (long)turn_real(current_player_ptr, w_ptr->game_turn));
196     the_score.turns[9] = '\0';
197
198     time_t ct = time((time_t *)0);
199
200     /* Save the date in standard encoded form (9 chars) */
201     strftime(the_score.day, 10, "@%Y%m%d", localtime(&ct));
202
203     /* Save the player name (15 chars) */
204     sprintf(the_score.who, "%-.15s", current_player_ptr->name);
205
206     /* Save the player info */
207     sprintf(the_score.uid, "%7u", current_player_ptr->player_uid);
208     sprintf(the_score.sex, "%c", (current_player_ptr->psex ? 'm' : 'f'));
209     snprintf(buf, sizeof(buf), "%2d", std::min(enum2i(current_player_ptr->prace), MAX_RACES));
210     memcpy(the_score.p_r, buf, 3);
211     snprintf(buf, sizeof(buf), "%2d", enum2i(std::min(current_player_ptr->pclass, PlayerClassType::MAX)));
212     memcpy(the_score.p_c, buf, 3);
213     snprintf(buf, sizeof(buf), "%2d", std::min(current_player_ptr->ppersonality, MAX_PERSONALITIES));
214     memcpy(the_score.p_a, buf, 3);
215
216     /* Save the level and such */
217     sprintf(the_score.cur_lev, "%3d", std::min<ushort>(current_player_ptr->lev, 999));
218     sprintf(the_score.cur_dun, "%3d", (int)current_player_ptr->current_floor_ptr->dun_level);
219     sprintf(the_score.max_lev, "%3d", std::min<ushort>(current_player_ptr->max_plv, 999));
220     sprintf(the_score.max_dun, "%3d", (int)max_dlv[current_player_ptr->dungeon_idx]);
221
222     /* Save the cause of death (31 chars) */
223     if (strlen(current_player_ptr->died_from) >= sizeof(the_score.how)) {
224 #ifdef JP
225         angband_strcpy(the_score.how, current_player_ptr->died_from, sizeof(the_score.how) - 2);
226         strcat(the_score.how, "…");
227 #else
228         angband_strcpy(the_score.how, current_player_ptr->died_from, sizeof(the_score.how) - 3);
229         strcat(the_score.how, "...");
230 #endif
231     } else {
232         strcpy(the_score.how, current_player_ptr->died_from);
233     }
234
235     /* Grab permissions */
236     safe_setuid_grab(current_player_ptr);
237
238     /* Lock (for writing) the highscore file, or fail */
239     errr err = fd_lock(highscore_fd, F_WRLCK);
240
241     /* Drop permissions */
242     safe_setuid_drop();
243
244     if (err)
245         return 1;
246
247     /* Add a new entry to the score list, see where it went */
248     int j = highscore_add(&the_score);
249
250     /* Grab permissions */
251     safe_setuid_grab(current_player_ptr);
252
253     /* Unlock the highscore file, or fail */
254     err = fd_lock(highscore_fd, F_UNLCK);
255
256     /* Drop permissions */
257     safe_setuid_drop();
258
259     if (err)
260         return 1;
261
262     /* Hack -- Display the top fifteen scores */
263     if (j < 10) {
264         display_scores(0, 15, j, nullptr);
265         return 0;
266     }
267
268     /* Display the scores surrounding the player */
269     display_scores(0, 5, j, nullptr);
270     display_scores(j - 2, j + 7, j, nullptr);
271     return 0;
272 }
273
274 /*!
275  * @brief プレイヤーの現在のスコアをランキングに挟む /
276  * Predict the players location, and display it.
277  * @return エラーコード
278  */
279 errr predict_score(PlayerType *current_player_ptr)
280 {
281     high_score the_score;
282     char buf[32];
283
284     /* No score file */
285     if (highscore_fd < 0) {
286         msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
287         msg_print(nullptr);
288         return 0;
289     }
290
291     /* Save the version */
292     sprintf(the_score.what, "%u.%u.%u", H_VER_MAJOR, H_VER_MINOR, H_VER_PATCH);
293
294     /* Calculate and save the points */
295     sprintf(the_score.pts, "%9ld", (long)calc_score(current_player_ptr));
296
297     /* Save the current gold */
298     sprintf(the_score.gold, "%9lu", (long)current_player_ptr->au);
299
300     /* Save the current turn */
301     sprintf(the_score.turns, "%9lu", (long)turn_real(current_player_ptr, w_ptr->game_turn));
302
303     /* Hack -- no time needed */
304     strcpy(the_score.day, _("今日", "TODAY"));
305
306     /* Save the player name (15 chars) */
307     sprintf(the_score.who, "%-.15s", current_player_ptr->name);
308
309     /* Save the player info */
310     sprintf(the_score.uid, "%7u", current_player_ptr->player_uid);
311     sprintf(the_score.sex, "%c", (current_player_ptr->psex ? 'm' : 'f'));
312     snprintf(buf, sizeof(buf), "%2d", std::min(enum2i(current_player_ptr->prace), MAX_RACES));
313     memcpy(the_score.p_r, buf, 3);
314     snprintf(buf, sizeof(buf), "%2d", enum2i(std::min(current_player_ptr->pclass, PlayerClassType::MAX)));
315     memcpy(the_score.p_c, buf, 3);
316     snprintf(buf, sizeof(buf), "%2d", std::min(current_player_ptr->ppersonality, MAX_PERSONALITIES));
317     memcpy(the_score.p_a, buf, 3);
318
319     /* Save the level and such */
320     sprintf(the_score.cur_lev, "%3d", std::min<ushort>(current_player_ptr->lev, 999));
321     sprintf(the_score.cur_dun, "%3d", (int)current_player_ptr->current_floor_ptr->dun_level);
322     sprintf(the_score.max_lev, "%3d", std::min<ushort>(current_player_ptr->max_plv, 999));
323     sprintf(the_score.max_dun, "%3d", (int)max_dlv[current_player_ptr->dungeon_idx]);
324
325     /* まだ死んでいないときの識別文字 */
326     strcpy(the_score.how, _("yet", "nobody (yet!)"));
327
328     /* See where the entry would be placed */
329     int j = highscore_where(&the_score);
330
331     /* Hack -- Display the top fifteen scores */
332     if (j < 10) {
333         display_scores(0, 15, j, &the_score);
334         return 0;
335     }
336
337     display_scores(0, 5, -1, nullptr);
338     display_scores(j - 2, j + 7, j, &the_score);
339     return 0;
340 }
341
342 /*!
343  * @brief スコアランキングの簡易表示 /
344  * show_highclass - selectively list highscores based on class -KMW-
345  */
346 void show_highclass(PlayerType *current_player_ptr)
347 {
348     screen_save();
349     char buf[1024], out_val[256];
350     path_build(buf, sizeof(buf), ANGBAND_DIR_APEX, "scores.raw");
351
352     highscore_fd = fd_open(buf, O_RDONLY);
353
354     if (highscore_fd < 0) {
355         msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
356         msg_print(nullptr);
357         return;
358     }
359
360     if (highscore_seek(0))
361         return;
362
363     high_score the_score;
364     for (int i = 0; i < MAX_HISCORES; i++)
365         if (highscore_read(&the_score))
366             break;
367
368     int m = 0;
369     int j = 0;
370     PLAYER_LEVEL clev = 0;
371     int pr;
372     while ((m < 9) && (j < MAX_HISCORES)) {
373         if (highscore_seek(j))
374             break;
375         if (highscore_read(&the_score))
376             break;
377         pr = atoi(the_score.p_r);
378         clev = (PLAYER_LEVEL)atoi(the_score.cur_lev);
379
380 #ifdef JP
381         sprintf(out_val, "   %3d) %sの%s (レベル %2d)", (m + 1), race_info[pr].title, the_score.who, clev);
382 #else
383         sprintf(out_val, "%3d) %s the %s (Level %2d)", (m + 1), the_score.who, race_info[pr].title, clev);
384 #endif
385
386         prt(out_val, (m + 7), 0);
387         m++;
388         j++;
389     }
390
391 #ifdef JP
392     sprintf(out_val, "あなた) %sの%s (レベル %2d)", race_info[enum2i(current_player_ptr->prace)].title, current_player_ptr->name, current_player_ptr->lev);
393 #else
394     sprintf(out_val, "You) %s the %s (Level %2d)", current_player_ptr->name, race_info[enum2i(current_player_ptr->prace)].title, current_player_ptr->lev);
395 #endif
396
397     prt(out_val, (m + 8), 0);
398
399     (void)fd_close(highscore_fd);
400     highscore_fd = -1;
401     prt(_("何かキーを押すとゲームに戻ります", "Hit any key to continue"), 0, 0);
402
403     (void)inkey();
404
405     for (j = 5; j < 18; j++)
406         prt("", j, 0);
407     screen_load();
408 }
409
410 /*!
411  * @brief スコアランキングの簡易表示(種族毎)サブルーチン /
412  * Race Legends -KMW-
413  * @param race_num 種族ID
414  */
415 void race_score(PlayerType *current_player_ptr, int race_num)
416 {
417     int i = 0, j, m = 0;
418     int pr, clev, lastlev;
419     high_score the_score;
420     char buf[1024], out_val[256], tmp_str[80];
421
422     lastlev = 0;
423
424     /* rr9: TODO - pluralize the race */
425     sprintf(tmp_str, _("最高の%s", "The Greatest of all the %s"), race_info[race_num].title);
426
427     prt(tmp_str, 5, 15);
428     path_build(buf, sizeof(buf), ANGBAND_DIR_APEX, "scores.raw");
429
430     highscore_fd = fd_open(buf, O_RDONLY);
431
432     if (highscore_fd < 0) {
433         msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
434         msg_print(nullptr);
435         return;
436     }
437
438     if (highscore_seek(0))
439         return;
440
441     for (i = 0; i < MAX_HISCORES; i++) {
442         if (highscore_read(&the_score))
443             break;
444     }
445
446     m = 0;
447     j = 0;
448
449     while ((m < 10) || (j < MAX_HISCORES)) {
450         if (highscore_seek(j))
451             break;
452         if (highscore_read(&the_score))
453             break;
454         pr = atoi(the_score.p_r);
455         clev = atoi(the_score.cur_lev);
456
457         if (pr == race_num) {
458 #ifdef JP
459             sprintf(out_val, "   %3d) %sの%s (レベル %2d)", (m + 1), race_info[pr].title, the_score.who, clev);
460 #else
461             sprintf(out_val, "%3d) %s the %s (Level %3d)", (m + 1), the_score.who, race_info[pr].title, clev);
462 #endif
463
464             prt(out_val, (m + 7), 0);
465             m++;
466             lastlev = clev;
467         }
468         j++;
469     }
470
471     /* add player if qualified */
472     if ((enum2i(current_player_ptr->prace) == race_num) && (current_player_ptr->lev >= lastlev)) {
473 #ifdef JP
474         sprintf(out_val, "あなた) %sの%s (レベル %2d)", race_info[enum2i(current_player_ptr->prace)].title, current_player_ptr->name, current_player_ptr->lev);
475 #else
476         sprintf(out_val, "You) %s the %s (Level %3d)", current_player_ptr->name, race_info[enum2i(current_player_ptr->prace)].title, current_player_ptr->lev);
477 #endif
478
479         prt(out_val, (m + 8), 0);
480     }
481
482     (void)fd_close(highscore_fd);
483     highscore_fd = -1;
484 }
485
486 /*!
487  * @brief スコアランキングの簡易表示(種族毎)メインルーチン /
488  * Race Legends -KMW-
489  */
490 void race_legends(PlayerType *current_player_ptr)
491 {
492     for (int i = 0; i < MAX_RACES; i++) {
493         race_score(current_player_ptr, i);
494         msg_print(_("何かキーを押すとゲームに戻ります", "Hit any key to continue"));
495         msg_print(nullptr);
496         for (int j = 5; j < 19; j++)
497             prt("", j, 0);
498     }
499 }
500
501 /*!
502  * @brief スコアファイル出力
503  * Display some character info
504  */
505 bool check_score(PlayerType *current_player_ptr)
506 {
507     term_clear();
508
509     /* No score file */
510     if (highscore_fd < 0) {
511         msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
512         msg_print(nullptr);
513         return false;
514     }
515
516     /* Wizard-mode pre-empts scoring */
517     if (w_ptr->noscore & 0x000F) {
518         msg_print(_("ウィザード・モードではスコアが記録されません。", "Score not registered for wizards."));
519         msg_print(nullptr);
520         return false;
521     }
522
523     /* Cheaters are not scored */
524     if (w_ptr->noscore & 0xFF00) {
525         msg_print(_("詐欺をやった人はスコアが記録されません。", "Score not registered for cheaters."));
526         msg_print(nullptr);
527         return false;
528     }
529
530     /* Interupted */
531     if (!w_ptr->total_winner && streq(current_player_ptr->died_from, _("強制終了", "Interrupting"))) {
532         msg_print(_("強制終了のためスコアが記録されません。", "Score not registered due to interruption."));
533         msg_print(nullptr);
534         return false;
535     }
536
537     /* Quitter */
538     if (!w_ptr->total_winner && streq(current_player_ptr->died_from, _("途中終了", "Quitting"))) {
539         msg_print(_("途中終了のためスコアが記録されません。", "Score not registered due to quitting."));
540         msg_print(nullptr);
541         return false;
542     }
543     return true;
544 }