OSDN Git Service

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