OSDN Git Service

[Refactor] #2649 Renamed monster-type-definition.cpp/h to monster-entity.cpp/h
[hengbandforosx/hengbandosx.git] / src / knowledge / knowledge-monsters.cpp
1 /*!
2  * @file knowledge-monsters.cpp
3  * @brief 既知のモンスターに関する情報を表示する
4  * @date 2020/04/24
5  * @author Hourier
6  * @todo サブルーチン分割を行うと行数が膨れ上がりそう、再分割も検討すべし
7  */
8
9 #include "knowledge/knowledge-monsters.h"
10 #include "core/show-file.h"
11 #include "core/stuff-handler.h"
12 #include "game-option/cheat-options.h"
13 #include "game-option/special-options.h"
14 #include "io-dump/dump-util.h"
15 #include "io/input-key-acceptor.h"
16 #include "knowledge/monster-group-table.h"
17 #include "locale/english.h"
18 #include "lore/lore-util.h"
19 #include "monster-race/monster-race.h"
20 #include "monster-race/race-flags1.h"
21 #include "monster-race/race-flags3.h"
22 #include "monster-race/race-flags7.h"
23 #include "monster/monster-describer.h"
24 #include "monster/monster-description-types.h"
25 #include "monster/monster-info.h"
26 #include "monster/monster-status.h"
27 #include "monster/smart-learn-types.h"
28 #include "pet/pet-util.h"
29 #include "system/floor-type-definition.h"
30 #include "system/monster-entity.h"
31 #include "system/monster-race-info.h"
32 #include "system/player-type-definition.h"
33 #include "term/screen-processor.h"
34 #include "term/term-color-types.h"
35 #include "util/angband-files.h"
36 #include "util/bit-flags-calculator.h"
37 #include "util/int-char-converter.h"
38 #include "util/sort.h"
39 #include "util/string-processor.h"
40 #include "view/display-lore.h"
41 #include "view/display-monster-status.h"
42 #include "world/world.h"
43
44 /*!
45  * @brief 特定の与えられた条件に応じてモンスターのIDリストを作成する / Build a list of monster indexes in the given group.
46  * @param player_ptr プレイヤーへの参照ポインタ
47  * @param grp_cur グループ種別。リスト表記中の左一覧(各シンボル及び/ユニーク(-1)/騎乗可能モンスター(-2)/賞金首(-3)/アンバーの王族(-4))を参照できる
48  * @param mode 思い出の扱いに関するモード
49  * @return 作成したモンスターのIDリスト
50  */
51 static std::vector<MonsterRaceId> collect_monsters(PlayerType *player_ptr, IDX grp_cur, monster_lore_mode mode)
52 {
53     concptr group_char = monster_group_char[grp_cur];
54     bool grp_unique = (monster_group_char[grp_cur] == (char *)-1L);
55     bool grp_riding = (monster_group_char[grp_cur] == (char *)-2L);
56     bool grp_wanted = (monster_group_char[grp_cur] == (char *)-3L);
57     bool grp_amberite = (monster_group_char[grp_cur] == (char *)-4L);
58
59     std::vector<MonsterRaceId> r_idx_list;
60     for (const auto &[r_idx, r_ref] : monraces_info) {
61         if (r_ref.name.empty()) {
62             continue;
63         }
64         if (((mode != MONSTER_LORE_DEBUG) && (mode != MONSTER_LORE_RESEARCH)) && !cheat_know && !r_ref.r_sights) {
65             continue;
66         }
67
68         if (grp_unique) {
69             if (r_ref.kind_flags.has_not(MonsterKindType::UNIQUE)) {
70                 continue;
71             }
72         } else if (grp_riding) {
73             if (none_bits(r_ref.flags7, RF7_RIDING)) {
74                 continue;
75             }
76         } else if (grp_wanted) {
77             auto wanted = player_ptr->knows_daily_bounty && (w_ptr->today_mon == r_ref.idx);
78             wanted |= MonsterRace(r_ref.idx).is_bounty(false);
79
80             if (!wanted) {
81                 continue;
82             }
83         } else if (grp_amberite) {
84             if (r_ref.kind_flags.has_not(MonsterKindType::AMBERITE)) {
85                 continue;
86             }
87         } else {
88             if (!angband_strchr(group_char, r_ref.d_char)) {
89                 continue;
90             }
91         }
92
93         r_idx_list.push_back(r_ref.idx);
94         if (mode == MONSTER_LORE_NORMAL) {
95             break;
96         }
97         if (mode == MONSTER_LORE_DEBUG) {
98             break;
99         }
100     }
101
102     int dummy_why;
103     ang_sort(player_ptr, r_idx_list.data(), &dummy_why, r_idx_list.size(), ang_sort_comp_monster_level, ang_sort_swap_hook);
104     return r_idx_list;
105 }
106
107 /*!
108  * @brief 現在のペットを表示するコマンドのメインルーチン /
109  * Display current pets
110  * @param player_ptr プレイヤーへの参照ポインタ
111  */
112 void do_cmd_knowledge_pets(PlayerType *player_ptr)
113 {
114     FILE *fff = nullptr;
115     GAME_TEXT file_name[FILE_NAME_SIZE];
116     if (!open_temporary_file(&fff, file_name)) {
117         return;
118     }
119
120     MonsterEntity *m_ptr;
121     GAME_TEXT pet_name[MAX_NLEN];
122     int t_friends = 0;
123     for (int i = player_ptr->current_floor_ptr->m_max - 1; i >= 1; i--) {
124         m_ptr = &player_ptr->current_floor_ptr->m_list[i];
125         if (!m_ptr->is_valid() || !m_ptr->is_pet()) {
126             continue;
127         }
128
129         t_friends++;
130         monster_desc(player_ptr, pet_name, m_ptr, MD_ASSUME_VISIBLE | MD_INDEF_VISIBLE);
131         fprintf(fff, "%s (%s)\n", pet_name, look_mon_desc(m_ptr, 0x00));
132     }
133
134     int show_upkeep = calculate_upkeep(player_ptr);
135
136     fprintf(fff, "----------------------------------------------\n");
137 #ifdef JP
138     fprintf(fff, "    合計: %d 体のペット\n", t_friends);
139 #else
140     fprintf(fff, "   Total: %d pet%s.\n", t_friends, (t_friends == 1 ? "" : "s"));
141 #endif
142     fprintf(fff, _(" 維持コスト: %d%% MP\n", "   Upkeep: %d%% mana.\n"), show_upkeep);
143
144     angband_fclose(fff);
145     (void)show_file(player_ptr, true, file_name, _("現在のペット", "Current Pets"), 0, 0);
146     fd_kill(file_name);
147 }
148
149 /*!
150  * @brief 現在までに倒したモンスターを表示するコマンドのメインルーチン /
151  * @param player_ptr プレイヤーへの参照ポインタ
152  * Total kill count
153  * @note the player ghosts are ignored.
154  */
155 void do_cmd_knowledge_kill_count(PlayerType *player_ptr)
156 {
157     FILE *fff = nullptr;
158     GAME_TEXT file_name[FILE_NAME_SIZE];
159     if (!open_temporary_file(&fff, file_name)) {
160         return;
161     }
162
163     int32_t total = 0;
164     for (const auto &[r_idx, r_ref] : monraces_info) {
165         if (r_ref.kind_flags.has(MonsterKindType::UNIQUE)) {
166             bool dead = (r_ref.max_num == 0);
167
168             if (dead) {
169                 total++;
170             }
171         } else {
172             if (r_ref.r_pkills > 0) {
173                 total += r_ref.r_pkills;
174             }
175         }
176     }
177
178     if (total < 1) {
179         fprintf(fff, _("あなたはまだ敵を倒していない。\n\n", "You have defeated no enemies yet.\n\n"));
180     } else
181 #ifdef JP
182         fprintf(fff, "あなたは%ld体の敵を倒している。\n\n", (long int)total);
183 #else
184         fprintf(fff, "You have defeated %ld %s.\n\n", (long int)total, (total == 1) ? "enemy" : "enemies");
185 #endif
186
187     std::vector<MonsterRaceId> who;
188     total = 0;
189     for (const auto &[r_idx, r_ref] : monraces_info) {
190         if (MonsterRace(r_ref.idx).is_valid() && !r_ref.name.empty()) {
191             who.push_back(r_ref.idx);
192         }
193     }
194
195     uint16_t why = 2;
196     char buf[80];
197     ang_sort(player_ptr, who.data(), &why, who.size(), ang_sort_comp_hook, ang_sort_swap_hook);
198     for (auto r_idx : who) {
199         auto *r_ptr = &monraces_info[r_idx];
200         if (r_ptr->kind_flags.has(MonsterKindType::UNIQUE)) {
201             bool dead = (r_ptr->max_num == 0);
202             if (dead) {
203                 if (r_ptr->defeat_level && r_ptr->defeat_time) {
204                     sprintf(buf, _(" - レベル%2d - %d:%02d:%02d", " - level %2d - %d:%02d:%02d"), r_ptr->defeat_level, r_ptr->defeat_time / (60 * 60),
205                         (r_ptr->defeat_time / 60) % 60, r_ptr->defeat_time % 60);
206                 } else {
207                     buf[0] = '\0';
208                 }
209
210                 fprintf(fff, "     %s%s\n", r_ptr->name.data(), buf);
211                 total++;
212             }
213
214             continue;
215         }
216
217         MONSTER_NUMBER this_monster = r_ptr->r_pkills;
218         if (this_monster <= 0) {
219             continue;
220         }
221
222 #ifdef JP
223         concptr number_of_kills = angband_strchr("pt", r_ptr->d_char) ? "人" : "体";
224         fprintf(fff, "     %3d %sの %s\n", (int)this_monster, number_of_kills, r_ptr->name.data());
225 #else
226         if (this_monster < 2) {
227             if (angband_strstr(r_ptr->name.data(), "coins")) {
228                 fprintf(fff, "     1 pile of %s\n", r_ptr->name.data());
229             } else {
230                 fprintf(fff, "     1 %s\n", r_ptr->name.data());
231             }
232         } else {
233             char ToPlural[80];
234             strcpy(ToPlural, r_ptr->name.data());
235             plural_aux(ToPlural);
236             fprintf(fff, "     %d %s\n", this_monster, ToPlural);
237         }
238 #endif
239         total += this_monster;
240     }
241
242     fprintf(fff, "----------------------------------------------\n");
243 #ifdef JP
244     fprintf(fff, "    合計: %lu 体を倒した。\n", (ulong)total);
245 #else
246     fprintf(fff, "   Total: %lu creature%s killed.\n", (ulong)total, (total == 1 ? "" : "s"));
247 #endif
248
249     angband_fclose(fff);
250     (void)show_file(player_ptr, true, file_name, _("倒した敵の数", "Kill Count"), 0, 0);
251     fd_kill(file_name);
252 }
253
254 /*
255  * Display the monsters in a group.
256  */
257 static void display_monster_list(int col, int row, int per_page, const std::vector<MonsterRaceId> &mon_idx, int mon_cur, int mon_top, bool visual_only)
258 {
259     int i;
260     for (i = 0; i < per_page && mon_top + i < static_cast<int>(mon_idx.size()); i++) {
261         TERM_COLOR attr;
262         MonsterRaceId r_idx = mon_idx[mon_top + i];
263         auto *r_ptr = &monraces_info[r_idx];
264         attr = ((i + mon_top == mon_cur) ? TERM_L_BLUE : TERM_WHITE);
265         c_prt(attr, (r_ptr->name.data()), row + i, col);
266         if (per_page == 1) {
267             c_prt(attr, format("%02x/%02x", r_ptr->x_attr, r_ptr->x_char), row + i, (w_ptr->wizard || visual_only) ? 56 : 61);
268         }
269
270         if (w_ptr->wizard || visual_only) {
271             c_prt(attr, format("%d", r_idx), row + i, 62);
272         }
273
274         term_erase(69, row + i, 255);
275         term_queue_bigchar(use_bigtile ? 69 : 70, row + i, r_ptr->x_attr, r_ptr->x_char, 0, 0);
276         if (!visual_only) {
277             if (r_ptr->kind_flags.has_not(MonsterKindType::UNIQUE)) {
278                 put_str(format("%5d", r_ptr->r_pkills), row + i, 73);
279             } else {
280                 c_put_str((r_ptr->max_num == 0 ? TERM_L_DARK : TERM_WHITE), (r_ptr->max_num == 0 ? _("死亡", " dead") : _("生存", "alive")), row + i, 74);
281             }
282         }
283     }
284
285     for (; i < per_page; i++) {
286         term_erase(col, row + i, 255);
287     }
288 }
289
290 /*!
291  * Display known monsters.
292  * @param player_ptr プレイヤーへの参照ポインタ
293  * @param need_redraw 画面の再描画が必要な時TRUE
294  * @param visual_only ???
295  * @param direct_r_idx モンスターID
296  * @todo 引数の詳細について加筆求む
297  */
298 void do_cmd_knowledge_monsters(PlayerType *player_ptr, bool *need_redraw, bool visual_only, std::optional<MonsterRaceId> direct_r_idx)
299 {
300     TERM_LEN wid, hgt;
301     term_get_size(&wid, &hgt);
302     std::vector<MonsterRaceId> r_idx_list;
303     std::vector<IDX> grp_idx;
304
305     int max = 0;
306     bool visual_list = false;
307     TERM_COLOR attr_top = 0;
308     byte char_left = 0;
309     monster_lore_mode mode;
310     const int browser_rows = hgt - 8;
311     if (!direct_r_idx.has_value()) {
312         mode = visual_only ? MONSTER_LORE_DEBUG : MONSTER_LORE_NORMAL;
313         int len;
314         for (IDX i = 0; monster_group_text[i] != nullptr; i++) {
315             len = strlen(monster_group_text[i]);
316             if (len > max) {
317                 max = len;
318             }
319
320             if ((monster_group_char[i] == ((char *)-1L)) || !collect_monsters(player_ptr, i, mode).empty()) {
321                 grp_idx.push_back(i);
322             }
323         }
324     } else {
325         r_idx_list.push_back(direct_r_idx.value());
326
327         (void)visual_mode_command('v', &visual_list, browser_rows - 1, wid - (max + 3), &attr_top, &char_left, &monraces_info[direct_r_idx.value()].x_attr,
328             &monraces_info[direct_r_idx.value()].x_char, need_redraw);
329     }
330
331     grp_idx.push_back(-1); // Sentinel
332     mode = visual_only ? MONSTER_LORE_RESEARCH : MONSTER_LORE_NONE;
333     IDX old_grp_cur = -1;
334     IDX grp_cur = 0;
335     IDX grp_top = 0;
336     IDX mon_cur = 0;
337     IDX mon_top = 0;
338     int column = 0;
339     bool flag = false;
340     bool redraw = true;
341     while (!flag) {
342         if (redraw) {
343             clear_from(0);
344             prt(format(_("%s - モンスター", "%s - monsters"), !visual_only ? _("知識", "Knowledge") : _("表示", "Visuals")), 2, 0);
345             if (!direct_r_idx.has_value()) {
346                 prt(_("グループ", "Group"), 4, 0);
347             }
348             prt(_("名前", "Name"), 4, max + 3);
349             if (w_ptr->wizard || visual_only) {
350                 prt("Idx", 4, 62);
351             }
352             prt(_("文字", "Sym"), 4, 67);
353             if (!visual_only) {
354                 prt(_("殺害数", "Kills"), 4, 72);
355             }
356
357             for (IDX i = 0; i < 78; i++) {
358                 term_putch(i, 5, TERM_WHITE, '=');
359             }
360
361             if (!direct_r_idx.has_value()) {
362                 for (IDX i = 0; i < browser_rows; i++) {
363                     term_putch(max + 1, 6 + i, TERM_WHITE, '|');
364                 }
365             }
366
367             redraw = false;
368         }
369
370         if (!direct_r_idx.has_value()) {
371             if (grp_cur < grp_top) {
372                 grp_top = grp_cur;
373             }
374             if (grp_cur >= grp_top + browser_rows) {
375                 grp_top = grp_cur - browser_rows + 1;
376             }
377
378             display_group_list(0, 6, max, browser_rows, grp_idx.data(), monster_group_text, grp_cur, grp_top);
379             if (old_grp_cur != grp_cur) {
380                 old_grp_cur = grp_cur;
381                 r_idx_list = collect_monsters(player_ptr, grp_idx[grp_cur], mode);
382             }
383
384             while (mon_cur < mon_top) {
385                 mon_top = std::max<short>(0, mon_top - browser_rows / 2);
386             }
387
388             while (mon_cur >= mon_top + browser_rows) {
389                 auto remain = static_cast<int>(r_idx_list.size()) - browser_rows;
390                 mon_top = static_cast<short>(std::min(remain, mon_top + browser_rows / 2));
391             }
392         }
393
394         if (!visual_list) {
395             display_monster_list(max + 3, 6, browser_rows, r_idx_list, mon_cur, mon_top, visual_only);
396         } else {
397             mon_top = mon_cur;
398             display_monster_list(max + 3, 6, 1, r_idx_list, mon_cur, mon_top, visual_only);
399             display_visual_list(max + 3, 7, browser_rows - 1, wid - (max + 3), attr_top, char_left);
400         }
401
402         prt(format(_("%d 種", "%d Races"), r_idx_list.size()), 3, 26);
403         prt(format(_("<方向>%s%s%s, ESC", "<dir>%s%s%s, ESC"), (!visual_list && !visual_only) ? _(", 'r'で思い出を見る", ", 'r' to recall") : "",
404                 visual_list ? _(", ENTERで決定", ", ENTER to accept") : _(", 'v'でシンボル変更", ", 'v' for visuals"),
405                 (attr_idx || char_idx) ? _(", 'c', 'p'でペースト", ", 'c', 'p' to paste") : _(", 'c'でコピー", ", 'c' to copy")),
406             hgt - 1, 0);
407
408         TERM_COLOR dummy_a;
409         char dummy_c = 0;
410         auto *attr_ptr = &dummy_a;
411         auto *char_ptr = &dummy_c;
412         if (!r_idx_list.empty()) {
413             auto *r_ptr = &monraces_info[r_idx_list[mon_cur]];
414             attr_ptr = &r_ptr->x_attr;
415             char_ptr = &r_ptr->x_char;
416
417             if (!visual_only) {
418                 monster_race_track(player_ptr, r_idx_list[mon_cur]);
419                 handle_stuff(player_ptr);
420             }
421
422             if (visual_list) {
423                 place_visual_list_cursor(max + 3, 7, r_ptr->x_attr, r_ptr->x_char, attr_top, char_left);
424             } else if (!column) {
425                 term_gotoxy(0, 6 + (grp_cur - grp_top));
426             } else {
427                 term_gotoxy(max + 3, 6 + (mon_cur - mon_top));
428             }
429         }
430
431         char ch = inkey();
432         if (visual_mode_command(ch, &visual_list, browser_rows - 1, wid - (max + 3), &attr_top, &char_left, attr_ptr, char_ptr, need_redraw)) {
433             if (direct_r_idx.has_value()) {
434                 switch (ch) {
435                 case '\n':
436                 case '\r':
437                 case ESCAPE:
438                     flag = true;
439                     break;
440                 }
441             }
442
443             continue;
444         }
445
446         switch (ch) {
447         case ESCAPE: {
448             flag = true;
449             break;
450         }
451
452         case 'R':
453         case 'r': {
454             if (!visual_list && !visual_only && MonsterRace(r_idx_list[mon_cur]).is_valid()) {
455                 screen_roff(player_ptr, r_idx_list[mon_cur], MONSTER_LORE_NORMAL);
456
457                 (void)inkey();
458
459                 redraw = true;
460             }
461
462             break;
463         }
464
465         default: {
466             browser_cursor(ch, &column, &grp_cur, grp_idx.size() - 1, &mon_cur, r_idx_list.size());
467
468             break;
469         }
470         }
471     }
472 }
473
474 /*
475  * List wanted monsters
476  * @param player_ptr プレイヤーへの参照ポインタ
477  */
478 void do_cmd_knowledge_bounty(PlayerType *player_ptr)
479 {
480     FILE *fff = nullptr;
481     GAME_TEXT file_name[FILE_NAME_SIZE];
482     if (!open_temporary_file(&fff, file_name)) {
483         return;
484     }
485
486     fprintf(fff, _("今日のターゲット : %s\n", "Today's target : %s\n"),
487         player_ptr->knows_daily_bounty ? monraces_info[w_ptr->today_mon].name.data() : _("不明", "unknown"));
488     fprintf(fff, "\n");
489     fprintf(fff, _("賞金首リスト\n", "List of wanted monsters\n"));
490     fprintf(fff, "----------------------------------------------\n");
491
492     bool listed = false;
493     for (const auto &[r_idx, is_achieved] : w_ptr->bounties) {
494         if (!is_achieved) {
495             fprintf(fff, "%s\n", monraces_info[r_idx].name.data());
496             listed = true;
497         }
498     }
499
500     if (!listed) {
501         fprintf(fff, "\n%s\n", _("賞金首はもう残っていません。", "There are no more wanted monster."));
502     }
503
504     angband_fclose(fff);
505     (void)show_file(player_ptr, true, file_name, _("賞金首の一覧", "Wanted monsters"), 0, 0);
506     fd_kill(file_name);
507 }