OSDN Git Service

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