OSDN Git Service

79e21813a4c04f35b445cd4ee666152a31325410
[hengbandforosx/hengbandosx.git] / src / target / target-describer.cpp
1 #include "target/target-describer.h"
2 #include "action/travel-execution.h"
3 #include "core/stuff-handler.h"
4 #include "dungeon/quest.h"
5 #include "flavor/flavor-describer.h"
6 #include "floor/cave.h"
7 #include "floor/floor-object.h"
8 #include "floor/floor-town.h"
9 #include "floor/geometry.h"
10 #include "floor/object-scanner.h"
11 #include "game-option/input-options.h"
12 #include "grid/feature.h"
13 #include "grid/grid.h"
14 #include "info-reader/fixed-map-parser.h"
15 #include "io/cursor.h"
16 #include "io/input-key-acceptor.h"
17 #include "locale/english.h"
18 #include "lore/lore-util.h"
19 #include "monster-race/monster-race.h"
20 #include "monster/monster-describer.h"
21 #include "monster/monster-description-types.h"
22 #include "monster/monster-flag-types.h"
23 #include "monster/monster-util.h"
24 #include "object/item-tester-hooker.h"
25 #include "object/object-mark-types.h"
26 #include "player-base/player-race.h"
27 #include "player/player-status-table.h"
28 #include "system/building-type-definition.h"
29 #include "system/dungeon-info.h"
30 #include "system/floor-type-definition.h"
31 #include "system/grid-type-definition.h"
32 #include "system/item-entity.h"
33 #include "system/monster-entity.h"
34 #include "system/monster-race-info.h"
35 #include "system/player-type-definition.h"
36 #include "system/system-variables.h"
37 #include "system/terrain-type-definition.h"
38 #include "target/target-types.h"
39 #include "term/screen-processor.h"
40 #include "term/term-color-types.h"
41 #include "term/z-form.h"
42 #include "timed-effect/player-hallucination.h"
43 #include "timed-effect/timed-effects.h"
44 #include "util/bit-flags-calculator.h"
45 #include "view/display-lore.h"
46 #include "view/display-messages.h"
47 #include "view/display-monster-status.h"
48 #include "window/display-sub-windows.h"
49 #include "world/world.h"
50
51 namespace {
52 constexpr short CONTINUOUS_DESCRIPTION = 256;
53
54 class GridExamination {
55 public:
56     GridExamination(FloorType &floor, POSITION y, POSITION x, target_type mode, concptr info);
57     POSITION y;
58     POSITION x;
59     target_type mode;
60     bool boring = true;
61     concptr info;
62     concptr s1 = "";
63     concptr s2 = "";
64     concptr s3 = "";
65     concptr x_info = "";
66     char query = '\001';
67     char out_val[MAX_NLEN + 80]{};
68     OBJECT_IDX floor_list[23]{};
69     ITEM_NUMBER floor_num = 0;
70     Grid *g_ptr;
71     MonsterEntity *m_ptr;
72     OBJECT_IDX next_o_idx = 0;
73     FEAT_IDX feat = 0;
74     TerrainType *terrain_ptr = nullptr;
75     std::string name = "";
76 };
77
78 GridExamination::GridExamination(FloorType &floor, POSITION y, POSITION x, target_type mode, concptr info)
79     : y(y)
80     , x(x)
81     , mode(mode)
82     , info(info)
83 {
84     this->g_ptr = &floor.grid_array[y][x];
85     this->m_ptr = &floor.m_list[this->g_ptr->m_idx];
86     this->next_o_idx = 0;
87 }
88 }
89
90 bool show_gold_on_floor = false;
91
92 /*
93  * Evaluate number of kill needed to gain level
94  */
95 static std::string evaluate_monster_exp(PlayerType *player_ptr, MonsterEntity *m_ptr)
96 {
97     MonsterRaceInfo *ap_r_ptr = &m_ptr->get_appearance_monrace();
98     if ((player_ptr->lev >= PY_MAX_LEVEL) || PlayerRace(player_ptr).equals(PlayerRaceType::ANDROID)) {
99         return "**";
100     }
101
102     if (!ap_r_ptr->r_tkills || m_ptr->mflag2.has(MonsterConstantFlagType::KAGE)) {
103         if (!w_ptr->wizard) {
104             return "??";
105         }
106     }
107
108     int32_t exp_mon = ap_r_ptr->mexp * ap_r_ptr->level;
109     uint32_t exp_mon_frac = 0;
110     s64b_div(&exp_mon, &exp_mon_frac, 0, (player_ptr->max_plv + 2));
111
112     int32_t exp_adv = player_exp[player_ptr->lev - 1] * player_ptr->expfact;
113     uint32_t exp_adv_frac = 0;
114     s64b_div(&exp_adv, &exp_adv_frac, 0, 100);
115
116     s64b_sub(&exp_adv, &exp_adv_frac, player_ptr->exp, player_ptr->exp_frac);
117
118     s64b_add(&exp_adv, &exp_adv_frac, exp_mon, exp_mon_frac);
119     s64b_sub(&exp_adv, &exp_adv_frac, 0, 1);
120
121     s64b_div(&exp_adv, &exp_adv_frac, exp_mon, exp_mon_frac);
122
123     auto num = std::min<uint>(999, exp_adv_frac);
124     return format("%03ld", (long int)num);
125 }
126
127 static void describe_scan_result(PlayerType *player_ptr, GridExamination *ge_ptr)
128 {
129     if (!easy_floor) {
130         return;
131     }
132
133     ge_ptr->floor_num = scan_floor_items(player_ptr, ge_ptr->floor_list, ge_ptr->y, ge_ptr->x, SCAN_FLOOR_ONLY_MARKED, AllMatchItemTester());
134     if (ge_ptr->floor_num > 0) {
135         ge_ptr->x_info = _("x物 ", "x,");
136     }
137 }
138
139 static void describe_target(PlayerType *player_ptr, GridExamination *ge_ptr)
140 {
141     if (!player_ptr->is_located_at({ ge_ptr->y, ge_ptr->x })) {
142         ge_ptr->s1 = _("ターゲット:", "Target:");
143         return;
144     }
145
146 #ifdef JP
147     ge_ptr->s1 = "あなたは";
148     ge_ptr->s2 = "の上";
149     ge_ptr->s3 = "にいる";
150 #else
151     ge_ptr->s1 = "You are ";
152     ge_ptr->s2 = "on ";
153 #endif
154 }
155
156 static ProcessResult describe_hallucinated_target(PlayerType *player_ptr, GridExamination *ge_ptr)
157 {
158     if (!player_ptr->effects()->hallucination()->is_hallucinated()) {
159         return ProcessResult::PROCESS_CONTINUE;
160     }
161
162     concptr name = _("何か奇妙な物", "something strange");
163 #ifdef JP
164     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s]", ge_ptr->s1, name, ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
165 #else
166     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, name, ge_ptr->info);
167 #endif
168     prt(ge_ptr->out_val, 0, 0);
169     move_cursor_relative(ge_ptr->y, ge_ptr->x);
170     ge_ptr->query = inkey();
171     if ((ge_ptr->query != '\r') && (ge_ptr->query != '\n')) {
172         return ProcessResult::PROCESS_TRUE;
173     }
174
175     return ProcessResult::PROCESS_FALSE;
176 }
177
178 static bool describe_grid_lore(PlayerType *player_ptr, GridExamination *ge_ptr)
179 {
180     screen_save();
181     screen_roff(player_ptr, ge_ptr->m_ptr->ap_r_idx, MONSTER_LORE_NORMAL);
182     term_addstr(-1, TERM_WHITE, format(_("  [r思 %s%s]", "  [r,%s%s]"), ge_ptr->x_info, ge_ptr->info));
183     ge_ptr->query = inkey();
184     screen_load();
185     return ge_ptr->query != 'r';
186 }
187
188 static void describe_grid_monster(PlayerType *player_ptr, GridExamination *ge_ptr)
189 {
190     bool recall = false;
191     const auto m_name = monster_desc(player_ptr, ge_ptr->m_ptr, MD_INDEF_VISIBLE);
192     while (true) {
193         if (recall) {
194             if (describe_grid_lore(player_ptr, ge_ptr)) {
195                 return;
196             }
197
198             recall = false;
199             continue;
200         }
201
202         std::string acount = evaluate_monster_exp(player_ptr, ge_ptr->m_ptr);
203         const auto mon_desc = look_mon_desc(ge_ptr->m_ptr, 0x01);
204 #ifdef JP
205         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "[%s]%s%s(%s)%s%s [r思 %s%s]", acount.data(), ge_ptr->s1, m_name.data(), mon_desc.data(), ge_ptr->s2, ge_ptr->s3,
206             ge_ptr->x_info, ge_ptr->info);
207 #else
208         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "[%s]%s%s%s%s(%s) [r, %s%s]", acount.data(), ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, m_name.data(), mon_desc.data(),
209             ge_ptr->x_info, ge_ptr->info);
210 #endif
211         prt(ge_ptr->out_val, 0, 0);
212         move_cursor_relative(ge_ptr->y, ge_ptr->x);
213         ge_ptr->query = inkey();
214         if (ge_ptr->query != 'r') {
215             return;
216         }
217
218         recall = true;
219     }
220 }
221
222 static void describe_monster_person(GridExamination *ge_ptr)
223 {
224     const auto &monrace = ge_ptr->m_ptr->get_appearance_monrace();
225     ge_ptr->s1 = _("それは", "It is ");
226     if (monrace.sex == MonsterSex::FEMALE) {
227         ge_ptr->s1 = _("彼女は", "She is ");
228     } else if (monrace.sex == MonsterSex::MALE) {
229         ge_ptr->s1 = _("彼は", "He is ");
230     }
231
232 #ifdef JP
233     ge_ptr->s2 = "を";
234     ge_ptr->s3 = "持っている";
235 #else
236     ge_ptr->s2 = "carrying ";
237 #endif
238 }
239
240 static short describe_monster_item(PlayerType *player_ptr, GridExamination *ge_ptr)
241 {
242     for (const auto this_o_idx : ge_ptr->m_ptr->hold_o_idx_list) {
243         auto *o_ptr = &player_ptr->current_floor_ptr->o_list[this_o_idx];
244         const auto item_name = describe_flavor(player_ptr, o_ptr, 0);
245 #ifdef JP
246         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s[%s]", ge_ptr->s1, item_name.data(), ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
247 #else
248         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, item_name.data(), ge_ptr->info);
249 #endif
250         prt(ge_ptr->out_val, 0, 0);
251         move_cursor_relative(ge_ptr->y, ge_ptr->x);
252         ge_ptr->query = inkey();
253         if ((ge_ptr->query != '\r') && (ge_ptr->query != '\n') && (ge_ptr->query != ' ') && (ge_ptr->query != 'x')) {
254             return ge_ptr->query;
255         }
256
257         if ((ge_ptr->query == ' ') && !(ge_ptr->mode & TARGET_LOOK)) {
258             return ge_ptr->query;
259         }
260
261         ge_ptr->s2 = _("をまた", "also carrying ");
262     }
263
264     return CONTINUOUS_DESCRIPTION;
265 }
266
267 static bool within_char_util(const short input)
268 {
269     return (input > -127) && (input < 128);
270 }
271
272 static short describe_grid(PlayerType *player_ptr, GridExamination *ge_ptr)
273 {
274     if (!is_monster(ge_ptr->g_ptr->m_idx) || !player_ptr->current_floor_ptr->m_list[ge_ptr->g_ptr->m_idx].ml) {
275         return CONTINUOUS_DESCRIPTION;
276     }
277
278     ge_ptr->boring = false;
279     monster_race_track(player_ptr, ge_ptr->m_ptr->ap_r_idx);
280     health_track(player_ptr, ge_ptr->g_ptr->m_idx);
281     handle_stuff(player_ptr);
282     describe_grid_monster(player_ptr, ge_ptr);
283     if ((ge_ptr->query != '\r') && (ge_ptr->query != '\n') && (ge_ptr->query != ' ') && (ge_ptr->query != 'x')) {
284         return ge_ptr->query;
285     }
286
287     if ((ge_ptr->query == ' ') && !(ge_ptr->mode & TARGET_LOOK)) {
288         return ge_ptr->query;
289     }
290
291     describe_monster_person(ge_ptr);
292     const auto monster_item_description = describe_monster_item(player_ptr, ge_ptr);
293     if (within_char_util(monster_item_description)) {
294         return (char)monster_item_description;
295     }
296
297 #ifdef JP
298     ge_ptr->s2 = "の上";
299     ge_ptr->s3 = "にいる";
300 #else
301     ge_ptr->s2 = "on ";
302 #endif
303     return CONTINUOUS_DESCRIPTION;
304 }
305
306 static short describe_footing(PlayerType *player_ptr, GridExamination *ge_ptr)
307 {
308     if (ge_ptr->floor_num != 1) {
309         return CONTINUOUS_DESCRIPTION;
310     }
311
312     auto *o_ptr = &player_ptr->current_floor_ptr->o_list[ge_ptr->floor_list[0]];
313     const auto item_name = describe_flavor(player_ptr, o_ptr, 0);
314 #ifdef JP
315     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s[%s]", ge_ptr->s1, item_name.data(), ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
316 #else
317     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, item_name.data(), ge_ptr->info);
318 #endif
319     prt(ge_ptr->out_val, 0, 0);
320     move_cursor_relative(ge_ptr->y, ge_ptr->x);
321     ge_ptr->query = inkey();
322     return ge_ptr->query;
323 }
324
325 static short describe_footing_items(GridExamination *ge_ptr)
326 {
327     if (!ge_ptr->boring) {
328         return CONTINUOUS_DESCRIPTION;
329     }
330
331 #ifdef JP
332     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s %d個のアイテム%s%s ['x'で一覧, %s]", ge_ptr->s1, (int)ge_ptr->floor_num, ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
333 #else
334     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%sa pile of %d items [x,%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, (int)ge_ptr->floor_num, ge_ptr->info);
335 #endif
336     prt(ge_ptr->out_val, 0, 0);
337     move_cursor_relative(ge_ptr->y, ge_ptr->x);
338     ge_ptr->query = inkey();
339     if (ge_ptr->query != 'x' && ge_ptr->query != ' ') {
340         return ge_ptr->query;
341     }
342
343     return CONTINUOUS_DESCRIPTION;
344 }
345
346 static char describe_footing_many_items(PlayerType *player_ptr, GridExamination *ge_ptr, int *min_width)
347 {
348     while (true) {
349         screen_save();
350         show_gold_on_floor = true;
351         (void)show_floor_items(player_ptr, 0, ge_ptr->y, ge_ptr->x, min_width, AllMatchItemTester());
352         show_gold_on_floor = false;
353 #ifdef JP
354         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s %d個のアイテム%s%s [Enterで次へ, %s]", ge_ptr->s1, (int)ge_ptr->floor_num, ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
355 #else
356         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%sa pile of %d items [Enter,%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, (int)ge_ptr->floor_num, ge_ptr->info);
357 #endif
358         prt(ge_ptr->out_val, 0, 0);
359         ge_ptr->query = inkey();
360         screen_load();
361         if (ge_ptr->query != '\n' && ge_ptr->query != '\r') {
362             return ge_ptr->query;
363         }
364
365         if (ge_ptr->g_ptr->o_idx_list.size() < 2) {
366             continue;
367         }
368
369         ge_ptr->g_ptr->o_idx_list.rotate(player_ptr->current_floor_ptr);
370
371         // ターゲットしている床の座標を渡す必要があるので、window_stuff経由ではなく直接呼び出す
372         fix_floor_item_list(player_ptr, { ge_ptr->y, ge_ptr->x });
373     }
374 }
375
376 static short loop_describing_grid(PlayerType *player_ptr, GridExamination *ge_ptr)
377 {
378     if (ge_ptr->floor_num == 0) {
379         return CONTINUOUS_DESCRIPTION;
380     }
381
382     auto min_width = 0;
383     while (true) {
384         const auto footing_description = describe_footing(player_ptr, ge_ptr);
385         if (within_char_util(footing_description)) {
386             return (char)footing_description;
387         }
388
389         const auto footing_descriptions = describe_footing_items(ge_ptr);
390         if (within_char_util(footing_descriptions)) {
391             return (char)footing_descriptions;
392         }
393
394         return describe_footing_many_items(player_ptr, ge_ptr, &min_width);
395     }
396 }
397
398 static short describe_footing_sight(PlayerType *player_ptr, GridExamination *ge_ptr, ItemEntity *o_ptr)
399 {
400     if (o_ptr->marked.has_not(OmType::FOUND)) {
401         return CONTINUOUS_DESCRIPTION;
402     }
403
404     ge_ptr->boring = false;
405     const auto item_name = describe_flavor(player_ptr, o_ptr, 0);
406 #ifdef JP
407     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s[%s]", ge_ptr->s1, item_name.data(), ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
408 #else
409     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, item_name.data(), ge_ptr->info);
410 #endif
411     prt(ge_ptr->out_val, 0, 0);
412     move_cursor_relative(ge_ptr->y, ge_ptr->x);
413     ge_ptr->query = inkey();
414     if ((ge_ptr->query != '\r') && (ge_ptr->query != '\n') && (ge_ptr->query != ' ') && (ge_ptr->query != 'x')) {
415         return ge_ptr->query;
416     }
417
418     if ((ge_ptr->query == ' ') && !(ge_ptr->mode & TARGET_LOOK)) {
419         return ge_ptr->query;
420     }
421
422     ge_ptr->s1 = _("それは", "It is ");
423     if (o_ptr->number != 1) {
424         ge_ptr->s1 = _("それらは", "They are ");
425     }
426
427 #ifdef JP
428     ge_ptr->s2 = "の上";
429     ge_ptr->s3 = "に見える";
430 #else
431     ge_ptr->s2 = "on ";
432 #endif
433     return CONTINUOUS_DESCRIPTION;
434 }
435
436 static int16_t sweep_footing_items(PlayerType *player_ptr, GridExamination *ge_ptr)
437 {
438     for (const auto this_o_idx : ge_ptr->g_ptr->o_idx_list) {
439         auto *o_ptr = &player_ptr->current_floor_ptr->o_list[this_o_idx];
440         const auto ret = describe_footing_sight(player_ptr, ge_ptr, o_ptr);
441         if (within_char_util(ret)) {
442             return (char)ret;
443         }
444     }
445
446     return CONTINUOUS_DESCRIPTION;
447 }
448
449 static std::string decide_target_floor(PlayerType *player_ptr, GridExamination *ge_ptr)
450 {
451     auto *floor_ptr = player_ptr->current_floor_ptr;
452     if (ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::QUEST_ENTER)) {
453         QuestId old_quest = floor_ptr->quest_number;
454         const auto &quest_list = QuestList::get_instance();
455         const QuestId number = i2enum<QuestId>(ge_ptr->g_ptr->special);
456         const auto *q_ptr = &quest_list[number];
457         std::string_view msg(_("クエスト「%s」(%d階相当)", "the entrance to the quest '%s'(level %d)"));
458         for (int j = 0; j < 10; j++) {
459             quest_text[j][0] = '\0';
460         }
461
462         quest_text_line = 0;
463         floor_ptr->quest_number = number;
464         init_flags = INIT_NAME_ONLY;
465         parse_fixed_map(player_ptr, QUEST_DEFINITION_LIST, 0, 0, 0, 0);
466         floor_ptr->quest_number = old_quest;
467         return format(msg.data(), q_ptr->name.data(), q_ptr->level);
468     }
469
470     if (ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::BLDG) && !floor_ptr->inside_arena) {
471         return buildings[ge_ptr->terrain_ptr->subtype].name;
472     }
473
474     if (ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::ENTRANCE)) {
475         const auto &dungeon = dungeons_info[ge_ptr->g_ptr->special];
476         return format(_("%s(%d階相当)", "%s(level %d)"), dungeon.text.data(), dungeon.mindepth);
477     }
478
479     if (ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::TOWN)) {
480         return towns_info[ge_ptr->g_ptr->special].name;
481     }
482
483     if (player_ptr->wild_mode && (ge_ptr->feat == feat_floor)) {
484         return _("道", "road");
485     }
486
487     return ge_ptr->terrain_ptr->name;
488 }
489
490 static void describe_grid_monster_all(GridExamination *ge_ptr)
491 {
492     if (!w_ptr->wizard) {
493 #ifdef JP
494         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s[%s]", ge_ptr->s1, ge_ptr->name.data(), ge_ptr->s2, ge_ptr->s3, ge_ptr->info);
495 #else
496         strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s]", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, ge_ptr->name.data(), ge_ptr->info);
497 #endif
498         return;
499     }
500
501     std::string f_idx_str;
502     if (ge_ptr->g_ptr->mimic) {
503         f_idx_str = format("%d/%d", ge_ptr->g_ptr->feat, ge_ptr->g_ptr->mimic);
504     } else {
505         f_idx_str = std::to_string(ge_ptr->g_ptr->feat);
506     }
507
508 #ifdef JP
509     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s[%s] %x %s %d %d %d (%d,%d) %d", ge_ptr->s1, ge_ptr->name.data(), ge_ptr->s2, ge_ptr->s3, ge_ptr->info,
510         (uint)ge_ptr->g_ptr->info, f_idx_str.data(), ge_ptr->g_ptr->dists[FLOW_NORMAL], ge_ptr->g_ptr->costs[FLOW_NORMAL], ge_ptr->g_ptr->when, (int)ge_ptr->y,
511         (int)ge_ptr->x, travel.cost[ge_ptr->y][ge_ptr->x]);
512 #else
513     strnfmt(ge_ptr->out_val, sizeof(ge_ptr->out_val), "%s%s%s%s [%s] %x %s %d %d %d (%d,%d)", ge_ptr->s1, ge_ptr->s2, ge_ptr->s3, ge_ptr->name.data(), ge_ptr->info, ge_ptr->g_ptr->info,
514         f_idx_str.data(), ge_ptr->g_ptr->dists[FLOW_NORMAL], ge_ptr->g_ptr->costs[FLOW_NORMAL], ge_ptr->g_ptr->when, (int)ge_ptr->y, (int)ge_ptr->x);
515 #endif
516 }
517
518 /*!
519  * @brief xまたはlで指定したグリッドにあるアイテムやモンスターの説明を記述する
520  * @param player_ptr プレイヤーへの参照ポインタ
521  * @param y 指定グリッドのY座標
522  * @param x 指定グリッドのX座標
523  * @param mode x (KILL)かl (LOOK)
524  * @param info 記述用文字列
525  * @return 入力キー
526  * @todo xとlで処理を分ける?
527  */
528 char examine_grid(PlayerType *player_ptr, const POSITION y, const POSITION x, target_type mode, concptr info)
529 {
530     GridExamination tmp_eg(*player_ptr->current_floor_ptr, y, x, mode, info);
531     GridExamination *ge_ptr = &tmp_eg;
532     describe_scan_result(player_ptr, ge_ptr);
533     describe_target(player_ptr, ge_ptr);
534     ProcessResult next_target = describe_hallucinated_target(player_ptr, ge_ptr);
535     switch (next_target) {
536     case ProcessResult::PROCESS_FALSE:
537         return '\0';
538     case ProcessResult::PROCESS_TRUE:
539         return ge_ptr->query;
540     default:
541         break;
542     }
543
544     const auto description_grid = describe_grid(player_ptr, ge_ptr);
545     if (within_char_util(description_grid)) {
546         return (char)description_grid;
547     }
548
549     const auto loop_description = loop_describing_grid(player_ptr, ge_ptr);
550     if (within_char_util(loop_description)) {
551         return (char)loop_description;
552     }
553
554     const auto footing_items_description = sweep_footing_items(player_ptr, ge_ptr);
555     if (within_char_util(footing_items_description)) {
556         return (char)footing_items_description;
557     }
558
559     ge_ptr->feat = ge_ptr->g_ptr->get_feat_mimic();
560     if (!ge_ptr->g_ptr->is_mark() && !player_can_see_bold(player_ptr, y, x)) {
561         ge_ptr->feat = feat_none;
562     }
563
564     ge_ptr->terrain_ptr = &TerrainList::get_instance()[ge_ptr->feat];
565     if (!ge_ptr->boring && ge_ptr->terrain_ptr->flags.has_not(TerrainCharacteristics::REMEMBER)) {
566         return (ge_ptr->query != '\r') && (ge_ptr->query != '\n') ? ge_ptr->query : 0;
567     }
568
569     /*
570      * グローバル変数への代入をここで行っているので動かしたくない
571      * 安全を確保できたら構造体から外すことも検討する
572      */
573     ge_ptr->name = decide_target_floor(player_ptr, ge_ptr);
574     auto is_in = ge_ptr->terrain_ptr->flags.has_none_of({ TerrainCharacteristics::MOVE, TerrainCharacteristics::CAN_FLY });
575     is_in |= ge_ptr->terrain_ptr->flags.has_none_of({ TerrainCharacteristics::LOS, TerrainCharacteristics::TREE });
576     is_in |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::TOWN);
577     if (*ge_ptr->s2 && is_in) {
578         ge_ptr->s2 = _("の中", "in ");
579     }
580
581     auto is_entrance = ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::STORE);
582     is_entrance |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::QUEST_ENTER);
583     is_entrance |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::BLDG) && !player_ptr->current_floor_ptr->inside_arena;
584     is_entrance |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::ENTRANCE);
585     if (is_entrance) {
586         ge_ptr->s2 = _("の入口", "");
587     }
588 #ifdef JP
589 #else
590     else {
591         auto is_normal_terrain = ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::FLOOR);
592         is_normal_terrain |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::TOWN);
593         is_normal_terrain |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::SHALLOW);
594         is_normal_terrain |= ge_ptr->terrain_ptr->flags.has(TerrainCharacteristics::DEEP);
595         if (is_normal_terrain) {
596             ge_ptr->s3 = "";
597         } else {
598             ge_ptr->s3 = (is_a_vowel(ge_ptr->name[0])) ? "an " : "a ";
599         }
600     }
601 #endif
602
603     describe_grid_monster_all(ge_ptr);
604     prt(ge_ptr->out_val, 0, 0);
605     move_cursor_relative(y, x);
606     ge_ptr->query = inkey();
607     if ((ge_ptr->query != '\r') && (ge_ptr->query != '\n')) {
608         return ge_ptr->query;
609     }
610
611     return 0;
612 }