OSDN Git Service

Merge pull request #3569 from sikabane-works/release/3.0.0.88-alpha
[hengbandforosx/hengbandosx.git] / src / player / process-death.cpp
1 /*!
2  * @brief 死亡・引退・切腹時の画面表示
3  * @date 2020/02/24
4  * @author Hourier
5  * @details
6  * core、files、view-mainwindowの参照禁止。コールバックで対応すること
7  */
8
9 #include "player/process-death.h"
10 #include "core/asking-player.h"
11 #include "core/stuff-handler.h"
12 #include "flavor/flavor-describer.h"
13 #include "floor/floor-town.h"
14 #include "game-option/game-play-options.h"
15 #include "inventory/inventory-slot-types.h"
16 #include "io/files-util.h"
17 #include "io/input-key-acceptor.h"
18 #include "object/item-tester-hooker.h"
19 #include "object/item-use-flags.h"
20 #include "perception/object-perception.h"
21 #include "player-info/class-info.h"
22 #include "store/store-util.h"
23 #include "store/store.h"
24 #include "system/floor-type-definition.h"
25 #include "system/item-entity.h"
26 #include "system/player-type-definition.h"
27 #include "system/redrawing-flags-updater.h"
28 #include "term/gameterm.h"
29 #include "term/screen-processor.h"
30 #include "util/buffer-shaper.h"
31 #include "util/int-char-converter.h"
32 #include "util/string-processor.h"
33 #include "view/display-inventory.h"
34 #include "view/display-messages.h"
35 #include "view/display-player.h"
36 #include "world/world.h"
37
38 constexpr auto GRAVE_LINE_WIDTH = 31;
39 constexpr auto GRAVE_LINE_START_COL = 11;
40 constexpr auto GRAVE_PLAYER_NAME_ROW = 6;
41 constexpr auto GRAVE_PLAYER_TITLE_ROW = 8;
42 constexpr auto GRAVE_PLAYER_CLASS_ROW = 10;
43 constexpr auto GRAVE_LEVEL_ROW = 11;
44 constexpr auto GRAVE_EXP_ROW = 12;
45 constexpr auto GRAVE_AU_ROW = 13;
46 constexpr auto GRAVE_KILLER_NAME_ROW = _(14, 15);
47 constexpr auto GRAVE_DEAD_PLACE_ROW = _(15, 14);
48 constexpr auto GRAVE_DEAD_DATETIME_ROW = 17;
49
50 /*!
51  * @brief 墓石に文字列を表示する
52  *
53  * 墓石のアスキーアート上に与えられた文字列 str を row で指定された行に表示する
54  * 表示する位置は GRAVE_LINE_START_COL から GRAVE_LIEN_WIDTH 文字分の幅で、
55  * それより str の幅が小さい場合は中央寄せして表示する。
56  *
57  * @param str 表示する文字列
58  * @param row 表示する行
59  */
60 static void show_tomb_line(std::string_view str, int row)
61 {
62     const auto head = GRAVE_LINE_WIDTH / 2 - str.length() / 2;
63     const auto tail = GRAVE_LINE_WIDTH - str.length() - head;
64     put_str(std::string(head, ' ').append(str).append(tail, ' '), row, GRAVE_LINE_START_COL);
65 }
66
67 /*!
68  * @brief 墓に基本情報を表示
69  * @param player_ptr プレイヤーへの参照ポインタ
70  */
71 static void show_basic_params(PlayerType *player_ptr)
72 {
73     show_tomb_line(format(_("レベル: %d", "Level: %d"), (int)player_ptr->lev), GRAVE_LEVEL_ROW);
74
75     show_tomb_line(format(_("経験値: %ld", "Exp: %ld"), (long)player_ptr->exp), GRAVE_EXP_ROW);
76
77     show_tomb_line(format(_("所持金: %ld", "AU: %ld"), (long)player_ptr->au), GRAVE_AU_ROW);
78 }
79
80 #ifdef JP
81 /*!
82  * @brief プレイヤーを殺したモンスターを墓に表示する (日本語版専用)
83  *
84  * モンスターの名称を最大で2行で墓石のアスキーアート上に表示する。
85  * 名称が1行に収まる場合、そのまま表示する。
86  * 名称が3行以上になる場合は、2行で表示できるだけ表示し、2行目の最後を…にして以降を省略する。
87  * 2行の場合は基本的に、前詰めで表示するが、モンスターの名称が ○○○『△△△』
88  * のようなタイプの場合で『△△△』の途中で改行される場合○○○を1行目に、『△△△』を2行目に
89  * 分割して表示することを試みる。但し『△△△』が1行に入り切らない場合はそのまま表示する。
90  *
91  * @param player_ptr プレイヤーへの参照ポインタ
92  * @return 続いて死亡した場所を表示するためのオフセット行数
93  */
94 static int show_killing_monster(PlayerType *player_ptr)
95 {
96     const auto lines = shape_buffer(player_ptr->died_from, GRAVE_LINE_WIDTH + 1);
97     if (lines.size() == 1) {
98         show_tomb_line(lines[0], GRAVE_KILLER_NAME_ROW);
99         return 0;
100     }
101
102     if (lines.size() >= 3) {
103         char buf[GRAVE_LINE_WIDTH + 1];
104         angband_strcpy(buf, lines[1], sizeof(buf) - 2);
105         angband_strcat(buf, "…", sizeof(buf));
106         show_tomb_line(lines[0], GRAVE_KILLER_NAME_ROW);
107         show_tomb_line(buf, GRAVE_KILLER_NAME_ROW + 1);
108         return 1;
109     }
110
111     if (const auto start_pos = lines[0].find("『");
112         (start_pos != std::string::npos) && suffix(lines[1], "』")) {
113         if (lines[0].length() + lines[1].length() - start_pos <= GRAVE_LINE_WIDTH) {
114             const auto &name = lines[0].substr(start_pos).append(lines[1]);
115             std::string_view title(lines[0].data(), start_pos);
116             show_tomb_line(title, GRAVE_KILLER_NAME_ROW);
117             show_tomb_line(name, GRAVE_KILLER_NAME_ROW + 1);
118             return 1;
119         }
120     }
121
122     show_tomb_line(lines[0], GRAVE_KILLER_NAME_ROW);
123     show_tomb_line(lines[1], GRAVE_KILLER_NAME_ROW + 1);
124     return 1;
125 }
126
127 /*!
128  * @brief どこで死んだかを表示する (日本語版専用)
129  * @param player_ptr プレイヤーへの参照ポインタ
130  * @param extra_line 追加の行数
131  */
132 static void show_dead_place(PlayerType *player_ptr, int extra_line)
133 {
134     if (streq(player_ptr->died_from, "ripe") || streq(player_ptr->died_from, "Seppuku")) {
135         return;
136     }
137
138     std::string place;
139     if (player_ptr->current_floor_ptr->dun_level == 0) {
140         concptr field_name = player_ptr->town_num ? "街" : "荒野";
141         if (streq(player_ptr->died_from, "途中終了")) {
142             place = format("%sで死んだ", field_name);
143         } else {
144             place = format("に%sで殺された", field_name);
145         }
146     } else if (streq(player_ptr->died_from, "途中終了")) {
147         place = format("地下 %d 階で死んだ", (int)player_ptr->current_floor_ptr->dun_level);
148     } else {
149         place = format("に地下 %d 階で殺された", (int)player_ptr->current_floor_ptr->dun_level);
150     }
151
152     show_tomb_line(place, GRAVE_DEAD_PLACE_ROW + extra_line);
153 }
154
155 /*!
156  * @brief 墓に刻む言葉を細かく表示 (日本語版専用)
157  * @param player_ptr プレイヤーへの参照ポインタ
158  */
159 static void show_tomb_detail(PlayerType *player_ptr)
160 {
161     auto offset = 0;
162     if (streq(player_ptr->died_from, "途中終了")) {
163         show_tomb_line("<自殺>", GRAVE_KILLER_NAME_ROW);
164     } else if (streq(player_ptr->died_from, "ripe")) {
165         show_tomb_line("引退後に天寿を全う", GRAVE_KILLER_NAME_ROW);
166     } else if (streq(player_ptr->died_from, "Seppuku")) {
167         show_tomb_line("勝利の後、切腹", GRAVE_KILLER_NAME_ROW);
168     } else {
169         offset = show_killing_monster(player_ptr);
170     }
171
172     show_dead_place(player_ptr, offset);
173 }
174 #else
175
176 /*!
177  * @brief Detailed display of words engraved on the tomb (English version only)
178  * @param player_ptr reference pointer to the player
179  * @return nothing
180  */
181 static void show_tomb_detail(PlayerType *player_ptr)
182 {
183     show_tomb_line(format("Killed on Level %d", player_ptr->current_floor_ptr->dun_level), GRAVE_DEAD_PLACE_ROW);
184
185     auto lines = shape_buffer(format("by %s.", player_ptr->died_from.data()).data(), GRAVE_LINE_WIDTH + 1);
186     show_tomb_line(lines[0], GRAVE_KILLER_NAME_ROW);
187     if (lines.size() == 1) {
188         return;
189     }
190
191     if (lines.size() >= 3) {
192         if (lines[1].length() > GRAVE_LINE_WIDTH - 3) {
193             lines[1].erase(GRAVE_LINE_WIDTH - 3);
194         }
195         lines[1].append("...");
196     }
197
198     show_tomb_line(lines[1], GRAVE_KILLER_NAME_ROW + 1);
199 }
200 #endif
201
202 /*!
203  * @brief 墓石のアスキーアート表示 /
204  * Display a "tomb-stone"
205  * @param player_ptr プレイヤーへの参照ポインタ
206  */
207 void print_tomb(PlayerType *player_ptr)
208 {
209     term_clear();
210     read_dead_file();
211     concptr p = w_ptr->total_winner ? _("偉大なる者", "Magnificent") : player_titles[enum2i(player_ptr->pclass)][(player_ptr->lev - 1) / 5].data();
212
213     show_tomb_line(player_ptr->name, GRAVE_PLAYER_NAME_ROW);
214
215 #ifdef JP
216 #else
217     show_tomb_line("the", GRAVE_PLAYER_TITLE_ROW - 1);
218 #endif
219
220     show_tomb_line(p, GRAVE_PLAYER_TITLE_ROW);
221
222     show_tomb_line(cp_ptr->title, GRAVE_PLAYER_CLASS_ROW);
223
224     show_basic_params(player_ptr);
225     show_tomb_detail(player_ptr);
226
227     time_t ct = time((time_t *)0);
228     show_tomb_line(format("%-.24s", ctime(&ct)), GRAVE_DEAD_DATETIME_ROW);
229     msg_format(_("さようなら、%s!", "Goodbye, %s!"), player_ptr->name);
230 }
231
232 /*!
233  * @brief 死亡/引退/切腹時にインベントリ内のアイテムを*鑑定*する
234  * @param player_ptr プレイヤーへの参照ポインタ
235  */
236 static void inventory_aware(PlayerType *player_ptr)
237 {
238     ItemEntity *o_ptr;
239     for (int i = 0; i < INVEN_TOTAL; i++) {
240         o_ptr = &player_ptr->inventory_list[i];
241         if (!o_ptr->is_valid()) {
242             continue;
243         }
244
245         object_aware(player_ptr, o_ptr);
246         object_known(o_ptr);
247     }
248 }
249
250 /*!
251  * @brief 死亡/引退/切腹時に我が家のアイテムを*鑑定*する
252  * @param player_ptr プレイヤーへの参照ポインタ
253  */
254 static void home_aware(PlayerType *player_ptr)
255 {
256     for (size_t i = 1; i < towns_info.size(); i++) {
257         auto *store_ptr = &towns_info[i].stores[StoreSaleType::HOME];
258         for (auto j = 0; j < store_ptr->stock_num; j++) {
259             auto *o_ptr = &store_ptr->stock[j];
260             if (!o_ptr->is_valid()) {
261                 continue;
262             }
263
264             object_aware(player_ptr, o_ptr);
265             object_known(o_ptr);
266         }
267     }
268 }
269
270 /*!
271  * @brief プレイヤーの持ち物を表示する
272  * @param player_ptr プレイヤーへの参照ポインタ
273  * @return Escキーでゲームを終了する時TRUE
274  */
275 static bool show_dead_player_items(PlayerType *player_ptr)
276 {
277     if (player_ptr->equip_cnt) {
278         term_clear();
279         (void)show_equipment(player_ptr, 0, USE_FULL, AllMatchItemTester());
280         prt(_("装備していたアイテム: -続く-", "You are using: -more-"), 0, 0);
281         if (inkey() == ESCAPE) {
282             return true;
283         }
284     }
285
286     if (player_ptr->inven_cnt) {
287         term_clear();
288         (void)show_inventory(player_ptr, 0, USE_FULL, AllMatchItemTester());
289         prt(_("持っていたアイテム: -続く-", "You are carrying: -more-"), 0, 0);
290
291         if (inkey() == ESCAPE) {
292             return true;
293         }
294     }
295
296     return false;
297 }
298
299 /*!
300  * @brief 我が家にあったアイテムを表示する
301  * @param player_ptr プレイヤーへの参照ポインタ
302  */
303 static void show_dead_home_items(PlayerType *player_ptr)
304 {
305     for (size_t l = 1; l < towns_info.size(); l++) {
306         const auto *store_ptr = &towns_info[l].stores[StoreSaleType::HOME];
307         if (store_ptr->stock_num == 0) {
308             continue;
309         }
310
311         for (int i = 0, k = 0; i < store_ptr->stock_num; k++) {
312             term_clear();
313             for (int j = 0; (j < 12) && (i < store_ptr->stock_num); j++, i++) {
314                 const auto *o_ptr = &store_ptr->stock[i];
315                 prt(format("%c) ", I2A(j)), j + 2, 4);
316                 const auto item_name = describe_flavor(player_ptr, o_ptr, 0);
317                 c_put_str(tval_to_attr[enum2i(o_ptr->bi_key.tval())], item_name, j + 2, 7);
318             }
319
320             prt(format(_("我が家に置いてあったアイテム ( %d ページ): -続く-", "Your home contains (page %d): -more-"), k + 1), 0, 0);
321             if (inkey() == ESCAPE) {
322                 return;
323             }
324         }
325     }
326 }
327
328 /*!
329  * @brief キャラクタ情報をファイルに書き出す
330  * @param player_ptr プレイヤーへの参照ポインタ
331  * @param file_character ステータスダンプへのコールバック
332  */
333 static void export_player_info(PlayerType *player_ptr)
334 {
335     prt(_("キャラクターの記録をファイルに書き出すことができます。", "You may now dump a character record to one or more files."), 21, 0);
336     prt(_("リターンキーでキャラクターを見ます。ESCで中断します。", "Then, hit RETURN to see the character, or ESC to abort."), 22, 0);
337     while (true) {
338         put_str(_("ファイルネーム: ", "Filename: "), 23, 0);
339         const auto ask_result = askfor(60);
340         if (!ask_result || ask_result->empty()) {
341             return;
342         }
343
344         screen_save();
345         file_character(player_ptr, *ask_result);
346         screen_load();
347     }
348 }
349
350 /*!
351  * @brief 自動的にプレイヤーステータスをファイルダンプ出力する
352  */
353 static void file_character_auto(PlayerType *player_ptr)
354 {
355     time_t now_t = time(nullptr);
356     struct tm *now_tm = localtime(&now_t);
357
358     char datetime[32];
359     strftime(datetime, sizeof(datetime), "%Y-%m-%d_%H%M%S", now_tm);
360     screen_save();
361     const auto filename = format("%s_Autodump_%s.txt", player_ptr->name, datetime);
362     file_character(player_ptr, filename);
363     screen_load();
364 }
365
366 /*!
367  * @brief 死亡、引退時の簡易ステータス表示
368  * @param player_ptr プレイヤーへの参照ポインタ
369  * @param display_player ステータス表示へのコールバック
370  */
371 void show_death_info(PlayerType *player_ptr)
372 {
373     inventory_aware(player_ptr);
374     home_aware(player_ptr);
375
376     RedrawingFlagsUpdater::get_instance().set_flag(StatusRecalculatingFlag::BONUS);
377     handle_stuff(player_ptr);
378     flush();
379     msg_erase();
380
381     if (auto_dump) {
382         file_character_auto(player_ptr);
383     }
384
385     export_player_info(player_ptr);
386     (void)display_player(player_ptr, 0);
387     prt(_("何かキーを押すとさらに情報が続きます (ESCで中断): ", "Hit any key to see more information (ESC to abort): "), 23, 0);
388     if (inkey() == ESCAPE) {
389         return;
390     }
391     if (show_dead_player_items(player_ptr)) {
392         return;
393     }
394
395     show_dead_home_items(player_ptr);
396 }