OSDN Git Service

Merge pull request #3532 from sikabane-works/release/3.0.0.87-alpha
[hengbandforosx/hengbandosx.git] / src / window / main-window-left-frame.cpp
1 #include "window/main-window-left-frame.h"
2 #include "game-option/special-options.h"
3 #include "game-option/text-display-options.h"
4 #include "market/arena-info-table.h"
5 #include "monster-race/monster-race.h"
6 #include "monster/monster-status.h"
7 #include "player-base/player-race.h"
8 #include "player-info/class-info.h"
9 #include "player-info/mimic-info-table.h"
10 #include "player/player-status-table.h"
11 #include "system/floor-type-definition.h"
12 #include "system/monster-entity.h"
13 #include "system/monster-race-info.h"
14 #include "system/player-type-definition.h"
15 #include "term/gameterm.h"
16 #include "term/screen-processor.h"
17 #include "term/term-color-types.h"
18 #include "term/z-form.h"
19 #include "timed-effect/player-hallucination.h"
20 #include "timed-effect/timed-effects.h"
21 #include "util/string-processor.h"
22 #include "window/main-window-row-column.h"
23 #include "window/main-window-stat-poster.h"
24 #include "window/main-window-util.h"
25 #include "world/world.h"
26
27 /*!
28  * @brief ターゲットしているモンスターの情報部に表示する状態異常と文字色の対応を保持する構造体
29  */
30 struct condition_layout_info {
31     std::string label;
32     TERM_COLOR color;
33 };
34
35 /*!
36  * @brief プレイヤーの称号を表示する / Prints "title", including "wizard" or "winner" as needed.
37  */
38 void print_title(PlayerType *player_ptr)
39 {
40     GAME_TEXT str[14];
41     concptr p = "";
42     if (w_ptr->wizard) {
43         p = _("[ウィザード]", "[=-WIZARD-=]");
44     } else if (w_ptr->total_winner) {
45         if (player_ptr->is_true_winner()) {
46             p = _("*真・勝利者*", "*TRUEWINNER*");
47         } else {
48             p = _("***勝利者***", "***WINNER***");
49         }
50     } else {
51         angband_strcpy(str, player_titles[enum2i(player_ptr->pclass)][(player_ptr->lev - 1) / 5], sizeof(str));
52         p = str;
53     }
54
55     print_field(p, ROW_TITLE, COL_TITLE);
56 }
57
58 /*!
59  * @brief プレイヤーのレベルを表示する / Prints level
60  */
61 void print_level(PlayerType *player_ptr)
62 {
63     std::string tmp = format("%5d", player_ptr->lev);
64     if (player_ptr->lev >= player_ptr->max_plv) {
65         put_str(_("レベル ", "LEVEL "), ROW_LEVEL, 0);
66         c_put_str(TERM_L_GREEN, tmp, ROW_LEVEL, COL_LEVEL + 7);
67     } else {
68         put_str(_("xレベル", "Level "), ROW_LEVEL, 0);
69         c_put_str(TERM_YELLOW, tmp, ROW_LEVEL, COL_LEVEL + 7);
70     }
71 }
72
73 /*!
74  * @brief プレイヤーの経験値を表示する / Display the experience
75  */
76 void print_exp(PlayerType *player_ptr)
77 {
78     std::string out_val;
79
80     PlayerRace pr(player_ptr);
81     if ((!exp_need) || pr.equals(PlayerRaceType::ANDROID)) {
82         out_val = format("%8ld", (long)player_ptr->exp);
83     } else {
84         if (player_ptr->lev >= PY_MAX_LEVEL) {
85             out_val = "********";
86         } else {
87             out_val = format("%8ld", (long)(player_exp[player_ptr->lev - 1] * player_ptr->expfact / 100L) - player_ptr->exp);
88         }
89     }
90
91     if (player_ptr->exp >= player_ptr->max_exp) {
92         if (pr.equals(PlayerRaceType::ANDROID)) {
93             put_str(_("強化 ", "Cst "), ROW_EXP, 0);
94         } else {
95             put_str(_("経験 ", "EXP "), ROW_EXP, 0);
96         }
97         c_put_str(TERM_L_GREEN, out_val, ROW_EXP, COL_EXP + 4);
98     } else {
99         put_str(_("x経験", "Exp "), ROW_EXP, 0);
100         c_put_str(TERM_YELLOW, out_val, ROW_EXP, COL_EXP + 4);
101     }
102 }
103
104 /*!
105  * @brief プレイヤーのACを表示する / Prints current AC
106  */
107 void print_ac(PlayerType *player_ptr)
108 {
109     /* AC の表示方式を変更している */
110     put_str(_(" AC(     )", "Cur AC "), ROW_AC, COL_AC);
111     c_put_str(TERM_L_GREEN, format("%5d", player_ptr->dis_ac + player_ptr->dis_to_a), ROW_AC, COL_AC + _(6, 7));
112 }
113
114 /*!
115  * @brief プレイヤーのHPを表示する / Prints Cur/Max hit points
116  */
117 void print_hp(PlayerType *player_ptr)
118 {
119     put_str("HP", ROW_CURHP, COL_CURHP);
120     TERM_COLOR color;
121     if (player_ptr->chp >= player_ptr->mhp) {
122         color = TERM_L_GREEN;
123     } else if (player_ptr->chp > (player_ptr->mhp * hitpoint_warn) / 10) {
124         color = TERM_YELLOW;
125     } else {
126         color = TERM_RED;
127     }
128
129     c_put_str(color, format("%4ld", (long int)player_ptr->chp), ROW_CURHP, COL_CURHP + 3);
130     put_str("/", ROW_CURHP, COL_CURHP + 7);
131     color = TERM_L_GREEN;
132     c_put_str(color, format("%4ld", (long int)player_ptr->mhp), ROW_CURHP, COL_CURHP + 8);
133 }
134
135 /*!
136  * @brief プレイヤーのMPを表示する / Prints players max/cur spell points
137  */
138 void print_sp(PlayerType *player_ptr)
139 {
140     if ((mp_ptr->spell_book == ItemKindType::NONE) && mp_ptr->spell_first == SPELL_FIRST_NO_SPELL) {
141         return;
142     }
143
144     put_str(_("MP", "SP"), ROW_CURSP, COL_CURSP);
145     byte color;
146     if (player_ptr->csp >= player_ptr->msp) {
147         color = TERM_L_GREEN;
148     } else if (player_ptr->csp > (player_ptr->msp * mana_warn) / 10) {
149         color = TERM_YELLOW;
150     } else {
151         color = TERM_RED;
152     }
153
154     c_put_str(color, format("%4ld", (long int)player_ptr->csp), ROW_CURSP, COL_CURSP + 3);
155     put_str("/", ROW_CURSP, COL_CURSP + 7);
156     color = TERM_L_GREEN;
157     c_put_str(color, format("%4ld", (long int)player_ptr->msp), ROW_CURSP, COL_CURSP + 8);
158 }
159
160 /*!
161  * @brief プレイヤーの所持金を表示する / Prints current gold
162  * @param player_ptr プレイヤーへの参照ポインタ
163  */
164 void print_gold(PlayerType *player_ptr)
165 {
166     put_str(_("$ ", "AU "), ROW_GOLD, COL_GOLD);
167     c_put_str(TERM_L_GREEN, format("%9ld", (long)player_ptr->au), ROW_GOLD, COL_GOLD + 3);
168 }
169
170 /*!
171  * @brief 現在のフロアの深さを表示する / Prints depth in stat area
172  * @param player_ptr プレイヤーへの参照ポインタ
173  */
174 void print_depth(PlayerType *player_ptr)
175 {
176     TERM_COLOR attr = TERM_WHITE;
177
178     TERM_LEN wid, hgt;
179     term_get_size(&wid, &hgt);
180     TERM_LEN col_depth = wid + COL_DEPTH;
181     TERM_LEN row_depth = hgt + ROW_DEPTH;
182
183     auto *floor_ptr = player_ptr->current_floor_ptr;
184     if (!floor_ptr->dun_level) {
185         c_prt(attr, format("%7s", _("地上", "Surf.")), row_depth, col_depth);
186         return;
187     }
188
189     if (inside_quest(floor_ptr->quest_number) && !floor_ptr->dungeon_idx) {
190         c_prt(attr, format("%7s", _("地上", "Quest")), row_depth, col_depth);
191         return;
192     }
193
194     std::string depths;
195     if (depth_in_feet) {
196         depths = format(_("%d ft", "%d ft"), (int)floor_ptr->dun_level * 50);
197     } else {
198         depths = format(_("%d 階", "Lev %d"), (int)floor_ptr->dun_level);
199     }
200
201     switch (player_ptr->feeling) {
202     case 0:
203         attr = TERM_SLATE;
204         break; /* Unknown */
205     case 1:
206         attr = TERM_L_BLUE;
207         break; /* Special */
208     case 2:
209         attr = TERM_VIOLET;
210         break; /* Horrible visions */
211     case 3:
212         attr = TERM_RED;
213         break; /* Very dangerous */
214     case 4:
215         attr = TERM_L_RED;
216         break; /* Very bad feeling */
217     case 5:
218         attr = TERM_ORANGE;
219         break; /* Bad feeling */
220     case 6:
221         attr = TERM_YELLOW;
222         break; /* Nervous */
223     case 7:
224         attr = TERM_L_UMBER;
225         break; /* Luck is turning */
226     case 8:
227         attr = TERM_L_WHITE;
228         break; /* Don't like */
229     case 9:
230         attr = TERM_WHITE;
231         break; /* Reasonably safe */
232     case 10:
233         attr = TERM_WHITE;
234         break; /* Boring place */
235     }
236
237     c_prt(attr, format("%7s", depths.data()), row_depth, col_depth);
238 }
239
240 /*!
241  * @brief プレイヤーのステータスを一括表示する(左側部分) / Display basic info (mostly left of map)
242  * @param player_ptr プレイヤーへの参照ポインタ
243  */
244 void print_frame_basic(PlayerType *player_ptr)
245 {
246     if (player_ptr->mimic_form != MimicKindType::NONE) {
247         print_field(mimic_info.at(player_ptr->mimic_form).title, ROW_RACE, COL_RACE);
248     } else {
249         char str[14];
250         angband_strcpy(str, rp_ptr->title, sizeof(str));
251         print_field(str, ROW_RACE, COL_RACE);
252     }
253
254     print_title(player_ptr);
255     print_level(player_ptr);
256     print_exp(player_ptr);
257     for (int i = 0; i < A_MAX; i++) {
258         print_stat(player_ptr, i);
259     }
260
261     print_ac(player_ptr);
262     print_hp(player_ptr);
263     print_sp(player_ptr);
264     print_gold(player_ptr);
265     print_depth(player_ptr);
266     print_health(player_ptr, true);
267     print_health(player_ptr, false);
268 }
269
270 /*!
271  * @brief wizardモード中の闘技場情報を表示する
272  * @param player_ptr プレイヤーへの参照ポインタ
273  */
274 static void print_health_monster_in_arena_for_wizard(PlayerType *player_ptr)
275 {
276     int row = ROW_INFO - 1;
277     int col = COL_INFO + 2;
278
279     const int max_num_of_monster_in_arena = 4;
280
281     for (int i = 0; i < max_num_of_monster_in_arena; i++) {
282         auto row_offset = i;
283         auto monster_list_index = i + 1; // m_listの1-4に闘技場のモンスターデータが入っている
284
285         term_putstr(col - 2, row + row_offset, 12, TERM_WHITE, "      /     ");
286
287         auto &monster = player_ptr->current_floor_ptr->m_list[monster_list_index];
288         if (MonsterRace(monster.r_idx).is_valid()) {
289             term_putstr(col - 2, row + row_offset, 2, monraces_info[monster.r_idx].x_attr,
290                 format("%c", monraces_info[monster.r_idx].x_char));
291             term_putstr(col - 1, row + row_offset, 5, TERM_WHITE, format("%5d", monster.hp));
292             term_putstr(col + 5, row + row_offset, 6, TERM_WHITE, format("%5d", monster.max_maxhp));
293         }
294     }
295 }
296
297 /*!
298  * @brief 対象のモンスターからcondition_layout_infoのリストを生成して返す
299  * @param monster 対象のモンスター
300  * @return condition_layout_infoのリスト
301  */
302 static std::vector<condition_layout_info> get_condition_layout_info(const MonsterEntity &monster)
303 {
304     std::vector<condition_layout_info> result;
305
306     if (monster.is_invulnerable()) {
307         result.push_back({ effect_type_to_label.at(MTIMED_INVULNER), TERM_WHITE });
308     }
309     if (monster.is_accelerated()) {
310         result.push_back({ effect_type_to_label.at(MTIMED_FAST), TERM_L_GREEN });
311     }
312     if (monster.is_decelerated()) {
313         result.push_back({ effect_type_to_label.at(MTIMED_SLOW), TERM_UMBER });
314     }
315     if (monster.is_fearful()) {
316         result.push_back({ effect_type_to_label.at(MTIMED_MONFEAR), TERM_SLATE });
317     }
318     if (monster.is_confused()) {
319         result.push_back({ effect_type_to_label.at(MTIMED_CONFUSED), TERM_L_UMBER });
320     }
321     if (monster.is_asleep()) {
322         result.push_back({ effect_type_to_label.at(MTIMED_CSLEEP), TERM_BLUE });
323     }
324     if (monster.is_stunned()) {
325         result.push_back({ effect_type_to_label.at(MTIMED_STUNNED), TERM_ORANGE });
326     }
327
328     return result;
329 }
330
331 /*!
332  * @brief モンスターの体力ゲージを表示する
333  * @param riding TRUEならば騎乗中のモンスターの体力、FALSEならターゲットモンスターの体力を表示する。表示位置は固定。
334  * @details
335  * <pre>
336  * Redraw the "monster health bar"      -DRS-
337  * Rather extensive modifications by    -BEN-
338  *
339  * The "monster health bar" provides visual feedback on the "health"
340  * of the monster currently being "tracked".  There are several ways
341  * to "track" a monster, including targetting it, attacking it, and
342  * affecting it (and nobody else) with a ranged attack.
343  *
344  * Display the monster health bar (affectionately known as the
345  * "health-o-meter").  Clear health bar if nothing is being tracked.
346  * Auto-track current target monster when bored.  Note that the
347  * health-bar stops tracking any monster that "disappears".
348  * </pre>
349  */
350 void print_health(PlayerType *player_ptr, bool riding)
351 {
352     std::optional<short> monster_idx;
353     int row, col;
354
355     if (riding) {
356         if (player_ptr->riding > 0) {
357             monster_idx = player_ptr->riding;
358         }
359         row = ROW_RIDING_INFO;
360         col = COL_RIDING_INFO;
361     } else {
362         // ウィザードモードで闘技場観戦時の表示
363         if (w_ptr->wizard && player_ptr->phase_out) {
364             print_health_monster_in_arena_for_wizard(player_ptr);
365             return;
366         }
367         if (player_ptr->health_who > 0) {
368             monster_idx = player_ptr->health_who;
369         }
370         row = ROW_INFO;
371         col = COL_INFO;
372     }
373
374     const int max_width = 12; // 表示幅
375
376     TERM_LEN width, height;
377     term_get_size(&width, &height);
378     const auto extra_line_count = riding ? 0 : height - MAIN_TERM_MIN_ROWS;
379     for (auto y = row; y < row + extra_line_count + 1; ++y) {
380         term_erase(col, y, max_width);
381     }
382
383     if (!monster_idx.has_value()) {
384         return;
385     }
386
387     const auto &monster = player_ptr->current_floor_ptr->m_list[monster_idx.value()];
388
389     if ((!monster.ml) || (player_ptr->effects()->hallucination()->is_hallucinated()) || monster.is_dead()) {
390         term_putstr(col, row, max_width, TERM_WHITE, "[----------]");
391         return;
392     }
393
394     const auto [hit_point_bar_color, len] = monster.get_hp_bar_data();
395
396     term_putstr(col, row, max_width, TERM_WHITE, "[----------]");
397     term_putstr(col + 1, row, len, hit_point_bar_color, "**********");
398
399     // 騎乗中のモンスターの状態異常は表示しない
400     if (riding) {
401         return;
402     }
403
404     int col_offset = 0;
405     int row_offset = 1;
406
407     // 一時的状態異常
408     // MAX_WIDTHを超えたら次の行に移動する
409     for (const auto &info : get_condition_layout_info(monster)) {
410         if (row_offset > extra_line_count) {
411             break;
412         }
413         if (col_offset + info.label.length() > max_width) { // 改行が必要かどうかチェック
414             col_offset = 0;
415             row_offset++;
416         }
417         term_putstr(col + col_offset, row + row_offset, max_width, info.color, info.label);
418         col_offset += info.label.length() + 1; // 文字数と空白の分だけoffsetを加算
419     }
420 }