OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / spell-kind / spells-detection.cpp
1 #include "spell-kind/spells-detection.h"
2 #include "core/window-redrawer.h"
3 #include "dungeon/dungeon-flag-types.h"
4 #include "floor/cave.h"
5 #include "floor/floor-save-util.h"
6 #include "floor/geometry.h"
7 #include "grid/grid.h"
8 #include "grid/trap.h"
9 #include "monster-race/monster-race-hook.h"
10 #include "monster/monster-flag-types.h"
11 #include "monster/monster-info.h"
12 #include "monster/monster-status.h"
13 #include "monster/monster-update.h"
14 #include "object/object-mark-types.h"
15 #include "object/tval-types.h"
16 #include "realm/realm-song-numbers.h"
17 #include "realm/realm-song.h"
18 #include "spell-realm/spells-song.h"
19 #include "system/dungeon-info.h"
20 #include "system/floor-type-definition.h"
21 #include "system/grid-type-definition.h"
22 #include "system/item-entity.h"
23 #include "system/monster-race-info.h"
24 #include "system/player-type-definition.h"
25 #include "system/redrawing-flags-updater.h"
26 #include "system/terrain-type-definition.h"
27 #include "tracking/lore-tracker.h"
28 #include "util/string-processor.h"
29 #include "view/display-messages.h"
30
31 /*!
32  * @brief プレイヤー周辺の地形を感知する
33  * @param player_ptr プレイヤーへの参照ポインタ
34  * @param range 効果範囲
35  * @param flag 特定地形ID
36  * @param known 地形から危険フラグを外すならTRUE
37  * @return 効力があった場合TRUEを返す
38  */
39 static bool detect_feat_flag(PlayerType *player_ptr, POSITION range, TerrainCharacteristics flag, bool known)
40 {
41     auto &floor = *player_ptr->current_floor_ptr;
42     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
43         range /= 3;
44     }
45
46     bool detect = false;
47     for (POSITION y = 1; y < floor.height - 1; y++) {
48         for (POSITION x = 1; x <= floor.width - 1; x++) {
49             int dist = distance(player_ptr->y, player_ptr->x, y, x);
50             if (dist > range) {
51                 continue;
52             }
53
54             auto *g_ptr = &floor.grid_array[y][x];
55             if (flag == TerrainCharacteristics::TRAP) {
56                 /* Mark as detected */
57                 if (dist <= range && known) {
58                     if (dist <= range - 1) {
59                         g_ptr->info |= (CAVE_IN_DETECT);
60                     }
61
62                     g_ptr->info &= ~(CAVE_UNSAFE);
63
64                     lite_spot(player_ptr, y, x);
65                 }
66             }
67
68             if (g_ptr->cave_has_flag(flag)) {
69                 disclose_grid(player_ptr, y, x);
70                 g_ptr->info |= (CAVE_MARK);
71                 lite_spot(player_ptr, y, x);
72                 detect = true;
73             }
74         }
75     }
76
77     return detect;
78 }
79
80 /*!
81  * @brief プレイヤー周辺のトラップを感知する / Detect all traps on current panel
82  * @param player_ptr プレイヤーへの参照ポインタ
83  * @param range 効果範囲
84  * @param known 感知外範囲を超える警告フラグを立てる場合TRUEを返す
85  * @return 効力があった場合TRUEを返す
86  * @details
87  * 吟遊詩人による感知についてはFALSEを返す
88  */
89 bool detect_traps(PlayerType *player_ptr, POSITION range, bool known)
90 {
91     bool detect = detect_feat_flag(player_ptr, range, TerrainCharacteristics::TRAP, known);
92     if (!known && detect) {
93         detect_feat_flag(player_ptr, range, TerrainCharacteristics::TRAP, true);
94     }
95
96     if (known || detect) {
97         player_ptr->dtrap = true;
98     }
99
100     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 0) {
101         detect = false;
102     }
103
104     if (detect) {
105         msg_print(_("トラップの存在を感じとった!", "You sense the presence of traps!"));
106     }
107
108     return detect;
109 }
110
111 /*!
112  * @brief プレイヤー周辺のドアを感知する / Detect all doors on current panel
113  * @param player_ptr プレイヤーへの参照ポインタ
114  * @param range 効果範囲
115  * @return 効力があった場合TRUEを返す
116  */
117 bool detect_doors(PlayerType *player_ptr, POSITION range)
118 {
119     bool detect = detect_feat_flag(player_ptr, range, TerrainCharacteristics::DOOR, true);
120
121     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 0) {
122         detect = false;
123     }
124     if (detect) {
125         msg_print(_("ドアの存在を感じとった!", "You sense the presence of doors!"));
126     }
127
128     return detect;
129 }
130
131 /*!
132  * @brief プレイヤー周辺の階段を感知する / Detect all stairs on current panel
133  * @param player_ptr プレイヤーへの参照ポインタ
134  * @param range 効果範囲
135  * @return 効力があった場合TRUEを返す
136  */
137 bool detect_stairs(PlayerType *player_ptr, POSITION range)
138 {
139     bool detect = detect_feat_flag(player_ptr, range, TerrainCharacteristics::STAIRS, true);
140
141     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 0) {
142         detect = false;
143     }
144     if (detect) {
145         msg_print(_("階段の存在を感じとった!", "You sense the presence of stairs!"));
146     }
147
148     return detect;
149 }
150
151 /*!
152  * @brief プレイヤー周辺の地形財宝を感知する / Detect any treasure on the current panel
153  * @param player_ptr プレイヤーへの参照ポインタ
154  * @param range 効果範囲
155  * @return 効力があった場合TRUEを返す
156  */
157 bool detect_treasure(PlayerType *player_ptr, POSITION range)
158 {
159     bool detect = detect_feat_flag(player_ptr, range, TerrainCharacteristics::HAS_GOLD, true);
160
161     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 6) {
162         detect = false;
163     }
164     if (detect) {
165         msg_print(_("埋蔵された財宝の存在を感じとった!", "You sense the presence of buried treasure!"));
166     }
167
168     return detect;
169 }
170 /*!
171  * @brief プレイヤー周辺のアイテム財宝を感知する / Detect all "gold" objects on the current panel
172  * @param player_ptr プレイヤーへの参照ポインタ
173  * @param range 効果範囲
174  * @return 効力があった場合TRUEを返す
175  */
176 bool detect_objects_gold(PlayerType *player_ptr, POSITION range)
177 {
178     auto &floor = *player_ptr->current_floor_ptr;
179     POSITION range2 = range;
180     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
181         range2 /= 3;
182     }
183
184     /* Scan objects */
185     bool detect = false;
186     POSITION y, x;
187     for (OBJECT_IDX i = 1; i < floor.o_max; i++) {
188         auto *o_ptr = &floor.o_list[i];
189
190         if (!o_ptr->is_valid()) {
191             continue;
192         }
193         if (o_ptr->is_held_by_monster()) {
194             continue;
195         }
196
197         y = o_ptr->iy;
198         x = o_ptr->ix;
199         if (distance(player_ptr->y, player_ptr->x, y, x) > range2) {
200             continue;
201         }
202
203         if (o_ptr->bi_key.tval() == ItemKindType::GOLD) {
204             o_ptr->marked.set(OmType::FOUND);
205             lite_spot(player_ptr, y, x);
206             detect = true;
207         }
208     }
209
210     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 6) {
211         detect = false;
212     }
213     if (detect) {
214         msg_print(_("財宝の存在を感じとった!", "You sense the presence of treasure!"));
215     }
216
217     if (detect_monsters_string(player_ptr, range, "$")) {
218         detect = true;
219     }
220
221     return detect;
222 }
223
224 /*!
225  * @brief 通常のアイテムオブジェクトを感知する / Detect all "normal" objects on the current panel
226  * @param player_ptr プレイヤーへの参照ポインタ
227  * @param range 効果範囲
228  * @return 効力があった場合TRUEを返す
229  */
230 bool detect_objects_normal(PlayerType *player_ptr, POSITION range)
231 {
232     auto &floor = *player_ptr->current_floor_ptr;
233     POSITION range2 = range;
234     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
235         range2 /= 3;
236     }
237
238     bool detect = false;
239     for (OBJECT_IDX i = 1; i < floor.o_max; i++) {
240         auto *o_ptr = &floor.o_list[i];
241
242         if (!o_ptr->is_valid()) {
243             continue;
244         }
245         if (o_ptr->is_held_by_monster()) {
246             continue;
247         }
248
249         POSITION y = o_ptr->iy;
250         POSITION x = o_ptr->ix;
251
252         if (distance(player_ptr->y, player_ptr->x, y, x) > range2) {
253             continue;
254         }
255
256         if (o_ptr->bi_key.tval() != ItemKindType::GOLD) {
257             o_ptr->marked.set(OmType::FOUND);
258             lite_spot(player_ptr, y, x);
259             detect = true;
260         }
261     }
262
263     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 6) {
264         detect = false;
265     }
266     if (detect) {
267         RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::FOUND_ITEMS);
268         msg_print(_("アイテムの存在を感じとった!", "You sense the presence of objects!"));
269     }
270
271     if (detect_monsters_string(player_ptr, range, "!=?|/`")) {
272         detect = true;
273     }
274
275     return detect;
276 }
277
278 static bool is_object_magically(const ItemKindType tval)
279 {
280     switch (tval) {
281     case ItemKindType::WHISTLE:
282     case ItemKindType::AMULET:
283     case ItemKindType::RING:
284     case ItemKindType::STAFF:
285     case ItemKindType::WAND:
286     case ItemKindType::ROD:
287     case ItemKindType::SCROLL:
288     case ItemKindType::POTION:
289         return true;
290     default:
291         return false;
292     }
293 }
294
295 /*!
296  * @brief 魔法効果のあるのアイテムオブジェクトを感知する
297  * @param player_ptr プレイヤーへの参照ポインタ
298  * @param range 効果範囲
299  * @return 1つ以上感知したか否か
300  */
301 bool detect_objects_magic(PlayerType *player_ptr, POSITION range)
302 {
303     auto &floor = *player_ptr->current_floor_ptr;
304     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
305         range /= 3;
306     }
307
308     auto detect = false;
309     for (OBJECT_IDX i = 1; i < floor.o_max; i++) {
310         auto *o_ptr = &floor.o_list[i];
311         if (!o_ptr->is_valid() || o_ptr->is_held_by_monster()) {
312             continue;
313         }
314
315         auto y = o_ptr->iy;
316         auto x = o_ptr->ix;
317         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
318             continue;
319         }
320
321         auto has_bonus = o_ptr->to_a > 0;
322         has_bonus |= o_ptr->to_h + o_ptr->to_d > 0;
323         if (o_ptr->is_fixed_or_random_artifact() || o_ptr->is_ego() || is_object_magically(o_ptr->bi_key.tval()) || o_ptr->is_spell_book() || has_bonus) {
324             o_ptr->marked.set(OmType::FOUND);
325             lite_spot(player_ptr, y, x);
326             detect = true;
327         }
328     }
329
330     if (detect) {
331         RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::FOUND_ITEMS);
332         msg_print(_("魔法のアイテムの存在を感じとった!", "You sense the presence of magic objects!"));
333     }
334
335     return detect;
336 }
337
338 /*!
339  * @brief 一般のモンスターを感知する / Detect all "normal" monsters on the current panel
340  * @param player_ptr プレイヤーへの参照ポインタ
341  * @param range 効果範囲
342  * @return 効力があった場合TRUEを返す
343  */
344 bool detect_monsters_normal(PlayerType *player_ptr, POSITION range)
345 {
346     auto &floor = *player_ptr->current_floor_ptr;
347     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
348         range /= 3;
349     }
350
351     bool flag = false;
352     for (MONSTER_IDX i = 1; i < floor.m_max; i++) {
353         auto *m_ptr = &floor.m_list[i];
354         auto *r_ptr = &m_ptr->get_monrace();
355         if (!m_ptr->is_valid()) {
356             continue;
357         }
358
359         POSITION y = m_ptr->fy;
360         POSITION x = m_ptr->fx;
361         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
362             continue;
363         }
364
365         if (r_ptr->misc_flags.has_not(MonsterMiscType::INVISIBLE) || player_ptr->see_inv) {
366             m_ptr->mflag2.set({ MonsterConstantFlagType::MARK, MonsterConstantFlagType::SHOW });
367             update_monster(player_ptr, i, false);
368             flag = true;
369         }
370     }
371
372     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 3) {
373         flag = false;
374     }
375     if (flag) {
376         msg_print(_("モンスターの存在を感じとった!", "You sense the presence of monsters!"));
377     }
378
379     return flag;
380 }
381
382 /*!
383  * @brief 不可視のモンスターを感知する / Detect all "invisible" monsters around the player
384  * @param player_ptr プレイヤーへの参照ポインタ
385  * @param range 効果範囲
386  * @return 効力があった場合TRUEを返す
387  */
388 bool detect_monsters_invis(PlayerType *player_ptr, POSITION range)
389 {
390     auto &floor = *player_ptr->current_floor_ptr;
391     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
392         range /= 3;
393     }
394
395     const auto &tracker = LoreTracker::get_instance();
396     auto &rfu = RedrawingFlagsUpdater::get_instance();
397     auto flag = false;
398     for (MONSTER_IDX i = 1; i < floor.m_max; i++) {
399         auto *m_ptr = &floor.m_list[i];
400         auto *r_ptr = &m_ptr->get_monrace();
401
402         if (!m_ptr->is_valid()) {
403             continue;
404         }
405
406         POSITION y = m_ptr->fy;
407         POSITION x = m_ptr->fx;
408
409         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
410             continue;
411         }
412
413         if (r_ptr->misc_flags.has(MonsterMiscType::INVISIBLE)) {
414             if (tracker.is_tracking(m_ptr->r_idx)) {
415                 rfu.set_flag(SubWindowRedrawingFlag::MONSTER_LORE);
416             }
417
418             m_ptr->mflag2.set({ MonsterConstantFlagType::MARK, MonsterConstantFlagType::SHOW });
419             update_monster(player_ptr, i, false);
420             flag = true;
421         }
422     }
423
424     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 3) {
425         flag = false;
426     }
427     if (flag) {
428         msg_print(_("透明な生物の存在を感じとった!", "You sense the presence of invisible creatures!"));
429     }
430
431     return flag;
432 }
433
434 /*!
435  * @brief 邪悪なモンスターを感知する / Detect all "evil" monsters on current panel
436  * @param player_ptr プレイヤーへの参照ポインタ
437  * @param range 効果範囲
438  * @return 効力があった場合TRUEを返す
439  */
440 bool detect_monsters_evil(PlayerType *player_ptr, POSITION range)
441 {
442     auto &floor = *player_ptr->current_floor_ptr;
443     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
444         range /= 3;
445     }
446
447     const auto &tracker = LoreTracker::get_instance();
448     auto &rfu = RedrawingFlagsUpdater::get_instance();
449     auto flag = false;
450     for (MONSTER_IDX i = 1; i < floor.m_max; i++) {
451         auto *m_ptr = &floor.m_list[i];
452         auto *r_ptr = &m_ptr->get_monrace();
453         if (!m_ptr->is_valid()) {
454             continue;
455         }
456
457         POSITION y = m_ptr->fy;
458         POSITION x = m_ptr->fx;
459
460         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
461             continue;
462         }
463
464         if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
465             if (m_ptr->is_original_ap()) {
466                 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
467                 if (tracker.is_tracking(m_ptr->r_idx)) {
468                     rfu.set_flag(SubWindowRedrawingFlag::MONSTER_LORE);
469                 }
470             }
471
472             m_ptr->mflag2.set({ MonsterConstantFlagType::MARK, MonsterConstantFlagType::SHOW });
473             update_monster(player_ptr, i, false);
474             flag = true;
475         }
476     }
477
478     if (flag) {
479         msg_print(_("邪悪なる生物の存在を感じとった!", "You sense the presence of evil creatures!"));
480     }
481
482     return flag;
483 }
484
485 /*!
486  * @brief 無生命のモンスターを感知する(アンデッド、悪魔系を含む) / Detect all "nonliving", "undead" or "demonic" monsters on current panel
487  * @param player_ptr プレイヤーへの参照ポインタ
488  * @param range 効果範囲
489  * @return 効力があった場合TRUEを返す
490  */
491 bool detect_monsters_nonliving(PlayerType *player_ptr, POSITION range)
492 {
493     auto &floor = *player_ptr->current_floor_ptr;
494     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
495         range /= 3;
496     }
497
498     const auto &tracker = LoreTracker::get_instance();
499     auto &rfu = RedrawingFlagsUpdater::get_instance();
500     auto flag = false;
501     for (MONSTER_IDX i = 1; i < floor.m_max; i++) {
502         auto *m_ptr = &floor.m_list[i];
503         if (!m_ptr->is_valid()) {
504             continue;
505         }
506
507         POSITION y = m_ptr->fy;
508         POSITION x = m_ptr->fx;
509         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
510             continue;
511         }
512
513         if (!m_ptr->has_living_flag()) {
514             if (tracker.is_tracking(m_ptr->r_idx)) {
515                 rfu.set_flag(SubWindowRedrawingFlag::MONSTER_LORE);
516             }
517
518             m_ptr->mflag2.set({ MonsterConstantFlagType::MARK, MonsterConstantFlagType::SHOW });
519             update_monster(player_ptr, i, false);
520             flag = true;
521         }
522     }
523
524     if (flag) {
525         msg_print(_("自然でないモンスターの存在を感じた!", "You sense the presence of unnatural beings!"));
526     }
527
528     return flag;
529 }
530
531 /*!
532  * @brief 精神のあるモンスターを感知する / Detect all monsters it has mind on current panel
533  * @param player_ptr プレイヤーへの参照ポインタ
534  * @param range 効果範囲
535  * @return 効力があった場合TRUEを返す
536  */
537 bool detect_monsters_mind(PlayerType *player_ptr, POSITION range)
538 {
539     auto &floor = *player_ptr->current_floor_ptr;
540     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
541         range /= 3;
542     }
543
544     const auto &tracker = LoreTracker::get_instance();
545     auto &rfu = RedrawingFlagsUpdater::get_instance();
546     auto flag = false;
547     for (MONSTER_IDX i = 1; i < floor.m_max; i++) {
548         auto *m_ptr = &floor.m_list[i];
549         auto *r_ptr = &m_ptr->get_monrace();
550         if (!m_ptr->is_valid()) {
551             continue;
552         }
553
554         POSITION y = m_ptr->fy;
555         POSITION x = m_ptr->fx;
556
557         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
558             continue;
559         }
560
561         if (r_ptr->misc_flags.has_not(MonsterMiscType::EMPTY_MIND)) {
562             if (tracker.is_tracking(m_ptr->r_idx)) {
563                 rfu.set_flag(SubWindowRedrawingFlag::MONSTER_LORE);
564             }
565
566             m_ptr->mflag2.set({ MonsterConstantFlagType::MARK, MonsterConstantFlagType::SHOW });
567             update_monster(player_ptr, i, false);
568             flag = true;
569         }
570     }
571
572     if (flag) {
573         msg_print(_("殺気を感じとった!", "You sense the presence of someone's mind!"));
574     }
575
576     return flag;
577 }
578
579 /*!
580  * @brief 該当シンボルのモンスターを感知する / Detect all (string) monsters on current panel
581  * @param player_ptr プレイヤーへの参照ポインタ
582  * @param range 効果範囲
583  * @param Match 対応シンボルの混じったモンスター文字列(複数指定化)
584  * @return 効力があった場合TRUEを返す
585  */
586 bool detect_monsters_string(PlayerType *player_ptr, POSITION range, concptr Match)
587 {
588     auto &floor = *player_ptr->current_floor_ptr;
589     if (floor.get_dungeon_definition().flags.has(DungeonFeatureType::DARKNESS)) {
590         range /= 3;
591     }
592
593     const auto &tracker = LoreTracker::get_instance();
594     auto &rfu = RedrawingFlagsUpdater::get_instance();
595     auto flag = false;
596     for (MONSTER_IDX i = 1; i < floor.m_max; i++) {
597         auto *m_ptr = &floor.m_list[i];
598         const auto &monrace = m_ptr->get_monrace();
599         if (!m_ptr->is_valid()) {
600             continue;
601         }
602
603         POSITION y = m_ptr->fy;
604         POSITION x = m_ptr->fx;
605
606         if (distance(player_ptr->y, player_ptr->x, y, x) > range) {
607             continue;
608         }
609
610         if (angband_strchr(Match, monrace.symbol_definition.character)) {
611             if (tracker.is_tracking(m_ptr->r_idx)) {
612                 rfu.set_flag(SubWindowRedrawingFlag::MONSTER_LORE);
613             }
614
615             m_ptr->mflag2.set({ MonsterConstantFlagType::MARK, MonsterConstantFlagType::SHOW });
616             update_monster(player_ptr, i, false);
617             flag = true;
618         }
619     }
620
621     if (music_singing(player_ptr, MUSIC_DETECT) && get_singing_count(player_ptr) > 3) {
622         flag = false;
623     }
624     if (flag) {
625         msg_print(_("モンスターの存在を感じとった!", "You sense the presence of monsters!"));
626     }
627
628     return flag;
629 }
630
631 /*!
632  * @brief 全感知処理 / Detect everything
633  * @param player_ptr プレイヤーへの参照ポインタ
634  * @param range 効果範囲
635  * @return 効力があった場合TRUEを返す
636  */
637 bool detect_all(PlayerType *player_ptr, POSITION range)
638 {
639     bool detect = false;
640     if (detect_traps(player_ptr, range, true)) {
641         detect = true;
642     }
643     if (detect_doors(player_ptr, range)) {
644         detect = true;
645     }
646     if (detect_stairs(player_ptr, range)) {
647         detect = true;
648     }
649     if (detect_objects_gold(player_ptr, range)) {
650         detect = true;
651     }
652     if (detect_objects_normal(player_ptr, range)) {
653         detect = true;
654     }
655     if (detect_monsters_invis(player_ptr, range)) {
656         detect = true;
657     }
658     if (detect_monsters_normal(player_ptr, range)) {
659         detect = true;
660     }
661     return detect;
662 }