OSDN Git Service

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