OSDN Git Service

a0bf6035610682b7a30db1400515f1c692cfb886
[hengbandforosx/hengbandosx.git] / src / view / display-player.cpp
1 /*!
2  * @brief プレイヤーのステータス表示メインルーチン群
3  * @date 2020/02/25
4  * @author Hourier
5  * @details
6  * ここにこれ以上関数を引っ越してくるのは禁止
7  */
8
9 #include "view/display-player.h"
10 #include "dungeon/quest.h"
11 #include "floor/floor-util.h"
12 #include "game-option/text-display-options.h"
13 #include "info-reader/fixed-map-parser.h"
14 #include "inventory/inventory-slot-types.h"
15 #include "knowledge/knowledge-mutations.h"
16 #include "mind/mind-elementalist.h"
17 #include "mutation/mutation-flag-types.h"
18 #include "object/object-info.h"
19 #include "player-base/player-class.h"
20 #include "player-info/alignment.h"
21 #include "player-info/class-info.h"
22 #include "player-info/mimic-info-table.h"
23 #include "player/patron.h"
24 #include "player/player-personality.h"
25 #include "player/player-sex.h"
26 #include "player/player-status-flags.h"
27 #include "player/player-status-table.h"
28 #include "player/player-status.h"
29 #include "realm/realm-names-table.h"
30 #include "system/baseitem-info.h"
31 #include "system/floor-type-definition.h"
32 #include "system/item-entity.h"
33 #include "system/player-type-definition.h"
34 #include "term/gameterm.h"
35 #include "term/screen-processor.h"
36 #include "term/term-color-types.h"
37 #include "view/display-characteristic.h"
38 #include "view/display-player-middle.h"
39 #include "view/display-player-misc-info.h"
40 #include "view/display-player-stat-info.h"
41 #include "view/display-util.h"
42 #include "view/status-first-page.h"
43 #include "world/world.h"
44 #include <string>
45
46 /*!
47  * @brief
48  * @param player_ptr プレイヤーへの参照ポインタ
49  * @param mode ステータス表示モード
50  * @return どれかの処理をこなしたらTRUE、何もしなかったらFALSE
51  */
52 static bool display_player_info(PlayerType *player_ptr, int mode)
53 {
54     if (mode == 2) {
55         display_player_misc_info(player_ptr);
56         display_player_stat_info(player_ptr);
57         display_player_flag_info_1(player_ptr, display_player_equippy);
58         return true;
59     }
60
61     if (mode == 3) {
62         display_player_flag_info_2(player_ptr, display_player_equippy);
63         return true;
64     }
65
66     if (mode == 4) {
67         display_player_flag_info_3(player_ptr, display_player_equippy);
68         return true;
69     }
70
71     if (mode == 5) {
72         TermCenteredOffsetSetter tcos(MAIN_TERM_MIN_COLS, std::nullopt);
73         do_cmd_knowledge_mutations(player_ptr);
74         return true;
75     }
76
77     return false;
78 }
79
80 /*!
81  * @brief 名前、性別、種族、職業を表示する
82  * @param player_ptr プレイヤーへの参照ポインタ
83  */
84 static void display_player_basic_info(PlayerType *player_ptr)
85 {
86     std::string tmp = ap_ptr->title;
87     tmp.append(_(ap_ptr->no == 1 ? "の" : "", " ")).append(player_ptr->name);
88
89     display_player_one_line(ENTRY_NAME, tmp, TERM_L_BLUE);
90     display_player_one_line(ENTRY_SEX, sp_ptr->title, TERM_L_BLUE);
91     display_player_one_line(ENTRY_RACE, (player_ptr->mimic_form != MimicKindType::NONE ? mimic_info.at(player_ptr->mimic_form).title : rp_ptr->title), TERM_L_BLUE);
92     display_player_one_line(ENTRY_CLASS, cp_ptr->title, TERM_L_BLUE);
93 }
94
95 /*!
96  * @brief 魔法領域を表示する
97  * @param player_ptr プレイヤーへの参照ポインタ
98  */
99 static void display_magic_realms(PlayerType *player_ptr)
100 {
101     if (player_ptr->realm1 == REALM_NONE && player_ptr->element == REALM_NONE) {
102         return;
103     }
104
105     std::string tmp;
106     if (PlayerClass(player_ptr).equals(PlayerClassType::ELEMENTALIST)) {
107         tmp = get_element_title(player_ptr->element);
108     } else if (player_ptr->realm2) {
109         tmp = realm_names[player_ptr->realm1];
110         tmp.append(", ").append(realm_names[player_ptr->realm2]);
111     } else {
112         tmp = realm_names[player_ptr->realm1];
113     }
114
115     display_player_one_line(ENTRY_REALM, tmp, TERM_L_BLUE);
116 }
117
118 /*!
119  * @ brief 年齢、身長、体重、社会的地位を表示する
120  * @param player_ptr プレイヤーへの参照ポインタ
121  * @details
122  * 日本語版では、身長はcmに、体重はkgに変更してある
123  */
124 static void display_phisique(PlayerType *player_ptr)
125 {
126 #ifdef JP
127     display_player_one_line(ENTRY_AGE, format("%d才", (int)player_ptr->age), TERM_L_BLUE);
128     display_player_one_line(ENTRY_HEIGHT, format("%dcm", (int)((player_ptr->ht * 254) / 100)), TERM_L_BLUE);
129     display_player_one_line(ENTRY_WEIGHT, format("%dkg", (int)((player_ptr->wt * 4536) / 10000)), TERM_L_BLUE);
130     display_player_one_line(ENTRY_SOCIAL, format("%d  ", (int)player_ptr->sc), TERM_L_BLUE);
131 #else
132     display_player_one_line(ENTRY_AGE, format("%d", (int)player_ptr->age), TERM_L_BLUE);
133     display_player_one_line(ENTRY_HEIGHT, format("%d", (int)player_ptr->ht), TERM_L_BLUE);
134     display_player_one_line(ENTRY_WEIGHT, format("%d", (int)player_ptr->wt), TERM_L_BLUE);
135     display_player_one_line(ENTRY_SOCIAL, format("%d", (int)player_ptr->sc), TERM_L_BLUE);
136 #endif
137     std::string alg = PlayerAlignment(player_ptr).get_alignment_description();
138     display_player_one_line(ENTRY_ALIGN, format("%s", alg.data()), TERM_L_BLUE);
139 }
140
141 /*!
142  * @brief 能力値を (減少していたら色を変えて)表示する
143  * @param player_ptr プレイヤーへの参照ポインタ
144  */
145 static void display_player_stats(PlayerType *player_ptr)
146 {
147     for (int i = 0; i < A_MAX; i++) {
148         if (player_ptr->stat_cur[i] < player_ptr->stat_max[i]) {
149             put_str(stat_names_reduced[i], 3 + i, 53);
150             int value = player_ptr->stat_use[i];
151             c_put_str(TERM_YELLOW, cnv_stat(value), 3 + i, 60);
152             value = player_ptr->stat_top[i];
153             c_put_str(TERM_L_GREEN, cnv_stat(value), 3 + i, 67);
154         } else {
155             put_str(stat_names[i], 3 + i, 53);
156             c_put_str(TERM_L_GREEN, cnv_stat(player_ptr->stat_use[i]), 3 + i, 60);
157         }
158
159         if (player_ptr->stat_max[i] == player_ptr->stat_max_max[i]) {
160             c_put_str(TERM_WHITE, "!", 3 + i, _(58, 58 - 2));
161         }
162     }
163 }
164
165 /*!
166  * @brief ゲームオーバーの原因を探る (生きていたら何もしない)
167  * @param player_ptr プレイヤーへの参照ポインタ
168  * @param statmsg メッセージバッファ
169  * @return 生きていたらFALSE、死んでいたらTRUE
170  */
171 static std::optional<std::string> search_death_cause(PlayerType *player_ptr)
172 {
173     auto *floor_ptr = player_ptr->current_floor_ptr;
174     if (!player_ptr->is_dead) {
175         return std::nullopt;
176     }
177
178     if (w_ptr->total_winner) {
179         return std::string(format(_("…あなたは勝利の後%sした。", "...You %s after winning."),
180             streq(player_ptr->died_from, "Seppuku") ? _("切腹", "committed seppuku") : _("引退", "retired from the adventure")));
181     }
182
183     if (!floor_ptr->dun_level) {
184 #ifdef JP
185         return std::string(format("…あなたは%sで%sに殺された。", map_name(player_ptr), player_ptr->died_from.data()));
186 #else
187         return std::string(format("...You were killed by %s in %s.", player_ptr->died_from.data(), map_name(player_ptr)));
188 #endif
189     }
190
191     if (inside_quest(floor_ptr->quest_number) && quest_type::is_fixed(floor_ptr->quest_number)) {
192         const auto &quest_list = QuestList::get_instance();
193
194         /* Get the quest text */
195         /* Bewere that INIT_ASSIGN resets the cur_num. */
196         init_flags = INIT_NAME_ONLY;
197         parse_fixed_map(player_ptr, QUEST_DEFINITION_LIST, 0, 0, 0, 0);
198
199         const auto *q_ptr = &quest_list[floor_ptr->quest_number];
200 #ifdef JP
201         return std::string(format("…あなたは、クエスト「%s」で%sに殺された。", q_ptr->name, player_ptr->died_from.data()));
202 #else
203         return std::string(format("...You were killed by %s in the quest '%s'.", player_ptr->died_from.data(), q_ptr->name));
204 #endif
205     }
206
207 #ifdef JP
208     return std::string(format("…あなたは、%sの%d階で%sに殺された。", map_name(player_ptr), (int)floor_ptr->dun_level, player_ptr->died_from.data()));
209 #else
210     return std::string(format("...You were killed by %s on level %d of %s.", player_ptr->died_from.data(), floor_ptr->dun_level, map_name(player_ptr)));
211 #endif
212 }
213
214 /*!
215  * @brief クエストフロアで生きている場合、クエスト名をバッファに詰める
216  * @param player_ptr プレイヤーへの参照ポインタ
217  * @param statmsg メッセージバッファ
218  * @return クエスト内であればTRUE、いなければFALSE
219  */
220 static std::optional<std::string> decide_death_in_quest(PlayerType *player_ptr)
221 {
222     auto *floor_ptr = player_ptr->current_floor_ptr;
223     if (!inside_quest(floor_ptr->quest_number) || !quest_type::is_fixed(floor_ptr->quest_number)) {
224         return std::nullopt;
225     }
226
227     for (int i = 0; i < 10; i++) {
228         quest_text[i][0] = '\0';
229     }
230
231     const auto &quest_list = QuestList::get_instance();
232     quest_text_line = 0;
233     init_flags = INIT_NAME_ONLY;
234     parse_fixed_map(player_ptr, QUEST_DEFINITION_LIST, 0, 0, 0, 0);
235     return std::string(format(_("…あなたは現在、 クエスト「%s」を遂行中だ。", "...Now, you are in the quest '%s'."), quest_list[floor_ptr->quest_number].name));
236 }
237
238 /*!
239  * @brief 現在いるフロアを、または死んでいたらどこでどう死んだかをバッファに詰める
240  * @param player_ptr プレイヤーへの参照ポインタ
241  * @param statmsg メッセージバッファ
242  */
243 static std::string decide_current_floor(PlayerType *player_ptr)
244 {
245     if (auto death_cause = search_death_cause(player_ptr);
246         death_cause.has_value() || !w_ptr->character_dungeon) {
247         return death_cause.value_or("");
248     }
249
250     auto *floor_ptr = player_ptr->current_floor_ptr;
251     if (floor_ptr->dun_level == 0) {
252         return std::string(format(_("…あなたは現在、 %s にいる。", "...Now, you are in %s."), map_name(player_ptr)));
253     }
254
255     if (auto decision = decide_death_in_quest(player_ptr); decision.has_value()) {
256         return decision.value();
257     }
258
259 #ifdef JP
260     return std::string(format("…あなたは現在、 %s の %d 階で探索している。", map_name(player_ptr), (int)floor_ptr->dun_level));
261 #else
262     return std::string(format("...Now, you are exploring level %d of %s.", (int)floor_ptr->dun_level, map_name(player_ptr)));
263 #endif
264 }
265
266 /*!
267  * @brief プレイヤーのステータス表示メイン処理
268  * Display the character on the screen (various modes)
269  * @param player_ptr プレイヤーへの参照ポインタ
270  * @param tmp_mode 暫定表示モード (突然変異の有無で実際のモードに切り替える)
271  * @return 死亡原因となったモンスター名が複数行に亘る場合、表示に必要な行数. それ以外を表示する場合はnullopt
272  * @details
273  * 最初の1行と最後の2行は空行. / The top one and bottom two lines are left blank.
274  * Mode 0 = standard display with skills.
275  * Mode 1 = standard display with history.
276  * Mode 2 = summary of various things.
277  * Mode 3 = summary of various things (part 2).
278  * Mode 4 = mutations.
279  * Mode 5 = ??? (コード上の定義より6で割った余りは5になりうるが元のコメントに記載なし).
280  */
281 std::optional<int> display_player(PlayerType *player_ptr, const int tmp_mode)
282 {
283     auto has_any_mutation = (player_ptr->muta.any() || has_good_luck(player_ptr)) && display_mutations;
284     auto mode = has_any_mutation ? tmp_mode % 6 : tmp_mode % 5;
285     {
286         TermOffsetSetter tos(0, 0);
287         clear_from(0);
288     }
289     if (display_player_info(player_ptr, mode)) {
290         return std::nullopt;
291     }
292
293     display_player_basic_info(player_ptr);
294     display_magic_realms(player_ptr);
295     if (PlayerClass(player_ptr).equals(PlayerClassType::CHAOS_WARRIOR) || (player_ptr->muta.has(PlayerMutationType::CHAOS_GIFT))) {
296         display_player_one_line(ENTRY_PATRON, patron_list[player_ptr->chaos_patron].name, TERM_L_BLUE);
297     }
298
299     display_phisique(player_ptr);
300     display_player_stats(player_ptr);
301     if (mode == 0) {
302         display_player_middle(player_ptr);
303         display_player_various(player_ptr);
304         return std::nullopt;
305     }
306
307     put_str(_("(キャラクターの生い立ち)", "(Character Background)"), 11, 25);
308     for (auto i = 0; i < 4; i++) {
309         put_str(player_ptr->history[i], i + 12, 10);
310     }
311
312     auto statmsg = decide_current_floor(player_ptr);
313     if (statmsg.empty()) {
314         return std::nullopt;
315     }
316
317     constexpr auto chars_per_line = 60;
318     return display_wrap_around(statmsg, chars_per_line, 17, 10);
319 }
320
321 /*!
322  * @brief プレイヤーの装備一覧をシンボルで並べる
323  * Equippy chars
324  * @param player_ptr プレイヤーへの参照ポインタ
325  * @param y 表示するコンソールの行
326  * @param x 表示するコンソールの列
327  * @param mode オプション
328  * @todo y = 6、x = 0、mode = 0で固定。何とかする
329  */
330 void display_player_equippy(PlayerType *player_ptr, TERM_LEN y, TERM_LEN x, BIT_FLAGS16 mode)
331 {
332     const auto max_i = (mode & DP_WP) ? INVEN_BOW + 1 : INVEN_TOTAL;
333     for (int i = INVEN_MAIN_HAND; i < max_i; i++) {
334         const auto &o_ref = player_ptr->inventory_list[i];
335         auto a = o_ref.get_color();
336         auto c = o_ref.get_symbol();
337         if (!equippy_chars || (o_ref.bi_id == 0)) {
338             c = ' ';
339             a = TERM_DARK;
340         }
341
342         term_putch(x + i - INVEN_MAIN_HAND, y, a, c);
343     }
344 }