2 * @file knowledge-monsters.cpp
3 * @brief 既知のモンスターに関する情報を表示する
6 * @todo サブルーチン分割を行うと行数が膨れ上がりそう、再分割も検討すべし
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-race/race-indice-types.h"
24 #include "monster/monster-describer.h"
25 #include "monster/monster-description-types.h"
26 #include "monster/monster-info.h"
27 #include "monster/monster-status.h"
28 #include "monster/smart-learn-types.h"
29 #include "pet/pet-util.h"
30 #include "system/floor-type-definition.h"
31 #include "system/monster-race-definition.h"
32 #include "system/monster-type-definition.h"
33 #include "system/player-type-definition.h"
34 #include "term/screen-processor.h"
35 #include "term/term-color-types.h"
36 #include "util/angband-files.h"
37 #include "util/bit-flags-calculator.h"
38 #include "util/int-char-converter.h"
39 #include "util/sort.h"
40 #include "util/string-processor.h"
41 #include "view/display-lore.h"
42 #include "view/display-monster-status.h"
43 #include "world/world.h"
46 * @brief 特定の与えられた条件に応じてモンスターのIDリストを作成する / Build a list of monster indexes in the given group.
47 * @param player_ptr プレイヤーへの参照ポインタ
48 * @param grp_cur グループ種別。リスト表記中の左一覧(各シンボル及び/ユニーク(-1)/騎乗可能モンスター(-2)/賞金首(-3)/アンバーの王族(-4))を参照できる
49 * @param mon_idx[] ID一覧を返す配列参照
50 * @param mode 思い出の扱いに関するモード
51 * @return 得られたモンスターIDの数 / The number of monsters in the group
53 static IDX collect_monsters(PlayerType *player_ptr, IDX grp_cur, MonsterRaceId mon_idx[], monster_lore_mode mode)
55 concptr group_char = monster_group_char[grp_cur];
56 bool grp_unique = (monster_group_char[grp_cur] == (char *)-1L);
57 bool grp_riding = (monster_group_char[grp_cur] == (char *)-2L);
58 bool grp_wanted = (monster_group_char[grp_cur] == (char *)-3L);
59 bool grp_amberite = (monster_group_char[grp_cur] == (char *)-4L);
62 for (const auto &[r_idx, r_ref] : r_info) {
63 if (r_ref.name.empty()) {
66 if (((mode != MONSTER_LORE_DEBUG) && (mode != MONSTER_LORE_RESEARCH)) && !cheat_know && !r_ref.r_sights) {
71 if (r_ref.kind_flags.has_not(MonsterKindType::UNIQUE)) {
74 } else if (grp_riding) {
75 if (none_bits(r_ref.flags7, RF7_RIDING)) {
78 } else if (grp_wanted) {
79 auto wanted = player_ptr->knows_daily_bounty && (w_ptr->today_mon == r_ref.idx);
80 wanted |= MonsterRace(r_ref.idx).is_bounty(false);
85 } else if (grp_amberite) {
86 if (r_ref.kind_flags.has_not(MonsterKindType::AMBERITE)) {
90 if (!angband_strchr(group_char, r_ref.d_char)) {
95 mon_idx[mon_cnt++] = r_ref.idx;
96 if (mode == MONSTER_LORE_NORMAL) {
99 if (mode == MONSTER_LORE_DEBUG) {
104 mon_idx[mon_cnt] = i2enum<MonsterRaceId>(-1);
106 ang_sort(player_ptr, mon_idx, &dummy_why, mon_cnt, ang_sort_comp_monster_level, ang_sort_swap_hook);
111 * @brief 現在のペットを表示するコマンドのメインルーチン /
112 * Display current pets
113 * @param player_ptr プレイヤーへの参照ポインタ
115 void do_cmd_knowledge_pets(PlayerType *player_ptr)
118 GAME_TEXT file_name[FILE_NAME_SIZE];
119 if (!open_temporary_file(&fff, file_name)) {
124 GAME_TEXT pet_name[MAX_NLEN];
126 for (int i = player_ptr->current_floor_ptr->m_max - 1; i >= 1; i--) {
127 m_ptr = &player_ptr->current_floor_ptr->m_list[i];
128 if (!monster_is_valid(m_ptr) || !is_pet(m_ptr)) {
133 monster_desc(player_ptr, pet_name, m_ptr, MD_ASSUME_VISIBLE | MD_INDEF_VISIBLE);
134 fprintf(fff, "%s (%s)\n", pet_name, look_mon_desc(m_ptr, 0x00));
137 int show_upkeep = calculate_upkeep(player_ptr);
139 fprintf(fff, "----------------------------------------------\n");
141 fprintf(fff, " 合計: %d 体のペット\n", t_friends);
143 fprintf(fff, " Total: %d pet%s.\n", t_friends, (t_friends == 1 ? "" : "s"));
145 fprintf(fff, _(" 維持コスト: %d%% MP\n", " Upkeep: %d%% mana.\n"), show_upkeep);
148 (void)show_file(player_ptr, true, file_name, _("現在のペット", "Current Pets"), 0, 0);
153 * @brief 現在までに倒したモンスターを表示するコマンドのメインルーチン /
154 * @param player_ptr プレイヤーへの参照ポインタ
156 * @note the player ghosts are ignored.
158 void do_cmd_knowledge_kill_count(PlayerType *player_ptr)
161 GAME_TEXT file_name[FILE_NAME_SIZE];
162 if (!open_temporary_file(&fff, file_name)) {
167 for (const auto &[r_idx, r_ref] : r_info) {
168 if (r_ref.kind_flags.has(MonsterKindType::UNIQUE)) {
169 bool dead = (r_ref.max_num == 0);
175 if (r_ref.r_pkills > 0) {
176 total += r_ref.r_pkills;
182 fprintf(fff, _("あなたはまだ敵を倒していない。\n\n", "You have defeated no enemies yet.\n\n"));
185 fprintf(fff, "あなたは%ld体の敵を倒している。\n\n", (long int)total);
187 fprintf(fff, "You have defeated %ld %s.\n\n", (long int)total, (total == 1) ? "enemy" : "enemies");
190 std::vector<MonsterRaceId> who;
192 for (const auto &[r_idx, r_ref] : r_info) {
193 if (MonsterRace(r_ref.idx).is_valid() && !r_ref.name.empty()) {
194 who.push_back(r_ref.idx);
200 ang_sort(player_ptr, who.data(), &why, who.size(), ang_sort_comp_hook, ang_sort_swap_hook);
201 for (auto r_idx : who) {
202 auto *r_ptr = &r_info[r_idx];
203 if (r_ptr->kind_flags.has(MonsterKindType::UNIQUE)) {
204 bool dead = (r_ptr->max_num == 0);
206 if (r_ptr->defeat_level && r_ptr->defeat_time) {
207 sprintf(buf, _(" - レベル%2d - %d:%02d:%02d", " - level %2d - %d:%02d:%02d"), r_ptr->defeat_level, r_ptr->defeat_time / (60 * 60),
208 (r_ptr->defeat_time / 60) % 60, r_ptr->defeat_time % 60);
213 fprintf(fff, " %s%s\n", r_ptr->name.c_str(), buf);
220 MONSTER_NUMBER this_monster = r_ptr->r_pkills;
221 if (this_monster <= 0) {
226 concptr number_of_kills = angband_strchr("pt", r_ptr->d_char) ? "人" : "体";
227 fprintf(fff, " %3d %sの %s\n", (int)this_monster, number_of_kills, r_ptr->name.c_str());
229 if (this_monster < 2) {
230 if (angband_strstr(r_ptr->name.c_str(), "coins")) {
231 fprintf(fff, " 1 pile of %s\n", r_ptr->name.c_str());
233 fprintf(fff, " 1 %s\n", r_ptr->name.c_str());
237 strcpy(ToPlural, r_ptr->name.c_str());
238 plural_aux(ToPlural);
239 fprintf(fff, " %d %s\n", this_monster, ToPlural);
242 total += this_monster;
245 fprintf(fff, "----------------------------------------------\n");
247 fprintf(fff, " 合計: %lu 体を倒した。\n", (ulong)total);
249 fprintf(fff, " Total: %lu creature%s killed.\n", (ulong)total, (total == 1 ? "" : "s"));
253 (void)show_file(player_ptr, true, file_name, _("倒した敵の数", "Kill Count"), 0, 0);
258 * Display the monsters in a group.
260 static void display_monster_list(int col, int row, int per_page, MonsterRaceId mon_idx[], int mon_cur, int mon_top, bool visual_only)
263 for (i = 0; i < per_page && (mon_idx[mon_top + i] >= MonsterRaceId::PLAYER); i++) {
265 MonsterRaceId r_idx = mon_idx[mon_top + i];
266 auto *r_ptr = &r_info[r_idx];
267 attr = ((i + mon_top == mon_cur) ? TERM_L_BLUE : TERM_WHITE);
268 c_prt(attr, (r_ptr->name.c_str()), row + i, col);
270 c_prt(attr, format("%02x/%02x", r_ptr->x_attr, r_ptr->x_char), row + i, (w_ptr->wizard || visual_only) ? 56 : 61);
273 if (w_ptr->wizard || visual_only) {
274 c_prt(attr, format("%d", r_idx), row + i, 62);
277 term_erase(69, row + i, 255);
278 term_queue_bigchar(use_bigtile ? 69 : 70, row + i, r_ptr->x_attr, r_ptr->x_char, 0, 0);
280 if (r_ptr->kind_flags.has_not(MonsterKindType::UNIQUE)) {
281 put_str(format("%5d", r_ptr->r_pkills), row + i, 73);
283 c_put_str((r_ptr->max_num == 0 ? TERM_L_DARK : TERM_WHITE), (r_ptr->max_num == 0 ? _("死亡", " dead") : _("生存", "alive")), row + i, 74);
288 for (; i < per_page; i++) {
289 term_erase(col, row + i, 255);
294 * Display known monsters.
295 * @param player_ptr プレイヤーへの参照ポインタ
296 * @param need_redraw 画面の再描画が必要な時TRUE
297 * @param visual_only ???
298 * @param direct_r_idx モンスターID
299 * @todo 引数の詳細について加筆求む
301 void do_cmd_knowledge_monsters(PlayerType *player_ptr, bool *need_redraw, bool visual_only, std::optional<MonsterRaceId> direct_r_idx)
304 term_get_size(&wid, &hgt);
305 std::vector<MonsterRaceId> mon_idx(r_info.size());
306 std::vector<IDX> grp_idx;
310 bool visual_list = false;
311 TERM_COLOR attr_top = 0;
313 monster_lore_mode mode;
314 int browser_rows = hgt - 8;
315 if (!direct_r_idx.has_value()) {
316 mode = visual_only ? MONSTER_LORE_DEBUG : MONSTER_LORE_NORMAL;
318 for (IDX i = 0; monster_group_text[i] != nullptr; i++) {
319 len = strlen(monster_group_text[i]);
324 if ((monster_group_char[i] == ((char *)-1L)) || collect_monsters(player_ptr, i, mon_idx.data(), mode)) {
325 grp_idx.push_back(i);
331 mon_idx[0] = direct_r_idx.value();
333 mon_idx[1] = i2enum<MonsterRaceId>(-1);
335 (void)visual_mode_command('v', &visual_list, browser_rows - 1, wid - (max + 3), &attr_top, &char_left, &r_info[direct_r_idx.value()].x_attr,
336 &r_info[direct_r_idx.value()].x_char, need_redraw);
339 grp_idx.push_back(-1); // Sentinel
340 mode = visual_only ? MONSTER_LORE_RESEARCH : MONSTER_LORE_NONE;
341 IDX old_grp_cur = -1;
352 prt(format(_("%s - モンスター", "%s - monsters"), !visual_only ? _("知識", "Knowledge") : _("表示", "Visuals")), 2, 0);
353 if (!direct_r_idx.has_value()) {
354 prt(_("グループ", "Group"), 4, 0);
356 prt(_("名前", "Name"), 4, max + 3);
357 if (w_ptr->wizard || visual_only) {
360 prt(_("文字", "Sym"), 4, 67);
362 prt(_("殺害数", "Kills"), 4, 72);
365 for (IDX i = 0; i < 78; i++) {
366 term_putch(i, 5, TERM_WHITE, '=');
369 if (!direct_r_idx.has_value()) {
370 for (IDX i = 0; i < browser_rows; i++) {
371 term_putch(max + 1, 6 + i, TERM_WHITE, '|');
378 if (!direct_r_idx.has_value()) {
379 if (grp_cur < grp_top) {
382 if (grp_cur >= grp_top + browser_rows) {
383 grp_top = grp_cur - browser_rows + 1;
386 display_group_list(0, 6, max, browser_rows, grp_idx.data(), monster_group_text, grp_cur, grp_top);
387 if (old_grp_cur != grp_cur) {
388 old_grp_cur = grp_cur;
389 mon_cnt = collect_monsters(player_ptr, grp_idx[grp_cur], mon_idx.data(), mode);
392 while (mon_cur < mon_top) {
393 mon_top = std::max<short>(0, mon_top - browser_rows / 2);
396 while (mon_cur >= mon_top + browser_rows) {
397 mon_top = std::min<short>(mon_cnt - browser_rows, mon_top + browser_rows / 2);
402 display_monster_list(max + 3, 6, browser_rows, mon_idx.data(), mon_cur, mon_top, visual_only);
405 display_monster_list(max + 3, 6, 1, mon_idx.data(), mon_cur, mon_top, visual_only);
406 display_visual_list(max + 3, 7, browser_rows - 1, wid - (max + 3), attr_top, char_left);
409 prt(format(_("%d 種", "%d Races"), mon_cnt), 3, 26);
410 prt(format(_("<方向>%s%s%s, ESC", "<dir>%s%s%s, ESC"), (!visual_list && !visual_only) ? _(", 'r'で思い出を見る", ", 'r' to recall") : "",
411 visual_list ? _(", ENTERで決定", ", ENTER to accept") : _(", 'v'でシンボル変更", ", 'v' for visuals"),
412 (attr_idx || char_idx) ? _(", 'c', 'p'でペースト", ", 'c', 'p' to paste") : _(", 'c'でコピー", ", 'c' to copy")),
417 auto *attr_ptr = &dummy_a;
418 auto *char_ptr = &dummy_c;
419 if (mon_idx[0] != i2enum<MonsterRaceId>(-1)) {
420 auto *r_ptr = &r_info[mon_idx[mon_cur]];
421 attr_ptr = &r_ptr->x_attr;
422 char_ptr = &r_ptr->x_char;
426 monster_race_track(player_ptr, mon_idx[mon_cur]);
428 handle_stuff(player_ptr);
432 place_visual_list_cursor(max + 3, 7, r_ptr->x_attr, r_ptr->x_char, attr_top, char_left);
433 } else if (!column) {
434 term_gotoxy(0, 6 + (grp_cur - grp_top));
436 term_gotoxy(max + 3, 6 + (mon_cur - mon_top));
441 if (visual_mode_command(ch, &visual_list, browser_rows - 1, wid - (max + 3), &attr_top, &char_left, attr_ptr, char_ptr, need_redraw)) {
442 if (direct_r_idx.has_value()) {
463 if (!visual_list && !visual_only && MonsterRace(mon_idx[mon_cur]).is_valid()) {
464 screen_roff(player_ptr, mon_idx[mon_cur], MONSTER_LORE_NORMAL);
475 browser_cursor(ch, &column, &grp_cur, grp_idx.size() - 1, &mon_cur, mon_cnt);
484 * List wanted monsters
485 * @param player_ptr プレイヤーへの参照ポインタ
487 void do_cmd_knowledge_bounty(PlayerType *player_ptr)
490 GAME_TEXT file_name[FILE_NAME_SIZE];
491 if (!open_temporary_file(&fff, file_name)) {
495 fprintf(fff, _("今日のターゲット : %s\n", "Today's target : %s\n"),
496 player_ptr->knows_daily_bounty ? r_info[w_ptr->today_mon].name.c_str() : _("不明", "unknown"));
498 fprintf(fff, _("賞金首リスト\n", "List of wanted monsters\n"));
499 fprintf(fff, "----------------------------------------------\n");
502 for (const auto &[r_idx, is_achieved] : w_ptr->bounties) {
504 fprintf(fff, "%s\n", r_info[r_idx].name.c_str());
510 fprintf(fff, "\n%s\n", _("賞金首はもう残っていません。", "There are no more wanted monster."));
514 (void)show_file(player_ptr, true, file_name, _("賞金首の一覧", "Wanted monsters"), 0, 0);