2 * @brief モンスターの移動に関する処理
7 #include "monster-floor/monster-move.h"
8 #include "core/disturbance.h"
9 #include "core/speed-table.h"
10 #include "core/window-redrawer.h"
11 #include "effect/attribute-types.h"
12 #include "effect/effect-characteristics.h"
13 #include "effect/effect-processor.h"
14 #include "floor/cave.h"
15 #include "floor/geometry.h"
16 #include "game-option/disturbance-options.h"
17 #include "grid/feature.h"
18 #include "grid/grid.h"
19 #include "io/files-util.h"
20 #include "monster-attack/monster-attack-processor.h"
21 #include "monster-floor/monster-object.h"
22 #include "monster-race/monster-race.h"
23 #include "monster-race/race-indice-types.h"
24 #include "monster/monster-describer.h"
25 #include "monster/monster-flag-types.h"
26 #include "monster/monster-info.h"
27 #include "monster/monster-processor-util.h"
28 #include "monster/monster-status.h"
29 #include "monster/monster-update.h"
30 #include "pet/pet-util.h"
31 #include "player/player-status-flags.h"
32 #include "system/angband-system.h"
33 #include "system/floor-type-definition.h"
34 #include "system/grid-type-definition.h"
35 #include "system/monster-entity.h"
36 #include "system/monster-race-info.h"
37 #include "system/player-type-definition.h"
38 #include "system/redrawing-flags-updater.h"
39 #include "system/terrain-type-definition.h"
40 #include "target/projection-path-calculator.h"
41 #include "util/bit-flags-calculator.h"
42 #include "view/display-messages.h"
44 static bool check_hp_for_terrain_destruction(const TerrainType &terrain, const MonsterEntity &monster)
46 auto can_destroy = terrain.flags.has_not(TerrainCharacteristics::GLASS);
47 can_destroy |= monster.get_monrace().behavior_flags.has(MonsterBehaviorType::STUPID);
48 can_destroy |= monster.hp >= std::max(monster.maxhp / 3, 200);
53 * @brief モンスターによる壁の透過・破壊を行う
54 * @param player_ptr プレイヤーへの参照ポインタ
55 * @param monster モンスターへの参照
56 * @param pos モンスターの移動先座標
57 * @param can_cross モンスターが地形を踏破できるならばTRUE
58 * @return 透過も破壊もしなかった場合はFALSE、それ以外はTRUE
60 static bool process_wall(PlayerType *player_ptr, turn_flags *turn_flags_ptr, const MonsterEntity &monster, const Pos2D &pos, bool can_cross)
62 const auto &grid = player_ptr->current_floor_ptr->get_grid(pos);
63 const auto &terrain = grid.get_terrain();
64 if (player_ptr->is_located_at(pos)) {
65 turn_flags_ptr->do_move = true;
69 if (grid.has_monster()) {
70 turn_flags_ptr->do_move = true;
74 using Mft = MonsterFeatureType;
75 using Tc = TerrainCharacteristics;
76 const auto &monrace = monster.get_monrace();
77 auto can_kill_wall = monrace.feature_flags.has(Mft::KILL_WALL);
78 can_kill_wall &= can_cross ? terrain.flags.has_not(Tc::LOS) : !turn_flags_ptr->is_riding_mon;
79 can_kill_wall &= terrain.flags.has(Tc::HURT_DISI);
80 can_kill_wall &= terrain.flags.has_not(Tc::PERMANENT);
81 can_kill_wall &= check_hp_for_terrain_destruction(terrain, monster);
83 turn_flags_ptr->do_move = true;
85 turn_flags_ptr->must_alter_to_move = true;
88 turn_flags_ptr->did_kill_wall = true;
96 turn_flags_ptr->do_move = true;
97 if ((monrace.feature_flags.has(Mft::PASS_WALL)) && (!turn_flags_ptr->is_riding_mon || has_pass_wall(player_ptr)) && terrain.flags.has(Tc::CAN_PASS)) {
98 turn_flags_ptr->did_pass_wall = true;
105 * @brief モンスターが普通のドアを開ける処理
106 * @param player_ptr プレイヤーへの参照ポインタ
107 * @param turn_flags_ptr ターン経過処理フラグへの参照ポインタ
108 * @param monster モンスターへの参照
109 * @param pos モンスターの移動先座標
110 * @return ドアを打ち破るならここでの処理は実行せずtrue、開けるだけなら開けてfalseを返す
111 * @todo 関数名と処理内容が不一致、後で直す
113 static bool bash_normal_door(PlayerType *player_ptr, turn_flags *turn_flags_ptr, const MonsterEntity &monster, const Pos2D &pos)
115 const auto &monrace = monster.get_monrace();
116 const auto &grid = player_ptr->current_floor_ptr->get_grid(pos);
117 const auto &terrain = grid.get_terrain();
118 turn_flags_ptr->do_move = false;
119 using Tc = TerrainCharacteristics;
120 auto can_bash = monrace.behavior_flags.has_not(MonsterBehaviorType::OPEN_DOOR);
121 can_bash |= terrain.flags.has_not(Tc::OPEN);
122 can_bash |= monster.is_pet() && ((player_ptr->pet_extra_flags & PF_OPEN_DOORS) == 0);
127 if (terrain.power == 0) {
128 turn_flags_ptr->did_open_door = true;
129 turn_flags_ptr->do_turn = true;
133 if (randint0(monster.hp / 10) > terrain.power) {
134 cave_alter_feat(player_ptr, pos.y, pos.x, Tc::DISARM);
135 turn_flags_ptr->do_turn = true;
143 * @brief モンスターがガラスのドアを開ける処理
144 * @param player_ptr プレイヤーへの参照ポインタ
145 * @param turn_flags_ptr ターン経過処理フラグへの参照ポインタ
146 * @param monster モンスターへの参照
147 * @param terrain 地形への参照
148 * @param may_bash ドアを打ち破るならtrue、開けるだけならfalse
149 * @todo 関数名と処理内容が不一致、後で直す
151 static void bash_glass_door(PlayerType *player_ptr, turn_flags *turn_flags_ptr, const MonsterEntity &monster, const TerrainType &terrain, bool may_bash)
153 const auto &monrace = monster.get_monrace();
154 auto can_bash = may_bash;
155 can_bash &= monrace.behavior_flags.has(MonsterBehaviorType::BASH_DOOR);
156 can_bash &= terrain.flags.has(TerrainCharacteristics::BASH);
157 can_bash &= !monster.is_pet() || any_bits(player_ptr->pet_extra_flags, PF_OPEN_DOORS);
162 if (!check_hp_for_terrain_destruction(terrain, monster) || (randint0(monster.hp / 10) <= terrain.power)) {
166 if (terrain.flags.has(TerrainCharacteristics::GLASS)) {
167 msg_print(_("ガラスが砕ける音がした!", "You hear glass breaking!"));
169 msg_print(_("ドアを叩き開ける音がした!", "You hear a door burst open!"));
173 disturb(player_ptr, false, false);
176 turn_flags_ptr->did_bash_door = true;
177 turn_flags_ptr->do_move = true;
178 turn_flags_ptr->must_alter_to_move = true;
182 * @brief モンスターによるドアの開放・破壊を行う
183 * @param player_ptr プレイヤーへの参照ポインタ
184 * @param turn_flags_ptr ターン経過処理フラグへの参照ポインタ
185 * @param monster モンスターへの参照
186 * @param pos モンスターの移動先座標
187 * @return モンスターが死亡した場合のみFALSE
189 static bool process_door(PlayerType *player_ptr, turn_flags *turn_flags_ptr, const MonsterEntity &monster, const Pos2D &pos)
191 auto &monrace = monster.get_monrace();
192 const auto &grid = player_ptr->current_floor_ptr->get_grid(pos);
193 if (!is_closed_door(player_ptr, grid.feat)) {
197 auto &terrain = grid.get_terrain();
198 auto may_bash = bash_normal_door(player_ptr, turn_flags_ptr, monster, pos);
199 bash_glass_door(player_ptr, turn_flags_ptr, monster, terrain, may_bash);
200 if (!turn_flags_ptr->did_open_door && !turn_flags_ptr->did_bash_door) {
204 const auto is_open = feat_state(player_ptr->current_floor_ptr, grid.feat, TerrainCharacteristics::OPEN) == grid.feat;
205 if (turn_flags_ptr->did_bash_door && ((randint0(100) < 50) || is_open || terrain.flags.has(TerrainCharacteristics::GLASS))) {
206 cave_alter_feat(player_ptr, pos.y, pos.x, TerrainCharacteristics::BASH);
207 if (!monster.is_valid()) {
208 auto &rfu = RedrawingFlagsUpdater::get_instance();
209 rfu.set_flag(StatusRecalculatingFlag::FLOW);
210 static constexpr auto flags = {
211 SubWindowRedrawingFlag::OVERHEAD,
212 SubWindowRedrawingFlag::DUNGEON,
214 rfu.set_flags(flags);
215 if (is_original_ap_and_seen(player_ptr, &monster)) {
216 monrace.r_behavior_flags.set(MonsterBehaviorType::BASH_DOOR);
222 cave_alter_feat(player_ptr, pos.y, pos.x, TerrainCharacteristics::OPEN);
225 turn_flags_ptr->do_view = true;
230 * @brief 守りのルーンによるモンスターの移動制限を処理する
231 * @param player_ptr プレイヤーへの参照ポインタ
232 * @param turn_flags_ptr ターン経過処理フラグへの参照ポインタ
233 * @param m_ptr モンスターへの参照ポインタ
234 * @param pos モンスターの移動先座標
235 * @return ルーンに侵入できるか否か
237 static bool process_protection_rune(PlayerType *player_ptr, turn_flags *turn_flags_ptr, MonsterEntity *m_ptr, const Pos2D &pos)
239 auto &grid = player_ptr->current_floor_ptr->get_grid(pos);
240 const auto &monrace = m_ptr->get_monrace();
241 auto can_enter = turn_flags_ptr->do_move;
242 can_enter &= grid.is_rune_protection();
243 can_enter &= (monrace.behavior_flags.has_not(MonsterBehaviorType::NEVER_BLOW)) || !player_ptr->is_located_at(pos);
248 turn_flags_ptr->do_move = false;
249 if (m_ptr->is_pet() || (randint1(BREAK_RUNE_PROTECTION) >= monrace.level)) {
253 if (grid.is_mark()) {
254 msg_print(_("守りのルーンが壊れた!", "The rune of protection is broken!"));
257 grid.info &= ~(CAVE_MARK);
258 grid.info &= ~(CAVE_OBJECT);
260 turn_flags_ptr->do_move = true;
261 note_spot(player_ptr, pos.y, pos.x);
267 * @param player_ptr プレイヤーへの参照ポインタ
268 * @param turn_flags_ptr ターン経過処理フラグへの参照ポインタ
269 * @param m_ptr モンスターへの参照ポインタ
270 * @param pos モンスターの移動先座標
271 * @return モンスターが死亡した場合のみFALSE
273 static bool process_explosive_rune(PlayerType *player_ptr, turn_flags *turn_flags_ptr, MonsterEntity *m_ptr, const Pos2D &pos)
275 auto &grid = player_ptr->current_floor_ptr->get_grid(pos);
276 const auto &monrace = m_ptr->get_monrace();
277 auto should_explode = turn_flags_ptr->do_move;
278 should_explode &= grid.is_rune_explosion();
279 should_explode &= (monrace.behavior_flags.has_not(MonsterBehaviorType::NEVER_BLOW)) || !player_ptr->is_located_at(pos);
280 if (!should_explode) {
284 turn_flags_ptr->do_move = false;
285 if (m_ptr->is_pet()) {
289 if (randint1(BREAK_RUNE_EXPLOSION) > monrace.level) {
290 if (grid.info & CAVE_MARK) {
291 msg_print(_("ルーンが爆発した!", "The rune explodes!"));
292 BIT_FLAGS project_flags = PROJECT_GRID | PROJECT_ITEM | PROJECT_KILL | PROJECT_JUMP | PROJECT_NO_HANGEKI;
293 project(player_ptr, 0, 2, pos.y, pos.x, 2 * (player_ptr->lev + damroll(7, 7)), AttributeType::MANA, project_flags);
296 msg_print(_("爆発のルーンは解除された。", "An explosive rune was disarmed."));
299 grid.info &= ~(CAVE_MARK);
300 grid.info &= ~(CAVE_OBJECT);
303 note_spot(player_ptr, pos.y, pos.x);
304 lite_spot(player_ptr, pos.y, pos.x);
306 if (!m_ptr->is_valid()) {
310 turn_flags_ptr->do_move = true;
315 * @brief モンスターが壁を掘った後続処理を実行する
316 * @param player_ptr プレイヤーへの参照ポインタ
317 * @turn_flags_ptr ターン経過処理フラグへの参照ポインタ
318 * @param monster モンスターへの参照
319 * @param pos モンスターの移動先座標
320 * @return モンスターが死亡した場合のみFALSE
322 static bool process_post_dig_wall(PlayerType *player_ptr, turn_flags *turn_flags_ptr, const MonsterEntity &monster, const Pos2D &pos)
324 auto &monrace = monster.get_monrace();
325 const auto &grid = player_ptr->current_floor_ptr->get_grid(pos);
326 const auto &terrain = grid.get_terrain();
327 if (!turn_flags_ptr->did_kill_wall || !turn_flags_ptr->do_move) {
331 constexpr auto chance_sound = 20;
332 if (one_in_(chance_sound)) {
333 if (terrain.flags.has(TerrainCharacteristics::GLASS)) {
334 msg_print(_("何かの砕ける音が聞こえる。", "There is a crashing sound."));
336 msg_print(_("ギシギシいう音が聞こえる。", "There is a grinding sound."));
340 cave_alter_feat(player_ptr, pos.y, pos.x, TerrainCharacteristics::HURT_DISI);
342 if (!monster.is_valid()) {
343 auto &rfu = RedrawingFlagsUpdater::get_instance();
344 rfu.set_flag(StatusRecalculatingFlag::FLOW);
345 static constexpr auto flags = {
346 SubWindowRedrawingFlag::OVERHEAD,
347 SubWindowRedrawingFlag::DUNGEON,
349 rfu.set_flags(flags);
350 if (is_original_ap_and_seen(player_ptr, &monster)) {
351 monrace.r_feature_flags.set(MonsterFeatureType::KILL_WALL);
357 turn_flags_ptr->do_view = true;
358 turn_flags_ptr->do_turn = true;
363 * @brief モンスターの移動に関するメインルーチン
364 * @param player_ptr プレイヤーへの参照ポインタ
365 * @param turn_flags_ptr ターン経過処理フラグへの参照ポインタ
366 * @param m_idx モンスターID
367 * @param mm モンスターの移動方向
368 * @param pos モンスターの移動前座標
369 * @param count 移動回数 (のはず todo)
370 * @return 移動が阻害される何か (ドア等)があったらFALSE
371 * @todo 少し長いが、これといってブロックとしてまとまった部分もないので暫定でこのままとする
373 bool process_monster_movement(PlayerType *player_ptr, turn_flags *turn_flags_ptr, MONSTER_IDX m_idx, DIRECTION *mm, const Pos2D &pos, int *count)
375 for (int i = 0; mm[i]; i++) {
378 d = ddd[randint0(8)];
381 const Pos2D pos_neighbor(pos.y + ddy[d], pos.x + ddx[d]);
382 if (!in_bounds2(player_ptr->current_floor_ptr, pos_neighbor.y, pos_neighbor.x)) {
386 auto &grid = player_ptr->current_floor_ptr->get_grid(pos_neighbor);
387 auto &monster = player_ptr->current_floor_ptr->m_list[m_idx];
388 auto &monrace = monster.get_monrace();
389 bool can_cross = monster_can_cross_terrain(player_ptr, grid.feat, &monrace, turn_flags_ptr->is_riding_mon ? CEM_RIDING : 0);
391 if (!process_wall(player_ptr, turn_flags_ptr, monster, pos_neighbor, can_cross)) {
392 if (!process_door(player_ptr, turn_flags_ptr, monster, pos_neighbor)) {
397 if (!process_protection_rune(player_ptr, turn_flags_ptr, &monster, pos_neighbor)) {
398 if (!process_explosive_rune(player_ptr, turn_flags_ptr, &monster, pos_neighbor)) {
403 exe_monster_attack_to_player(player_ptr, turn_flags_ptr, m_idx, pos_neighbor);
404 if (process_monster_attack_to_monster(player_ptr, turn_flags_ptr, m_idx, &grid, can_cross)) {
408 if (turn_flags_ptr->is_riding_mon) {
409 const auto &monster_riding = player_ptr->current_floor_ptr->m_list[player_ptr->riding];
410 if (!player_ptr->riding_ryoute && !monster_riding.is_fearful()) {
411 turn_flags_ptr->do_move = false;
415 if (!process_post_dig_wall(player_ptr, turn_flags_ptr, monster, pos_neighbor)) {
419 if (turn_flags_ptr->must_alter_to_move && monrace.feature_flags.has(MonsterFeatureType::AQUATIC)) {
420 if (!monster_can_cross_terrain(player_ptr, grid.feat, &monrace, turn_flags_ptr->is_riding_mon ? CEM_RIDING : 0)) {
421 turn_flags_ptr->do_move = false;
425 if (turn_flags_ptr->do_move && !can_cross && !turn_flags_ptr->did_kill_wall && !turn_flags_ptr->did_bash_door) {
426 turn_flags_ptr->do_move = false;
429 if (turn_flags_ptr->do_move && monrace.behavior_flags.has(MonsterBehaviorType::NEVER_MOVE)) {
430 if (is_original_ap_and_seen(player_ptr, &monster)) {
431 monrace.r_behavior_flags.set(MonsterBehaviorType::NEVER_MOVE);
434 turn_flags_ptr->do_move = false;
437 if (!turn_flags_ptr->do_move) {
438 if (turn_flags_ptr->do_turn) {
445 turn_flags_ptr->do_turn = true;
446 const auto &terrain = grid.get_terrain();
447 auto can_recover_energy = terrain.flags.has(TerrainCharacteristics::TREE);
448 can_recover_energy &= monrace.feature_flags.has_not(MonsterFeatureType::CAN_FLY);
449 can_recover_energy &= monrace.wilderness_flags.has_not(MonsterWildernessType::WILD_WOOD);
450 if (can_recover_energy) {
451 monster.energy_need += ENERGY_NEED();
454 if (!update_riding_monster(player_ptr, turn_flags_ptr, m_idx, pos.y, pos.x, pos_neighbor.y, pos_neighbor.x)) {
458 const auto &ap_r_ref = monster.get_appearance_monrace();
459 const auto is_projectable = projectable(player_ptr, player_ptr->y, player_ptr->x, monster.fy, monster.fx);
460 const auto can_see = disturb_near && monster.mflag.has(MonsterTemporaryFlagType::VIEW) && is_projectable;
461 const auto is_high_level = disturb_high && (ap_r_ref.r_tkills > 0) && (ap_r_ref.level >= player_ptr->lev);
462 if (monster.ml && (disturb_move || can_see || is_high_level)) {
463 if (monster.is_hostile()) {
464 disturb(player_ptr, false, true);
468 bool is_takable_or_killable = !grid.o_idx_list.empty();
469 is_takable_or_killable &= monrace.behavior_flags.has_any_of({ MonsterBehaviorType::TAKE_ITEM, MonsterBehaviorType::KILL_ITEM });
471 bool is_pickup_items = (player_ptr->pet_extra_flags & PF_PICKUP_ITEMS) != 0;
472 is_pickup_items &= monrace.behavior_flags.has(MonsterBehaviorType::TAKE_ITEM);
474 is_takable_or_killable &= !monster.is_pet() || is_pickup_items;
475 if (!is_takable_or_killable) {
476 if (turn_flags_ptr->do_turn) {
483 update_object_by_monster_movement(player_ptr, turn_flags_ptr, m_idx, pos_neighbor.y, pos_neighbor.x);
484 if (turn_flags_ptr->do_turn) {
494 static bool can_speak(const MonsterRaceInfo &ap_r_ref, MonsterSpeakType mon_speak_type)
496 const auto can_speak_all = ap_r_ref.speak_flags.has(MonsterSpeakType::SPEAK_ALL);
497 const auto can_speak_specific = ap_r_ref.speak_flags.has(mon_speak_type);
498 return can_speak_all || can_speak_specific;
501 static std::string_view get_speak_filename(const MonsterEntity &monster)
503 const auto &ap_monrace = monster.get_appearance_monrace();
504 if (monster.is_fearful() && can_speak(ap_monrace, MonsterSpeakType::SPEAK_FEAR)) {
505 return _("monfear_j.txt", "monfear.txt");
508 constexpr auto monspeak_txt(_("monspeak_j.txt", "monspeak.txt"));
509 if (monster.is_pet() && can_speak(ap_monrace, MonsterSpeakType::SPEAK_BATTLE)) {
513 if (monster.is_friendly() && can_speak(ap_monrace, MonsterSpeakType::SPEAK_FRIEND)) {
514 return _("monfrien_j.txt", "monfrien.txt");
517 if (can_speak(ap_monrace, MonsterSpeakType::SPEAK_BATTLE)) {
525 * @brief モンスターを喋らせたり足音を立てたりする
526 * @param player_ptr プレイヤーへの参照ポインタ
527 * @param m_idx モンスターID
528 * @param oy モンスターが元々いたY座標
529 * @param ox モンスターが元々いたX座標
530 * @param aware モンスターがプレイヤーに気付いているならばTRUE、超隠密状態ならばFALSE
532 void process_speak_sound(PlayerType *player_ptr, MONSTER_IDX m_idx, POSITION oy, POSITION ox, bool aware)
534 if (AngbandSystem::get_instance().is_phase_out()) {
538 const auto &floor = *player_ptr->current_floor_ptr;
539 const auto &monster = floor.m_list[m_idx];
540 constexpr auto chance_noise = 20;
541 if (monster.ap_r_idx == MonsterRaceId::CYBER && one_in_(chance_noise) && !monster.ml && (monster.cdis <= MAX_PLAYER_SIGHT)) {
543 disturb(player_ptr, false, false);
545 msg_print(_("重厚な足音が聞こえた。", "You hear heavy steps."));
548 const auto can_speak = monster.get_appearance_monrace().speak_flags.any();
549 constexpr auto chance_speak = 8;
550 if (!can_speak || !aware || !one_in_(chance_speak) || !floor.has_los({ oy, ox }) || !projectable(player_ptr, oy, ox, player_ptr->y, player_ptr->x)) {
554 const auto m_name = monster.ml ? monster_desc(player_ptr, &monster, 0) : std::string(_("それ", "It"));
555 auto filename = get_speak_filename(monster);
556 if (filename.empty()) {
560 const auto monmessage = get_random_line(filename.data(), enum2i(monster.ap_r_idx));
562 msg_format(_("%s^%s", "%s^ %s"), m_name.data(), monmessage->data());
567 * @brief モンスターの目標地点をセットする / Set the target of counter attack
568 * @param m_ptr モンスターの参照ポインタ
572 void set_target(MonsterEntity *m_ptr, POSITION y, POSITION x)
579 * @brief モンスターの目標地点をリセットする / Reset the target of counter attack
580 * @param m_ptr モンスターの参照ポインタ
582 void reset_target(MonsterEntity *m_ptr)
584 set_target(m_ptr, 0, 0);