3 * @brief ハイスコア処理 / Highscores handling
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.
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/input-key-acceptor.h"
22 #include "io/report.h"
23 #include "io/signal-handlers.h"
24 #include "io/uid-checker.h"
25 #include "player-info/class-info.h"
26 #include "player/player-personality.h"
27 #include "player/player-status.h"
28 #include "player/race-info-table.h"
29 #include "system/angband-version.h"
30 #include "system/floor-type-definition.h"
31 #include "system/player-type-definition.h"
32 #include "term/screen-processor.h"
33 #include "term/term-color-types.h"
34 #include "util/angband-files.h"
35 #include "util/enum-converter.h"
36 #include "util/int-char-converter.h"
37 #include "util/string-processor.h"
38 #include "view/display-messages.h"
39 #include "view/display-scores.h"
40 #include "world/world.h"
43 #include "locale/japanese.h"
47 * @brief 所定ポインタへスコア情報を書き込む / Write one score to the highscore file
48 * @param score スコア情報参照ポインタ
49 * @return エラーコード(問題がなければ0を返す)
51 static int highscore_write(high_score *score)
53 /* Write the record, note failure */
54 return (fd_write(highscore_fd, (char *)(score), sizeof(high_score)));
58 * @brief スコア情報を全て得るまで繰り返し取得する / Just determine where a new score *would* be placed
59 * @param score スコア情報参照ポインタ
60 * @return 正常ならば(MAX_HISCORES - 1)、問題があれば-1を返す
62 static int highscore_where(high_score *score)
64 /* Paranoia -- it may not have opened */
68 /* Go to the start of the highscore file */
69 if (highscore_seek(0))
72 /* Read until we get to a higher score */
74 int my_score = atoi(score->pts);
75 for (int i = 0; i < MAX_HISCORES; i++) {
77 if (highscore_read(&the_score))
79 old_score = atoi(the_score.pts);
80 if (my_score > old_score)
84 /* The "last" entry is always usable */
85 return MAX_HISCORES - 1;
89 * @brief スコア情報をバッファの末尾に追加する / Actually place an entry into the high score file
90 * @param score スコア情報参照ポインタ
91 * @return 正常ならば書き込んだスロット位置、問題があれば-1を返す / Return the location (0 is best) or -1 on "failure"
93 static int highscore_add(high_score *score)
95 /* Paranoia -- it may not have opened */
99 /* Determine where the score should go */
100 int slot = highscore_where(score);
102 /* Hack -- Not on the list */
106 /* Hack -- prepare to dump the new score */
107 high_score the_score = (*score);
109 /* Slide all the scores down one */
112 for (int i = slot; !done && (i < MAX_HISCORES); i++) {
113 /* Read the old guy, note errors */
114 if (highscore_seek(i))
116 if (highscore_read(&tmpscore))
119 /* Back up and dump the score we were holding */
120 if (highscore_seek(i))
122 if (highscore_write(&the_score))
125 /* Hack -- Save the old score, for the next pass */
126 the_score = tmpscore;
129 /* Return location used */
134 * @brief スコアサーバへの転送処理
135 * @param current_player_ptr プレーヤーへの参照ポインタ
136 * @param do_send 実際に転送ア処置を行うか否か
137 * @return 転送が成功したらTRUEを返す
139 bool send_world_score(player_type *current_player_ptr, bool do_send, display_player_pf display_player)
142 if (!send_score || !do_send) {
147 msg_print(_("初心者モードではワールドスコアに登録できません。", "Since you are in the Easy Mode, you cannot send score to world score server."));
151 auto is_registration = get_check_strict(
152 current_player_ptr, _("スコアをスコア・サーバに登録しますか? ", "Do you send score to the world score server? "), (CHECK_NO_ESCAPE | CHECK_NO_HISTORY));
153 if (!is_registration) {
158 prt(_("送信中..", "Sending..."), 0, 0);
161 auto successful_send = report_score(current_player_ptr, display_player);
163 if (!successful_send) {
167 prt(_("完了。何かキーを押してください。", "Completed. Hit any key."), 0, 0);
170 (void)current_player_ptr;
172 (void)display_player;
178 * @brief スコアの過去二十位内ランキングを表示する
179 * Enters a players name on a hi-score table, if "legal", and in any
180 * case, displays some relevant portion of the high score list.
181 * @param current_player_ptr スコアに適用するための現在プレイヤークリーチャー参照ポインタ
184 * Assumes "signals_ignore_tstp()" has been called.
186 errr top_twenty(player_type *current_player_ptr)
188 high_score the_score;
190 (void)WIPE(&the_score, high_score);
192 /* Save the version */
193 sprintf(the_score.what, "%u.%u.%u", FAKE_VER_MAJOR, FAKE_VER_MINOR, FAKE_VER_PATCH);
195 /* Calculate and save the points */
196 sprintf(the_score.pts, "%9ld", (long)calc_score(current_player_ptr));
197 the_score.pts[9] = '\0';
199 /* Save the current gold */
200 sprintf(the_score.gold, "%9lu", (long)current_player_ptr->au);
201 the_score.gold[9] = '\0';
203 /* Save the current turn */
204 sprintf(the_score.turns, "%9lu", (long)turn_real(current_player_ptr, current_world_ptr->game_turn));
205 the_score.turns[9] = '\0';
207 time_t ct = time((time_t *)0);
209 /* Save the date in standard encoded form (9 chars) */
210 strftime(the_score.day, 10, "@%Y%m%d", localtime(&ct));
212 /* Save the player name (15 chars) */
213 sprintf(the_score.who, "%-.15s", current_player_ptr->name);
215 /* Save the player info */
216 sprintf(the_score.uid, "%7u", current_player_ptr->player_uid);
217 sprintf(the_score.sex, "%c", (current_player_ptr->psex ? 'm' : 'f'));
218 snprintf(buf, sizeof(buf), "%2d", MIN(enum2i(current_player_ptr->prace), MAX_RACES));
219 memcpy(the_score.p_r, buf, 3);
220 snprintf(buf, sizeof(buf), "%2d", MIN(current_player_ptr->pclass, MAX_CLASS));
221 memcpy(the_score.p_c, buf, 3);
222 snprintf(buf, sizeof(buf), "%2d", MIN(current_player_ptr->pseikaku, MAX_PERSONALITIES));
223 memcpy(the_score.p_a, buf, 3);
225 /* Save the level and such */
226 sprintf(the_score.cur_lev, "%3d", MIN((uint16_t)current_player_ptr->lev, 999));
227 sprintf(the_score.cur_dun, "%3d", (int)current_player_ptr->current_floor_ptr->dun_level);
228 sprintf(the_score.max_lev, "%3d", MIN((uint16_t)current_player_ptr->max_plv, 999));
229 sprintf(the_score.max_dun, "%3d", (int)max_dlv[current_player_ptr->dungeon_idx]);
231 /* Save the cause of death (31 chars) */
232 if (strlen(current_player_ptr->died_from) >= sizeof(the_score.how)) {
234 angband_strcpy(the_score.how, current_player_ptr->died_from, sizeof(the_score.how) - 2);
235 strcat(the_score.how, "…");
237 angband_strcpy(the_score.how, current_player_ptr->died_from, sizeof(the_score.how) - 3);
238 strcat(the_score.how, "...");
241 strcpy(the_score.how, current_player_ptr->died_from);
244 /* Grab permissions */
245 safe_setuid_grab(current_player_ptr);
247 /* Lock (for writing) the highscore file, or fail */
248 errr err = fd_lock(highscore_fd, F_WRLCK);
250 /* Drop permissions */
256 /* Add a new entry to the score list, see where it went */
257 int j = highscore_add(&the_score);
259 /* Grab permissions */
260 safe_setuid_grab(current_player_ptr);
262 /* Unlock the highscore file, or fail */
263 err = fd_lock(highscore_fd, F_UNLCK);
265 /* Drop permissions */
271 /* Hack -- Display the top fifteen scores */
273 display_scores(0, 15, j, nullptr);
277 /* Display the scores surrounding the player */
278 display_scores(0, 5, j, nullptr);
279 display_scores(j - 2, j + 7, j, nullptr);
284 * @brief プレイヤーの現在のスコアをランキングに挟む /
285 * Predict the players location, and display it.
288 errr predict_score(player_type *current_player_ptr)
290 high_score the_score;
294 if (highscore_fd < 0) {
295 msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
300 /* Save the version */
301 sprintf(the_score.what, "%u.%u.%u", FAKE_VER_MAJOR, FAKE_VER_MINOR, FAKE_VER_PATCH);
303 /* Calculate and save the points */
304 sprintf(the_score.pts, "%9ld", (long)calc_score(current_player_ptr));
306 /* Save the current gold */
307 sprintf(the_score.gold, "%9lu", (long)current_player_ptr->au);
309 /* Save the current turn */
310 sprintf(the_score.turns, "%9lu", (long)turn_real(current_player_ptr, current_world_ptr->game_turn));
312 /* Hack -- no time needed */
313 strcpy(the_score.day, _("今日", "TODAY"));
315 /* Save the player name (15 chars) */
316 sprintf(the_score.who, "%-.15s", current_player_ptr->name);
318 /* Save the player info */
319 sprintf(the_score.uid, "%7u", current_player_ptr->player_uid);
320 sprintf(the_score.sex, "%c", (current_player_ptr->psex ? 'm' : 'f'));
321 snprintf(buf, sizeof(buf), "%2d", MIN(enum2i(current_player_ptr->prace), MAX_RACES));
322 memcpy(the_score.p_r, buf, 3);
323 snprintf(buf, sizeof(buf), "%2d", MIN(current_player_ptr->pclass, MAX_CLASS));
324 memcpy(the_score.p_c, buf, 3);
325 snprintf(buf, sizeof(buf), "%2d", MIN(current_player_ptr->pseikaku, MAX_PERSONALITIES));
326 memcpy(the_score.p_a, buf, 3);
328 /* Save the level and such */
329 sprintf(the_score.cur_lev, "%3d", MIN((uint16_t)current_player_ptr->lev, 999));
330 sprintf(the_score.cur_dun, "%3d", (int)current_player_ptr->current_floor_ptr->dun_level);
331 sprintf(the_score.max_lev, "%3d", MIN((uint16_t)current_player_ptr->max_plv, 999));
332 sprintf(the_score.max_dun, "%3d", (int)max_dlv[current_player_ptr->dungeon_idx]);
334 /* Hack -- no cause of death */
335 /* まだ死んでいないときの識別文字 */
336 strcpy(the_score.how, _("yet", "nobody (yet!)"));
338 /* See where the entry would be placed */
339 int j = highscore_where(&the_score);
341 /* Hack -- Display the top fifteen scores */
343 display_scores(0, 15, j, &the_score);
347 display_scores(0, 5, -1, nullptr);
348 display_scores(j - 2, j + 7, j, &the_score);
353 * @brief スコアランキングの簡易表示 /
354 * show_highclass - selectively list highscores based on class -KMW-
356 void show_highclass(player_type *current_player_ptr)
359 char buf[1024], out_val[256];
360 path_build(buf, sizeof(buf), ANGBAND_DIR_APEX, "scores.raw");
362 highscore_fd = fd_open(buf, O_RDONLY);
364 if (highscore_fd < 0) {
365 msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
370 if (highscore_seek(0))
373 high_score the_score;
374 for (int i = 0; i < MAX_HISCORES; i++)
375 if (highscore_read(&the_score))
380 PLAYER_LEVEL clev = 0;
382 while ((m < 9) && (j < MAX_HISCORES)) {
383 if (highscore_seek(j))
385 if (highscore_read(&the_score))
387 pr = atoi(the_score.p_r);
388 clev = (PLAYER_LEVEL)atoi(the_score.cur_lev);
391 sprintf(out_val, " %3d) %sの%s (レベル %2d)", (m + 1), race_info[pr].title, the_score.who, clev);
393 sprintf(out_val, "%3d) %s the %s (Level %2d)", (m + 1), the_score.who, race_info[pr].title, clev);
396 prt(out_val, (m + 7), 0);
402 sprintf(out_val, "あなた) %sの%s (レベル %2d)", race_info[enum2i(current_player_ptr->prace)].title, current_player_ptr->name, current_player_ptr->lev);
404 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);
407 prt(out_val, (m + 8), 0);
409 (void)fd_close(highscore_fd);
411 prt(_("何かキーを押すとゲームに戻ります", "Hit any key to continue"), 0, 0);
415 for (j = 5; j < 18; j++)
421 * @brief スコアランキングの簡易表示(種族毎)サブルーチン /
423 * @param race_num 種族ID
425 void race_score(player_type *current_player_ptr, int race_num)
428 int pr, clev, lastlev;
429 high_score the_score;
430 char buf[1024], out_val[256], tmp_str[80];
434 /* rr9: TODO - pluralize the race */
435 sprintf(tmp_str, _("最高の%s", "The Greatest of all the %s"), race_info[race_num].title);
438 path_build(buf, sizeof(buf), ANGBAND_DIR_APEX, "scores.raw");
440 highscore_fd = fd_open(buf, O_RDONLY);
442 if (highscore_fd < 0) {
443 msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
448 if (highscore_seek(0))
451 for (i = 0; i < MAX_HISCORES; i++) {
452 if (highscore_read(&the_score))
459 while ((m < 10) || (j < MAX_HISCORES)) {
460 if (highscore_seek(j))
462 if (highscore_read(&the_score))
464 pr = atoi(the_score.p_r);
465 clev = atoi(the_score.cur_lev);
467 if (pr == race_num) {
469 sprintf(out_val, " %3d) %sの%s (レベル %2d)", (m + 1), race_info[pr].title, the_score.who, clev);
471 sprintf(out_val, "%3d) %s the %s (Level %3d)", (m + 1), the_score.who, race_info[pr].title, clev);
474 prt(out_val, (m + 7), 0);
481 /* add player if qualified */
482 if ((enum2i(current_player_ptr->prace) == race_num) && (current_player_ptr->lev >= lastlev)) {
484 sprintf(out_val, "あなた) %sの%s (レベル %2d)", race_info[enum2i(current_player_ptr->prace)].title, current_player_ptr->name, current_player_ptr->lev);
486 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);
489 prt(out_val, (m + 8), 0);
492 (void)fd_close(highscore_fd);
497 * @brief スコアランキングの簡易表示(種族毎)メインルーチン /
500 void race_legends(player_type *current_player_ptr)
502 for (int i = 0; i < MAX_RACES; i++) {
503 race_score(current_player_ptr, i);
504 msg_print(_("何かキーを押すとゲームに戻ります", "Hit any key to continue"));
506 for (int j = 5; j < 19; j++)
513 * Display some character info
515 bool check_score(player_type *current_player_ptr)
520 if (highscore_fd < 0) {
521 msg_print(_("スコア・ファイルが使用できません。", "Score file unavailable."));
526 /* Wizard-mode pre-empts scoring */
527 if (current_world_ptr->noscore & 0x000F) {
528 msg_print(_("ウィザード・モードではスコアが記録されません。", "Score not registered for wizards."));
533 /* Cheaters are not scored */
534 if (current_world_ptr->noscore & 0xFF00) {
535 msg_print(_("詐欺をやった人はスコアが記録されません。", "Score not registered for cheaters."));
541 if (!current_world_ptr->total_winner && streq(current_player_ptr->died_from, _("強制終了", "Interrupting"))) {
542 msg_print(_("強制終了のためスコアが記録されません。", "Score not registered due to interruption."));
548 if (!current_world_ptr->total_winner && streq(current_player_ptr->died_from, _("途中終了", "Quitting"))) {
549 msg_print(_("途中終了のためスコアが記録されません。", "Score not registered due to quitting."));