OSDN Git Service

Merge pull request #1173 from Hourier/feature/Replace-Boolean-Macro
[hengbandforosx/hengbandosx.git] / src / mspell / mspell-selector.cpp
1 /*!
2  * @brief モンスターが詠唱する魔法を選択する処理
3  * @date 2020/07/23
4  * @author Hourier
5  * @details ba-no-ru/rupa-toの特殊処理はここで実施
6  */
7
8 #include "mspell/mspell-selector.h"
9 #include "floor/geometry.h"
10 #include "monster-race/monster-race.h"
11 #include "monster-race/race-flags2.h"
12 #include "monster-race/race-indice-types.h"
13 #include "monster/monster-status.h"
14 #include "mspell/mspell-attack-util.h"
15 #include "mspell/mspell-judgement.h"
16 #include "player/player-status.h"
17 #include "system/monster-type-definition.h"
18 #include "system/floor-type-definition.h"
19 #include "system/monster-race-definition.h"
20 #include "system/player-type-definition.h"
21 #include "world/world.h"
22
23 /*!
24  * @brief 指定したID値が指定した範囲内のIDかどうかを返す
25  *
26  * enum値に対して範囲で判定するのはあまり好ましくないが、歴史的経緯により仕方がない
27  *
28  * @param spell 判定対象のID
29  * @param start 範囲の開始ID
30  * @param end 範囲の終了ID(このIDも含む)
31  * @return IDが start <= spell <= end なら true、そうでなければ false
32  */
33 static bool spell_in_between(RF_ABILITY spell, RF_ABILITY start, RF_ABILITY end)
34 {
35     auto spell_int = static_cast<int>(spell);
36     return static_cast<int>(start) <= spell_int && spell_int <= static_cast<int>(end);
37 }
38
39 /*!
40  * @brief ID値が攻撃魔法のIDかどうかを返す /
41  * Return TRUE if a spell is good for hurting the player (directly).
42  * @param spell 判定対象のID
43  * @return 正しいIDならばTRUEを返す。
44  */
45 static bool spell_attack(RF_ABILITY spell)
46 {
47     /* All RF4 spells hurt (except for shriek and dispel) */
48     if (spell_in_between(spell, RF_ABILITY::ROCKET, RF_ABILITY::BR_DISI))
49         return true;
50
51     /* Various "ball" spells */
52     if (spell_in_between(spell, RF_ABILITY::BA_ACID, RF_ABILITY::BA_DARK))
53         return true;
54
55     /* "Cause wounds" and "bolt" spells */
56     if (spell_in_between(spell, RF_ABILITY::CAUSE_1, RF_ABILITY::MISSILE))
57         return true;
58
59     /* Hand of Doom */
60     if (spell == RF_ABILITY::HAND_DOOM)
61         return true;
62
63     /* Psycho-Spear */
64     if (spell == RF_ABILITY::PSY_SPEAR)
65         return true;
66
67     /* Doesn't hurt */
68     return false;
69 }
70
71 /*!
72  * @brief ID値が退避目的に適したモンスター魔法IDかどうかを返す /
73  * Return TRUE if a spell is good for escaping.
74  * @param spell 判定対象のID
75  * @return 適した魔法のIDならばTRUEを返す。
76  */
77 static bool spell_escape(RF_ABILITY spell)
78 {
79     /* Blink or Teleport */
80     if (spell == RF_ABILITY::BLINK || spell == RF_ABILITY::TPORT)
81         return true;
82
83     /* Teleport the player away */
84     if (spell == RF_ABILITY::TELE_AWAY || spell == RF_ABILITY::TELE_LEVEL)
85         return true;
86
87     /* Isn't good for escaping */
88     return false;
89 }
90
91 /*!
92  * @brief ID値が妨害目的に適したモンスター魔法IDかどうかを返す /
93  * Return TRUE if a spell is good for annoying the player.
94  * @param spell 判定対象のID
95  * @return 適した魔法のIDならばTRUEを返す。
96  */
97 static bool spell_annoy(RF_ABILITY spell)
98 {
99     /* Shriek */
100     if (spell == RF_ABILITY::SHRIEK)
101         return true;
102
103     /* Brain smash, et al (added curses) */
104     if (spell_in_between(spell, RF_ABILITY::DRAIN_MANA, RF_ABILITY::CAUSE_4))
105         return true;
106
107     /* Scare, confuse, blind, slow, paralyze */
108     if (spell_in_between(spell, RF_ABILITY::SCARE, RF_ABILITY::HOLD))
109         return true;
110
111     /* Teleport to */
112     if (spell == RF_ABILITY::TELE_TO)
113         return true;
114
115     /* Teleport level */
116     if (spell == RF_ABILITY::TELE_LEVEL)
117         return true;
118
119     /* Darkness, make traps, cause amnesia */
120     if (spell_in_between(spell, RF_ABILITY::TRAPS, RF_ABILITY::RAISE_DEAD))
121         return true;
122
123     /* Doesn't annoy */
124     return false;
125 }
126
127 /*!
128  * @brief ID値が召喚型のモンスター魔法IDかどうかを返す /
129  * Return TRUE if a spell is good for annoying the player.
130  * @param spell 判定対象のID
131  * @return 召喚型魔法のIDならばTRUEを返す。
132  */
133 static bool spell_summon(RF_ABILITY spell)
134 {
135     return spell_in_between(spell, RF_ABILITY::S_KIN, RF_ABILITY::S_UNIQUE);
136 }
137
138 /*!
139  * @brief ID値が死者復活処理かどうかを返す /
140  * Return TRUE if a spell is good for annoying the player.
141  * @param spell 判定対象のID
142  * @return 死者復活の処理ならばTRUEを返す。
143  */
144 static bool spell_raise(RF_ABILITY spell)
145 {
146     return spell == RF_ABILITY::RAISE_DEAD;
147 }
148
149 /*!
150  * @brief ID値が戦術的なモンスター魔法IDかどうかを返す /
151  * Return TRUE if a spell is good in a tactical situation.
152  * @param spell 判定対象のID
153  * @return 戦術的な魔法のIDならばTRUEを返す。
154  */
155 static bool spell_tactic(RF_ABILITY spell)
156 {
157     return spell == RF_ABILITY::BLINK;
158 }
159
160 /*!
161  * @brief ID値が無敵化するモンスター魔法IDかどうかを返す /
162  * Return TRUE if a spell makes invulnerable.
163  * @param spell 判定対象のID
164  * @return 召喚型魔法のIDならばTRUEを返す。
165  */
166 static bool spell_invulner(RF_ABILITY spell)
167 {
168     return spell == RF_ABILITY::INVULNER;
169 }
170
171 /*!
172  * @brief ID値が加速するモンスター魔法IDかどうかを返す /
173  * Return TRUE if a spell hastes.
174  * @param spell 判定対象のID
175  * @return 召喚型魔法のIDならばTRUEを返す。
176  */
177 static bool spell_haste(RF_ABILITY spell)
178 {
179     return spell == RF_ABILITY::HASTE;
180 }
181
182 /*!
183  * @brief ID値が時間停止を行うモンスター魔法IDかどうかを返す /
184  * Return TRUE if a spell world.
185  * @param spell 判定対象のID
186  * @return 時間停止魔法のIDならばTRUEを返す。
187  */
188 static bool spell_world(RF_ABILITY spell)
189 {
190     return spell == RF_ABILITY::WORLD;
191 }
192
193 /*!
194  * @brief ID値が特別効果のモンスター魔法IDかどうかを返す /
195  * Return TRUE if a spell special.
196  * @param target_ptr プレーヤーへの参照ポインタ
197  * @param spell 判定対象のID
198  * @return 特別効果魔法のIDならばTRUEを返す。
199  */
200 static bool spell_special(player_type *target_ptr, RF_ABILITY spell)
201 {
202     if (target_ptr->phase_out)
203         return false;
204
205     return spell == RF_ABILITY::SPECIAL;
206 }
207
208 /*!
209  * @brief ID値が光の剣のモンスター魔法IDかどうかを返す /
210  * Return TRUE if a spell psycho-spear.
211  * @param spell 判定対象のID
212  * @return 光の剣のIDならばTRUEを返す。
213  */
214 static bool spell_psy_spe(RF_ABILITY spell)
215 {
216     return spell == RF_ABILITY::PSY_SPEAR;
217 }
218
219 /*!
220  * @brief ID値が治癒魔法かどうかを返す /
221  * Return TRUE if a spell is good for healing.
222  * @param spell 判定対象のID
223  * @return 治癒魔法のIDならばTRUEを返す。
224  */
225 static bool spell_heal(RF_ABILITY spell)
226 {
227     return spell == RF_ABILITY::HEAL;
228 }
229
230 /*!
231  * @brief ID値が魔力消去かどうかを返す /
232  * Return TRUE if a spell is good for dispel.
233  * @param spell 判定対象のID
234  * @return 魔力消去のIDならばTRUEを返す。
235  */
236 static bool spell_dispel(RF_ABILITY spell)
237 {
238     return spell == RF_ABILITY::DISPEL;
239 }
240
241 /*!
242  * @brief モンスターの魔法選択ルーチン
243  * Have a monster choose a spell from a list of "useful" spells.
244  * @param target_ptr プレーヤーへの参照ポインタ
245  * @param m_idx モンスターの構造体配列ID
246  * @param spells 候補魔法IDをまとめた配列
247  * @param num spellsの長さ
248  * @return 選択したモンスター魔法のID
249  * @details
250  * Note that this list does NOT include spells that will just hit\n
251  * other monsters, and the list is restricted when the monster is\n
252  * "desperate".  Should that be the job of this function instead?\n
253  *\n
254  * Stupid monsters will just pick a spell randomly.  Smart monsters\n
255  * will choose more "intelligently".\n
256  *\n
257  * Use the helper functions above to put spells into categories.\n
258  *\n
259  * This function may well be an efficiency bottleneck.\n
260  * @todo 長過ぎる。切り分けが必要
261  */
262 RF_ABILITY choose_attack_spell(player_type *target_ptr, msa_type *msa_ptr)
263 {
264     std::vector<RF_ABILITY> escape;
265     std::vector<RF_ABILITY> attack;
266     std::vector<RF_ABILITY> summon;
267     std::vector<RF_ABILITY> tactic;
268     std::vector<RF_ABILITY> annoy;
269     std::vector<RF_ABILITY> invul;
270     std::vector<RF_ABILITY> haste;
271     std::vector<RF_ABILITY> world;
272     std::vector<RF_ABILITY> special;
273     std::vector<RF_ABILITY> psy_spe;
274     std::vector<RF_ABILITY> raise;
275     std::vector<RF_ABILITY> heal;
276     std::vector<RF_ABILITY> dispel;
277
278     monster_type *m_ptr = &target_ptr->current_floor_ptr->m_list[msa_ptr->m_idx];
279     monster_race *r_ptr = &r_info[m_ptr->r_idx];
280     if (r_ptr->flags2 & RF2_STUPID)
281         return (msa_ptr->mspells[randint0(msa_ptr->mspells.size())]);
282
283     for (size_t i = 0; i < msa_ptr->mspells.size(); i++) {
284         if (spell_escape(msa_ptr->mspells[i]))
285             escape.push_back(msa_ptr->mspells[i]);
286
287         if (spell_attack(msa_ptr->mspells[i]))
288             attack.push_back(msa_ptr->mspells[i]);
289
290         if (spell_summon(msa_ptr->mspells[i]))
291             summon.push_back(msa_ptr->mspells[i]);
292
293         if (spell_tactic(msa_ptr->mspells[i]))
294             tactic.push_back(msa_ptr->mspells[i]);
295
296         if (spell_annoy(msa_ptr->mspells[i]))
297             annoy.push_back(msa_ptr->mspells[i]);
298
299         if (spell_invulner(msa_ptr->mspells[i]))
300             invul.push_back(msa_ptr->mspells[i]);
301
302         if (spell_haste(msa_ptr->mspells[i]))
303             haste.push_back(msa_ptr->mspells[i]);
304
305         if (spell_world(msa_ptr->mspells[i]))
306             world.push_back(msa_ptr->mspells[i]);
307
308         if (spell_special(target_ptr, msa_ptr->mspells[i]))
309             special.push_back(msa_ptr->mspells[i]);
310
311         if (spell_psy_spe(msa_ptr->mspells[i]))
312             psy_spe.push_back(msa_ptr->mspells[i]);
313
314         if (spell_raise(msa_ptr->mspells[i]))
315             raise.push_back(msa_ptr->mspells[i]);
316
317         if (spell_heal(msa_ptr->mspells[i]))
318             heal.push_back(msa_ptr->mspells[i]);
319
320         if (spell_dispel(msa_ptr->mspells[i]))
321             dispel.push_back(msa_ptr->mspells[i]);
322     }
323
324     if (!world.empty() && (randint0(100) < 15) && !current_world_ptr->timewalk_m_idx)
325         return (world[randint0(world.size())]);
326
327     if (!special.empty()) {
328         bool success = false;
329         switch (m_ptr->r_idx) {
330         case MON_BANOR:
331         case MON_LUPART:
332             if ((m_ptr->hp < m_ptr->maxhp / 2) && r_info[MON_BANOR].max_num && r_info[MON_LUPART].max_num)
333                 success = true;
334             break;
335         default:
336             break;
337         }
338
339         if (success)
340             return (special[randint0(special.size())]);
341     }
342
343     if (m_ptr->hp < m_ptr->maxhp / 3 && one_in_(2)) {
344         if (!heal.empty())
345             return (heal[randint0(heal.size())]);
346     }
347
348     if (((m_ptr->hp < m_ptr->maxhp / 3) || monster_fear_remaining(m_ptr)) && one_in_(2)) {
349         if (!escape.empty())
350             return (escape[randint0(escape.size())]);
351     }
352
353     if (!special.empty()) {
354         bool success = false;
355         switch (m_ptr->r_idx) {
356         case MON_OHMU:
357         case MON_BANOR:
358         case MON_LUPART:
359             break;
360         case MON_BANORLUPART:
361             if (randint0(100) < 70)
362                 success = true;
363             break;
364         case MON_ROLENTO:
365             if (randint0(100) < 40)
366                 success = true;
367             break;
368         default:
369             if (randint0(100) < 50)
370                 success = true;
371             break;
372         }
373         if (success)
374             return (special[randint0(special.size())]);
375     }
376
377     if ((distance(target_ptr->y, target_ptr->x, m_ptr->fy, m_ptr->fx) < 4) && (!attack.empty() || r_ptr->ability_flags.has(RF_ABILITY::TRAPS)) && (randint0(100) < 75)
378         && !current_world_ptr->timewalk_m_idx) {
379         if (!tactic.empty())
380             return (tactic[randint0(tactic.size())]);
381     }
382
383     if (!summon.empty() && (randint0(100) < 40))
384         return (summon[randint0(summon.size())]);
385
386     if (!dispel.empty() && one_in_(2)) {
387         if (dispel_check(target_ptr, msa_ptr->m_idx)) {
388             return (dispel[randint0(dispel.size())]);
389         }
390     }
391
392     if (!raise.empty() && (randint0(100) < 40))
393         return (raise[randint0(raise.size())]);
394
395     if (is_invuln(target_ptr)) {
396         if (!psy_spe.empty() && (randint0(100) < 50)) {
397             return (psy_spe[randint0(psy_spe.size())]);
398         } else if (!attack.empty() && (randint0(100) < 40)) {
399             return (attack[randint0(attack.size())]);
400         }
401     } else if (!attack.empty() && (randint0(100) < 85)) {
402         return (attack[randint0(attack.size())]);
403     }
404
405     if (!tactic.empty() && (randint0(100) < 50) && !current_world_ptr->timewalk_m_idx)
406         return (tactic[randint0(tactic.size())]);
407
408     if (!invul.empty() && !m_ptr->mtimed[MTIMED_INVULNER] && (randint0(100) < 50))
409         return (invul[randint0(invul.size())]);
410
411     if ((m_ptr->hp < m_ptr->maxhp * 3 / 4) && (randint0(100) < 25)) {
412         if (!heal.empty())
413             return (heal[randint0(heal.size())]);
414     }
415
416     if (!haste.empty() && (randint0(100) < 20) && !monster_fast_remaining(m_ptr))
417         return (haste[randint0(haste.size())]);
418
419     if (!annoy.empty() && (randint0(100) < 80))
420         return (annoy[randint0(annoy.size())]);
421
422     return RF_ABILITY::MAX;
423 }