OSDN Git Service

Merge pull request #3814 from Slimebreath6078/feature/Add_Laffey_II
[hengbandforosx/hengbandosx.git] / src / monster / monster-status.cpp
1 #include "monster/monster-status.h"
2 #include "autopick/autopick-pref-processor.h"
3 #include "core/speed-table.h"
4 #include "floor/geometry.h"
5 #include "game-option/birth-options.h"
6 #include "game-option/text-display-options.h"
7 #include "grid/grid.h"
8 #include "monster-race/monster-kind-mask.h"
9 #include "monster-race/monster-race.h"
10 #include "monster-race/race-flags-resistance.h"
11 #include "monster-race/race-special-flags.h"
12 #include "monster/monster-describer.h"
13 #include "monster/monster-info.h"
14 #include "monster/monster-list.h"
15 #include "monster/monster-status-setter.h" //!< @todo 相互依存. 後で何とかする.
16 #include "monster/monster-update.h"
17 #include "system/angband-system.h"
18 #include "system/floor-type-definition.h"
19 #include "system/monster-entity.h"
20 #include "system/monster-race-info.h"
21 #include "system/player-type-definition.h"
22 #include "system/redrawing-flags-updater.h"
23 #include "timed-effect/player-blindness.h"
24 #include "timed-effect/player-hallucination.h"
25 #include "timed-effect/timed-effects.h"
26 #include "util/bit-flags-calculator.h"
27 #include "view/display-messages.h"
28 #include "world/world.h"
29 #if JP
30 #else
31 #include "monster/monster-description-types.h"
32 #endif
33
34 static uint32_t csleep_noise;
35
36 /*!
37  * @brief モンスターIDからPOWERFULフラグの有無を取得する /
38  * @param floor_ptr 現在フロアへの参照ポインタ
39  * @param m_idx モンスターID
40  * @return POWERFULフラグがあればTRUE、なければFALSEを返す。
41  */
42 bool monster_is_powerful(FloorType *floor_ptr, MONSTER_IDX m_idx)
43 {
44     auto *m_ptr = &floor_ptr->m_list[m_idx];
45     auto *r_ptr = &m_ptr->get_monrace();
46     return r_ptr->misc_flags.has(MonsterMiscType::POWERFUL);
47 }
48
49 /*!
50  * @brief モンスターIDからモンスターのレベルを取得する(ただし最低1を保証する) /
51  * @param m_idx モンスターID
52  * @return モンスターのレベル
53  */
54 DEPTH monster_level_idx(FloorType *floor_ptr, MONSTER_IDX m_idx)
55 {
56     auto *m_ptr = &floor_ptr->m_list[m_idx];
57     auto *r_ptr = &m_ptr->get_monrace();
58     return (r_ptr->level >= 1) ? r_ptr->level : 1;
59 }
60
61 /*!
62  * @brief モンスターに与えたダメージの修正処理 /
63  * Modify the physical damage done to the monster.
64  * @param player_ptr プレイヤーへの参照ポインタ
65  * @param m_ptr ダメージを受けるモンスターの構造体参照ポインタ
66  * @param dam ダメージ基本値
67  * @param is_psy_spear 攻撃手段が光の剣ならばTRUE
68  * @return 修正を行った結果のダメージ量
69  * @details RES_ALL持ちはAC軽減後のダメージを1/100に補正する. 光の剣は無敵を無効化する. 一定確率で無敵は貫通できる.
70  */
71 int mon_damage_mod(PlayerType *player_ptr, MonsterEntity *m_ptr, int dam, bool is_psy_spear)
72 {
73     auto *r_ptr = &m_ptr->get_monrace();
74     if (r_ptr->resistance_flags.has(MonsterResistanceType::RESIST_ALL) && dam > 0) {
75         dam /= 100;
76         if ((dam == 0) && one_in_(3)) {
77             dam = 1;
78         }
79     }
80
81     auto &race_info = m_ptr->get_monrace();
82
83     if (race_info.special_flags.has(MonsterSpecialType::DIMINISH_MAX_DAMAGE)) {
84         race_info.r_special_flags.set(MonsterSpecialType::DIMINISH_MAX_DAMAGE);
85         if (dam > m_ptr->hp / 10) {
86             dam = std::max(m_ptr->hp / 10, m_ptr->maxhp * 7 / 500);
87             msg_format(_("%s^は致命的なダメージを抑えた!", "%s^ resisted a critical damage!"), monster_desc(player_ptr, m_ptr, 0).data());
88         }
89     }
90
91     if (!m_ptr->is_invulnerable()) {
92         return dam;
93     }
94
95     if (is_psy_spear) {
96         if (!player_ptr->effects()->blindness()->is_blind() && is_seen(player_ptr, m_ptr)) {
97             msg_print(_("バリアを切り裂いた!", "The barrier is penetrated!"));
98         }
99
100         return dam;
101     }
102
103     return one_in_(PENETRATE_INVULNERABILITY) ? dam : 0;
104 }
105
106 /*!
107  * @brief モンスターの時限ステータスを取得する
108  * @param floor_ptr 現在フロアへの参照ポインタ
109  * @return m_idx モンスターの参照ID
110  * @return mproc_type モンスターの時限ステータスID
111  * @return 残りターン値
112  */
113 int get_mproc_idx(FloorType *floor_ptr, MONSTER_IDX m_idx, int mproc_type)
114 {
115     const auto &cur_mproc_list = floor_ptr->mproc_list[mproc_type];
116     for (int i = floor_ptr->mproc_max[mproc_type] - 1; i >= 0; i--) {
117         if (cur_mproc_list[i] == m_idx) {
118             return i;
119         }
120     }
121
122     return -1;
123 }
124
125 /*!
126  * @brief モンスターの時限ステータスリストを追加する
127  * @param floor_ptr 現在フロアへの参照ポインタ
128  * @return m_idx モンスターの参照ID
129  * @return mproc_type 追加したいモンスターの時限ステータスID
130  */
131 void mproc_add(FloorType *floor_ptr, MONSTER_IDX m_idx, int mproc_type)
132 {
133     if (floor_ptr->mproc_max[mproc_type] < w_ptr->max_m_idx) {
134         floor_ptr->mproc_list[mproc_type][floor_ptr->mproc_max[mproc_type]++] = (int16_t)m_idx;
135     }
136 }
137
138 /*!
139  * @brief モンスターの時限ステータスリストを初期化する / Initialize monster process
140  * @param floor_ptr 現在フロアへの参照ポインタ
141  */
142 void mproc_init(FloorType *floor_ptr)
143 {
144     /* Reset "player_ptr->current_floor_ptr->mproc_max[]" */
145     for (int i = 0; i < MAX_MTIMED; i++) {
146         floor_ptr->mproc_max[i] = 0;
147     }
148
149     /* Process the monsters (backwards) */
150     for (MONSTER_IDX i = floor_ptr->m_max - 1; i >= 1; i--) {
151         auto *m_ptr = &floor_ptr->m_list[i];
152
153         /* Ignore "dead" monsters */
154         if (!m_ptr->is_valid()) {
155             continue;
156         }
157
158         for (int cmi = 0; cmi < MAX_MTIMED; cmi++) {
159             if (m_ptr->mtimed[cmi]) {
160                 mproc_add(floor_ptr, i, cmi);
161             }
162         }
163     }
164 }
165
166 /*!
167  * @brief モンスターの各種状態値を時間経過により更新するサブルーチン
168  * @param floor_ptr 現在フロアへの参照ポインタ
169  * @param m_idx モンスター参照ID
170  * @param mtimed_idx 更新するモンスターの時限ステータスID
171  */
172 static void process_monsters_mtimed_aux(PlayerType *player_ptr, MONSTER_IDX m_idx, int mtimed_idx)
173 {
174     auto &floor = *player_ptr->current_floor_ptr;
175     auto *m_ptr = &floor.m_list[m_idx];
176     switch (mtimed_idx) {
177     case MTIMED_CSLEEP: {
178         auto *r_ptr = &m_ptr->get_monrace();
179         auto is_wakeup = false;
180         if (m_ptr->cdis < MAX_MONSTER_SENSING) {
181             /* Handle "sensing radius" */
182             if (m_ptr->cdis <= (m_ptr->is_pet() ? ((r_ptr->aaf > MAX_PLAYER_SIGHT) ? MAX_PLAYER_SIGHT : r_ptr->aaf) : r_ptr->aaf)) {
183                 is_wakeup = true;
184             }
185
186             /* Handle "sight" and "aggravation" */
187             else if ((m_ptr->cdis <= MAX_PLAYER_SIGHT) && floor.has_los({ m_ptr->fy, m_ptr->fx })) {
188                 is_wakeup = true;
189             }
190         }
191
192         if (!is_wakeup) {
193             break;
194         }
195
196         auto notice = (uint32_t)randint0(1024);
197
198         /* Nightmare monsters are more alert */
199         if (ironman_nightmare) {
200             notice /= 2;
201         }
202
203         /* Hack -- See if monster "notices" player */
204         if ((notice * notice * notice) > csleep_noise) {
205             break;
206         }
207
208         /* Hack -- amount of "waking" */
209         /* Wake up faster near the player */
210         auto d = (m_ptr->cdis < MAX_MONSTER_SENSING / 2) ? (MAX_MONSTER_SENSING / m_ptr->cdis) : 1;
211
212         /* Hack -- amount of "waking" is affected by speed of player */
213         d = (d * speed_to_energy(player_ptr->pspeed)) / 10;
214         if (d < 0) {
215             d = 1;
216         }
217
218         /* Monster wakes up "a little bit" */
219
220         /* Still asleep */
221         if (!set_monster_csleep(player_ptr, m_idx, m_ptr->get_remaining_sleep() - d)) {
222             /* Notice the "not waking up" */
223             if (is_original_ap_and_seen(player_ptr, m_ptr)) {
224                 /* Hack -- Count the ignores */
225                 if (r_ptr->r_ignore < MAX_UCHAR) {
226                     r_ptr->r_ignore++;
227                 }
228             }
229
230             break;
231         }
232
233         /* Notice the "waking up" */
234         if (m_ptr->ml) {
235             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
236             msg_format(_("%s^が目を覚ました。", "%s^ wakes up."), m_name.data());
237         }
238
239         if (is_original_ap_and_seen(player_ptr, m_ptr)) {
240             /* Hack -- Count the wakings */
241             if (r_ptr->r_wake < MAX_UCHAR) {
242                 r_ptr->r_wake++;
243             }
244         }
245
246         break;
247     }
248
249     case MTIMED_FAST:
250         /* Reduce by one, note if expires */
251         if (set_monster_fast(player_ptr, m_idx, m_ptr->get_remaining_acceleration() - 1)) {
252             if (is_seen(player_ptr, m_ptr)) {
253                 const auto m_name = monster_desc(player_ptr, m_ptr, 0);
254                 msg_format(_("%s^はもう加速されていない。", "%s^ is no longer fast."), m_name.data());
255             }
256         }
257
258         break;
259
260     case MTIMED_SLOW:
261         /* Reduce by one, note if expires */
262         if (set_monster_slow(player_ptr, m_idx, m_ptr->get_remaining_deceleration() - 1)) {
263             if (is_seen(player_ptr, m_ptr)) {
264                 const auto m_name = monster_desc(player_ptr, m_ptr, 0);
265                 msg_format(_("%s^はもう減速されていない。", "%s^ is no longer slow."), m_name.data());
266             }
267         }
268
269         break;
270
271     case MTIMED_STUNNED: {
272         int rlev = m_ptr->get_monrace().level;
273
274         /* Recover from stun */
275         if (set_monster_stunned(player_ptr, m_idx, (randint0(10000) <= rlev * rlev) ? 0 : (m_ptr->get_remaining_stun() - 1))) {
276             /* Message if visible */
277             if (is_seen(player_ptr, m_ptr)) {
278                 const auto m_name = monster_desc(player_ptr, m_ptr, 0);
279                 msg_format(_("%s^は朦朧状態から立ち直った。", "%s^ is no longer stunned."), m_name.data());
280             }
281         }
282
283         break;
284     }
285
286     case MTIMED_CONFUSED: {
287         /* Reduce the confusion */
288         if (!set_monster_confused(player_ptr, m_idx, m_ptr->get_remaining_confusion() - randint1(m_ptr->get_monrace().level / 20 + 1))) {
289             break;
290         }
291
292         /* Message if visible */
293         if (is_seen(player_ptr, m_ptr)) {
294             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
295             msg_format(_("%s^は混乱から立ち直った。", "%s^ is no longer confused."), m_name.data());
296         }
297
298         break;
299     }
300
301     case MTIMED_MONFEAR: {
302         /* Reduce the fear */
303         if (!set_monster_monfear(player_ptr, m_idx, m_ptr->get_remaining_fear() - randint1(m_ptr->get_monrace().level / 20 + 1))) {
304             break;
305         }
306
307         /* Visual note */
308         if (is_seen(player_ptr, m_ptr)) {
309             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
310 #ifdef JP
311 #else
312             /* Acquire the monster possessive */
313             const auto m_poss = monster_desc(player_ptr, m_ptr, MD_PRON_VISIBLE | MD_POSSESSIVE);
314 #endif
315 #ifdef JP
316             msg_format("%s^は勇気を取り戻した。", m_name.data());
317 #else
318             msg_format("%s^ recovers %s courage.", m_name.data(), m_poss.data());
319 #endif
320         }
321
322         break;
323     }
324
325     case MTIMED_INVULNER: {
326         /* Reduce by one, note if expires */
327         if (!set_monster_invulner(player_ptr, m_idx, m_ptr->get_remaining_invulnerability() - 1, true)) {
328             break;
329         }
330
331         if (is_seen(player_ptr, m_ptr)) {
332             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
333             msg_format(_("%s^はもう無敵でない。", "%s^ is no longer invulnerable."), m_name.data());
334         }
335
336         break;
337     }
338     }
339 }
340
341 /*!
342  * @brief 全モンスターの各種状態値を時間経過により更新するメインルーチン
343  * @param mtimed_idx 更新するモンスターの時限ステータスID
344  * @param player_ptr プレイヤーへの参照ポインタ
345  * @details
346  * Process the counters of monsters (once per 10 game turns)\n
347  * These functions are to process monsters' counters same as player's.
348  */
349 void process_monsters_mtimed(PlayerType *player_ptr, int mtimed_idx)
350 {
351     auto *floor_ptr = player_ptr->current_floor_ptr;
352     const auto &cur_mproc_list = floor_ptr->mproc_list[mtimed_idx];
353
354     /* Hack -- calculate the "player noise" */
355     if (mtimed_idx == MTIMED_CSLEEP) {
356         csleep_noise = (1U << (30 - player_ptr->skill_stl));
357     }
358
359     /* Process the monsters (backwards) */
360     for (auto i = floor_ptr->mproc_max[mtimed_idx] - 1; i >= 0; i--) {
361         process_monsters_mtimed_aux(player_ptr, cur_mproc_list[i], mtimed_idx);
362     }
363 }
364
365 /*!
366  * @brief モンスターへの魔力消去処理
367  * @param player_ptr プレイヤーへの参照ポインタ
368  * @param m_idx 魔力消去を受けるモンスターの参照ID
369  */
370 void dispel_monster_status(PlayerType *player_ptr, MONSTER_IDX m_idx)
371 {
372     auto *m_ptr = &player_ptr->current_floor_ptr->m_list[m_idx];
373     const auto m_name = monster_desc(player_ptr, m_ptr, 0);
374     if (set_monster_invulner(player_ptr, m_idx, 0, true)) {
375         if (m_ptr->ml) {
376             msg_format(_("%sはもう無敵ではない。", "%s^ is no longer invulnerable."), m_name.data());
377         }
378     }
379
380     if (set_monster_fast(player_ptr, m_idx, 0)) {
381         if (m_ptr->ml) {
382             msg_format(_("%sはもう加速されていない。", "%s^ is no longer fast."), m_name.data());
383         }
384     }
385
386     if (set_monster_slow(player_ptr, m_idx, 0)) {
387         if (m_ptr->ml) {
388             msg_format(_("%sはもう減速されていない。", "%s^ is no longer slow."), m_name.data());
389         }
390     }
391 }
392
393 /*!
394  * @brief モンスターの経験値取得処理
395  * @param player_ptr プレイヤーへの参照ポインタ
396  * @param m_idx 経験値を得るモンスターの参照ID
397  * @param s_idx 撃破されたモンスター種族の参照ID
398  */
399 void monster_gain_exp(PlayerType *player_ptr, MONSTER_IDX m_idx, MonsterRaceId s_idx)
400 {
401     if (m_idx <= 0 || !MonsterRace(s_idx).is_valid()) {
402         return;
403     }
404
405     auto *floor_ptr = player_ptr->current_floor_ptr;
406     auto *m_ptr = &floor_ptr->m_list[m_idx];
407
408     if (!m_ptr->is_valid()) {
409         return;
410     }
411
412     auto *r_ptr = &m_ptr->get_monrace();
413     auto *s_ptr = &monraces_info[s_idx];
414
415     if (AngbandSystem::get_instance().is_phase_out() || (r_ptr->next_exp == 0)) {
416         return;
417     }
418
419     auto new_exp = s_ptr->mexp * s_ptr->level / (r_ptr->level + 2);
420     if (m_idx == player_ptr->riding) {
421         new_exp = (new_exp + 1) / 2;
422     }
423
424     if (!floor_ptr->dun_level) {
425         new_exp /= 5;
426     }
427
428     m_ptr->exp += new_exp;
429     if (m_ptr->mflag2.has(MonsterConstantFlagType::CHAMELEON)) {
430         return;
431     }
432
433     auto &rfu = RedrawingFlagsUpdater::get_instance();
434     if (m_ptr->exp < r_ptr->next_exp) {
435         if (m_idx == player_ptr->riding) {
436             rfu.set_flag(StatusRecalculatingFlag::BONUS);
437         }
438
439         return;
440     }
441
442     auto old_hp = m_ptr->hp;
443     auto old_maxhp = m_ptr->max_maxhp;
444     auto old_r_idx = m_ptr->r_idx;
445     auto old_sub_align = m_ptr->sub_align;
446
447     /* Hack -- Reduce the racial counter of previous monster */
448     m_ptr->get_real_monrace().cur_num--;
449
450     const auto m_name = monster_desc(player_ptr, m_ptr, 0);
451     m_ptr->r_idx = r_ptr->next_r_idx;
452
453     /* Count the monsters on the level */
454     m_ptr->get_real_monrace().cur_num++;
455
456     m_ptr->ap_r_idx = m_ptr->r_idx;
457     r_ptr = &m_ptr->get_monrace();
458
459     m_ptr->max_maxhp = r_ptr->misc_flags.has(MonsterMiscType::FORCE_MAXHP) ? maxroll(r_ptr->hdice, r_ptr->hside) : damroll(r_ptr->hdice, r_ptr->hside);
460     if (ironman_nightmare) {
461         auto hp = m_ptr->max_maxhp * 2;
462         m_ptr->max_maxhp = std::min(MONSTER_MAXHP, hp);
463     }
464
465     m_ptr->maxhp = m_ptr->max_maxhp;
466     m_ptr->hp = old_hp * m_ptr->maxhp / old_maxhp;
467
468     /* dealt damage is 0 at initial*/
469     m_ptr->dealt_damage = 0;
470
471     /* Extract the monster base speed */
472     m_ptr->mspeed = get_mspeed(floor_ptr, r_ptr);
473
474     /* Sub-alignment of a monster */
475     if (!m_ptr->is_pet() && r_ptr->kind_flags.has_none_of(alignment_mask)) {
476         m_ptr->sub_align = old_sub_align;
477     } else {
478         m_ptr->sub_align = SUB_ALIGN_NEUTRAL;
479         if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
480             m_ptr->sub_align |= SUB_ALIGN_EVIL;
481         }
482
483         if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
484             m_ptr->sub_align |= SUB_ALIGN_GOOD;
485         }
486     }
487
488     m_ptr->exp = 0;
489     if (m_ptr->is_pet() || m_ptr->ml) {
490         auto is_hallucinated = player_ptr->effects()->hallucination()->is_hallucinated();
491         if (!ignore_unview || player_can_see_bold(player_ptr, m_ptr->fy, m_ptr->fx)) {
492             if (is_hallucinated) {
493                 MonsterRaceInfo *hallucinated_race = nullptr;
494                 do {
495                     auto r_idx = MonsterRace::pick_one_at_random();
496                     hallucinated_race = &monraces_info[r_idx];
497                 } while (hallucinated_race->kind_flags.has(MonsterKindType::UNIQUE));
498                 auto mes_evolution = _("%sは%sに進化した。", "%s^ evolved into %s.");
499                 auto mes_degeneration = _("%sは%sに退化した。", "%s^ degenerated into %s.");
500                 auto mes = randint0(2) == 0 ? mes_evolution : mes_degeneration;
501                 msg_format(mes, m_name.data(), hallucinated_race->name.data());
502             } else {
503                 msg_format(_("%sは%sに進化した。", "%s^ evolved into %s."), m_name.data(), r_ptr->name.data());
504             }
505         }
506
507         if (!is_hallucinated) {
508             monraces_info[old_r_idx].r_can_evolve = true;
509         }
510
511         /* Now you feel very close to this pet. */
512         m_ptr->parent_m_idx = 0;
513     }
514
515     update_monster(player_ptr, m_idx, false);
516     lite_spot(player_ptr, m_ptr->fy, m_ptr->fx);
517
518     if (m_idx == player_ptr->riding) {
519         rfu.set_flag(StatusRecalculatingFlag::BONUS);
520     }
521 }