OSDN Git Service

Merge pull request #3311 from habu1010/feature/implement-rand-choice-function-no...
[hengbandforosx/hengbandosx.git] / src / spell-realm / spells-hex.cpp
1 #include "spell-realm/spells-hex.h"
2 #include "core/asking-player.h"
3 #include "core/player-redraw-types.h"
4 #include "core/window-redrawer.h"
5 #include "effect/effect-characteristics.h"
6 #include "effect/effect-processor.h"
7 #include "monster-attack/monster-attack-player.h"
8 #include "monster-race/monster-race.h"
9 #include "player-base/player-class.h"
10 #include "player-info/spell-hex-data-type.h"
11 #include "player/attack-defense-types.h"
12 #include "player/player-skill.h"
13 #include "realm/realm-hex-numbers.h"
14 #include "spell-kind/spells-teleport.h"
15 #include "spell-realm/spells-crusade.h"
16 #include "spell-realm/spells-song.h"
17 #include "spell/spell-info.h"
18 #include "spell/spells-execution.h"
19 #include "spell/technic-info-table.h"
20 #include "status/action-setter.h"
21 #include "system/floor-type-definition.h"
22 #include "system/monster-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 "term/screen-processor.h"
27 #include "util/bit-flags-calculator.h"
28 #include "util/int-char-converter.h"
29 #include "view/display-messages.h"
30
31 #ifdef JP
32 #else
33 #include "monster/monster-describer.h"
34 #include "monster/monster-description-types.h"
35 #endif
36
37 #include <iterator>
38
39 /*!< 呪術の最大詠唱数 */
40 constexpr int MAX_KEEP = 4;
41
42 SpellHex::SpellHex(PlayerType *player_ptr)
43     : player_ptr(player_ptr)
44     , spell_hex_data(PlayerClass(player_ptr).get_specific_data<spell_hex_data_type>())
45 {
46     if (!this->spell_hex_data) {
47         return;
48     }
49
50     HexSpellFlagGroup::get_flags(this->spell_hex_data->casting_spells, std::back_inserter(this->casting_spells));
51
52     if (this->casting_spells.size() > MAX_KEEP) {
53         throw("Invalid numbers of hex magics keep!");
54     }
55 }
56
57 SpellHex::SpellHex(PlayerType *player_ptr, MonsterAttackPlayer *monap_ptr)
58     : player_ptr(player_ptr)
59     , monap_ptr(monap_ptr)
60 {
61 }
62
63 /*!
64  * @brief プレイヤーが詠唱中の全呪術を停止する
65  */
66 void SpellHex::stop_all_spells()
67 {
68     for (auto spell : this->casting_spells) {
69         exe_spell(this->player_ptr, REALM_HEX, spell, SpellProcessType::STOP);
70     }
71
72     this->spell_hex_data->casting_spells.clear();
73     if (this->player_ptr->action == ACTION_SPELL) {
74         set_action(this->player_ptr, ACTION_NONE);
75     }
76
77     auto &rfu = RedrawingFlagsUpdater::get_instance();
78     const auto flags_srf = {
79         StatusRedrawingFlag::BONUS,
80         StatusRedrawingFlag::HP,
81         StatusRedrawingFlag::MP,
82         StatusRedrawingFlag::SPELLS,
83     };
84     rfu.set_flags(flags_srf);
85     this->player_ptr->redraw |= PR_EXTRA | PR_HP | PR_MP;
86 }
87
88 /*!
89  * @brief プレイヤーが詠唱中の呪術から選択式で一つまたは全てを停止する
90  * @return 停止したらtrue、停止をキャンセルしたらfalse
91  */
92 bool SpellHex::stop_spells_with_selection()
93 {
94     if (!this->is_spelling_any()) {
95         msg_print(_("呪文を詠唱していません。", "You are not casting a spell."));
96         return false;
97     }
98
99     auto casting_num = this->get_casting_num();
100     if ((casting_num == 1) || (this->player_ptr->lev < 35)) {
101         this->stop_all_spells();
102         return true;
103     }
104
105     char out_val[160];
106     strnfmt(out_val, 78, _("どの呪文の詠唱を中断しますか?(呪文 %c-%c, 'l'全て, ESC)", "Which spell do you stop casting? (Spell %c-%c, 'l' to all, ESC)"),
107         I2A(0), I2A(casting_num - 1));
108     screen_save();
109     auto [is_all, is_selected, choice] = select_spell_stopping(out_val);
110     if (is_all) {
111         return true;
112     }
113
114     screen_load();
115     if (is_selected) {
116         auto n = this->casting_spells[A2I(choice)];
117         exe_spell(this->player_ptr, REALM_HEX, n, SpellProcessType::STOP);
118         this->reset_casting_flag(i2enum<spell_hex_type>(n));
119     }
120
121     auto &rfu = RedrawingFlagsUpdater::get_instance();
122     const auto flags_srf = {
123         StatusRedrawingFlag::BONUS,
124         StatusRedrawingFlag::HP,
125         StatusRedrawingFlag::MP,
126         StatusRedrawingFlag::SPELLS,
127     };
128     rfu.set_flags(flags_srf);
129     this->player_ptr->redraw |= PR_EXTRA | PR_HP | PR_MP;
130     return is_selected;
131 }
132
133 /*!
134  * @brief 中断する呪術を選択する
135  * @param out_val 呪文名
136  * @return
137  * Item1: 全ての呪文を中断するならばtrue、1つの呪文を中断するならばfalse
138  * Item2: 選択が完了したらtrue、キャンセルならばfalse
139  * Item3: 選択した呪文番号 (a~d、lの5択)
140  */
141 std::tuple<bool, bool, char> SpellHex::select_spell_stopping(char *out_val)
142 {
143     while (true) {
144         char choice = 0;
145         this->display_casting_spells_list();
146         if (!get_com(out_val, &choice, true)) {
147             return std::make_tuple(false, false, choice);
148         }
149
150         if (isupper(choice)) {
151             choice = static_cast<char>(tolower(choice));
152         }
153
154         if (choice == 'l') {
155             screen_load();
156             this->stop_all_spells();
157             return std::make_tuple(true, true, choice);
158         }
159
160         if ((choice < I2A(0)) || (choice > I2A(this->get_casting_num() - 1))) {
161             continue;
162         }
163
164         return std::make_tuple(false, true, choice);
165     }
166 }
167
168 void SpellHex::display_casting_spells_list()
169 {
170     constexpr auto y = 1;
171     constexpr auto x = 20;
172     auto n = 0;
173     term_erase(x, y, 255);
174     prt(_("     名前", "     Name"), y, x + 5);
175     for (auto spell : this->casting_spells) {
176         term_erase(x, y + n + 1, 255);
177         const auto spell_name = exe_spell(this->player_ptr, REALM_HEX, spell, SpellProcessType::NAME);
178         put_str(format("%c)  %s", I2A(n), spell_name->data()), y + n + 1, x + 2);
179         n++;
180     }
181 }
182
183 /*!
184  * @brief 一定時間毎に呪術で消費するMPを処理する
185  */
186 void SpellHex::decrease_mana()
187 {
188     if (!this->spell_hex_data) {
189         return;
190     }
191
192     if (this->spell_hex_data->casting_spells.none() && this->spell_hex_data->interrupting_spells.none()) {
193         return;
194     }
195
196     auto need_restart = this->check_restart();
197     if (this->player_ptr->anti_magic) {
198         this->stop_all_spells();
199         return;
200     }
201
202     if (!this->process_mana_cost(need_restart)) {
203         return;
204     }
205
206     this->gain_exp();
207     for (auto spell : this->casting_spells) {
208         exe_spell(this->player_ptr, REALM_HEX, spell, SpellProcessType::CONTNUATION);
209     }
210 }
211
212 /*!
213  * @brief 継続的な呪文の詠唱が可能な程度にMPが残っているか確認し、残量に応じて継続・中断を行う
214  * @param need_restart 詠唱を再開するか否か
215  * @return MPが足りているか否か
216  * @todo 64ビットの割り算をしなければいけない箇所には見えない. 調査の後不要ならば消すこと.
217  */
218 bool SpellHex::process_mana_cost(const bool need_restart)
219 {
220     auto need_mana = this->calc_need_mana();
221     uint need_mana_frac = 0;
222     s64b_div(&need_mana, &need_mana_frac, 0, 3); /* Divide by 3 */
223     need_mana += this->get_casting_num() - 1;
224
225     auto enough_mana = s64b_cmp(this->player_ptr->csp, this->player_ptr->csp_frac, need_mana, need_mana_frac) >= 0;
226     if (!enough_mana) {
227         this->stop_all_spells();
228         return false;
229     }
230
231     s64b_sub(&(this->player_ptr->csp), &(this->player_ptr->csp_frac), need_mana, need_mana_frac);
232     this->player_ptr->redraw |= PR_MP;
233     if (!need_restart) {
234         return true;
235     }
236
237     msg_print(_("詠唱を再開した。", "You restart casting."));
238     this->player_ptr->action = ACTION_SPELL;
239     auto &rfu = RedrawingFlagsUpdater::get_instance();
240     const auto flags_srf = {
241         StatusRedrawingFlag::BONUS,
242         StatusRedrawingFlag::HP,
243         StatusRedrawingFlag::MONSTER_STATUSES,
244     };
245     rfu.set_flags(flags_srf);
246     this->player_ptr->redraw |= PR_MAP | PR_TIMED_EFFECT | PR_ACTION;
247     this->player_ptr->window_flags |= PW_OVERHEAD | PW_DUNGEON;
248     return true;
249 }
250
251 bool SpellHex::check_restart()
252 {
253     if (this->spell_hex_data->interrupting_spells.none()) {
254         return false;
255     }
256
257     this->spell_hex_data->casting_spells = this->spell_hex_data->interrupting_spells;
258     this->spell_hex_data->interrupting_spells.clear();
259     return true;
260 }
261
262 int SpellHex::calc_need_mana()
263 {
264     auto need_mana = 0;
265     for (auto spell : this->casting_spells) {
266         const auto *s_ptr = &technic_info[REALM_HEX - MIN_TECHNIC][spell];
267         need_mana += mod_need_mana(this->player_ptr, s_ptr->smana, spell, REALM_HEX);
268     }
269
270     return need_mana;
271 }
272
273 void SpellHex::gain_exp()
274 {
275     PlayerSkill ps(player_ptr);
276     for (auto spell : this->casting_spells) {
277         if (!this->is_spelling_specific(spell)) {
278             continue;
279         }
280
281         ps.gain_continuous_spell_skill_exp(REALM_HEX, spell);
282     }
283 }
284
285 /*!
286  * @brief プレイヤーの呪術詠唱枠がすでに最大かどうかを返す
287  * @return すでに全枠を利用しているならTRUEを返す
288  */
289 bool SpellHex::is_casting_full_capacity() const
290 {
291     auto k_max = (this->player_ptr->lev / 15) + 1;
292     k_max = std::min(k_max, MAX_KEEP);
293     return this->get_casting_num() >= k_max;
294 }
295
296 /*!
297  * @brief 一定ゲームターン毎に復讐処理の残り期間の判定を行う
298  */
299 void SpellHex::continue_revenge()
300 {
301     if (!this->spell_hex_data || (this->get_revenge_turn() == 0)) {
302         return;
303     }
304
305     switch (this->get_revenge_type()) {
306     case SpellHexRevengeType::PATIENCE:
307         exe_spell(this->player_ptr, REALM_HEX, HEX_PATIENCE, SpellProcessType::CONTNUATION);
308         return;
309     case SpellHexRevengeType::REVENGE:
310         exe_spell(this->player_ptr, REALM_HEX, HEX_REVENGE, SpellProcessType::CONTNUATION);
311         return;
312     default:
313         return;
314     }
315 }
316
317 /*!
318  * @brief 復讐ダメージの追加を行う
319  * @param dam 蓄積されるダメージ量
320  */
321 void SpellHex::store_vengeful_damage(int dam)
322 {
323     if (!this->spell_hex_data || (this->get_revenge_turn() == 0)) {
324         return;
325     }
326
327     this->set_revenge_power(dam, false);
328 }
329
330 /*!
331  * @brief 呪術結界の判定
332  * @param m_idx 判定の対象となるモンスターID
333  * @return 呪術の効果が適用されるならTRUEを返す
334  * @details v3.0.0現在は反テレポート・反魔法・反増殖の3種類
335  */
336 bool SpellHex::check_hex_barrier(MONSTER_IDX m_idx, spell_hex_type type) const
337 {
338     const auto *m_ptr = &this->player_ptr->current_floor_ptr->m_list[m_idx];
339     const auto *r_ptr = &monraces_info[m_ptr->r_idx];
340     return this->is_spelling_specific(type) && ((this->player_ptr->lev * 3 / 2) >= randint1(r_ptr->level));
341 }
342
343 bool SpellHex::is_spelling_specific(int hex) const
344 {
345     return this->spell_hex_data && this->spell_hex_data->casting_spells.has(i2enum<spell_hex_type>(hex));
346 }
347
348 bool SpellHex::is_spelling_any() const
349 {
350     return this->spell_hex_data && (this->get_casting_num() > 0);
351 }
352
353 void SpellHex::interrupt_spelling()
354 {
355     this->spell_hex_data->interrupting_spells = this->spell_hex_data->casting_spells;
356     this->spell_hex_data->casting_spells.clear();
357 }
358
359 /*!
360  * @brief 呪術「目には目を」の効果処理
361  * @param this->player_ptr プレイヤーへの参照ポインタ
362  * @param monap_ptr モンスターからプレイヤーへの直接攻撃構造体への参照ポインタ
363  */
364 void SpellHex::eyes_on_eyes()
365 {
366     if (this->monap_ptr == nullptr) {
367         throw("Invalid constructor was used!");
368     }
369
370     const auto is_eyeeye_finished = (this->player_ptr->tim_eyeeye == 0) && !this->is_spelling_specific(HEX_EYE_FOR_EYE);
371     if (is_eyeeye_finished || (this->monap_ptr->get_damage == 0) || this->player_ptr->is_dead) {
372         return;
373     }
374
375 #ifdef JP
376     msg_format("攻撃が%s自身を傷つけた!", this->monap_ptr->m_name);
377 #else
378     const auto m_name_self = monster_desc(this->player_ptr, this->monap_ptr->m_ptr, MD_PRON_VISIBLE | MD_POSSESSIVE | MD_OBJECTIVE);
379     msg_format("The attack of %s has wounded %s!", this->monap_ptr->m_name, m_name_self.data());
380 #endif
381     const auto y = this->monap_ptr->m_ptr->fy;
382     const auto x = this->monap_ptr->m_ptr->fx;
383     project(this->player_ptr, 0, 0, y, x, this->monap_ptr->get_damage, AttributeType::MISSILE, PROJECT_KILL);
384     if (this->player_ptr->tim_eyeeye) {
385         set_tim_eyeeye(this->player_ptr, this->player_ptr->tim_eyeeye - 5, true);
386     }
387 }
388
389 void SpellHex::thief_teleport()
390 {
391     if (this->monap_ptr == nullptr) {
392         throw("Invalid constructor was used!");
393     }
394
395     if (!this->monap_ptr->blinked || !this->monap_ptr->alive || this->player_ptr->is_dead) {
396         return;
397     }
398
399     if (this->check_hex_barrier(this->monap_ptr->m_idx, HEX_ANTI_TELE)) {
400         msg_print(_("泥棒は笑って逃げ...ようとしたがバリアに防がれた。", "The thief flees laughing...? But a magic barrier obstructs it."));
401     } else {
402         msg_print(_("泥棒は笑って逃げた!", "The thief flees laughing!"));
403         teleport_away(this->player_ptr, this->monap_ptr->m_idx, MAX_PLAYER_SIGHT * 2 + 5, TELEPORT_SPONTANEOUS);
404     }
405 }
406
407 void SpellHex::set_casting_flag(spell_hex_type type)
408 {
409     this->spell_hex_data->casting_spells.set(type);
410 }
411
412 void SpellHex::reset_casting_flag(spell_hex_type type)
413 {
414     this->spell_hex_data->casting_spells.reset(type);
415 }
416
417 int32_t SpellHex::get_casting_num() const
418 {
419     return this->spell_hex_data->casting_spells.count();
420 }
421
422 int32_t SpellHex::get_revenge_power() const
423 {
424     return this->spell_hex_data->revenge_power;
425 }
426
427 void SpellHex::set_revenge_power(int32_t power, bool substitution)
428 {
429     if (substitution) {
430         this->spell_hex_data->revenge_power = power;
431     } else {
432         this->spell_hex_data->revenge_power += power;
433     }
434 }
435
436 byte SpellHex::get_revenge_turn() const
437 {
438     return this->spell_hex_data->revenge_turn;
439 }
440
441 /*!
442  * @brief 復讐の残りターンをセットするか、残りターン数を減らす
443  * @param turn 残りターン (非負整数であること)
444  * @param substitution セットならtrue、ターン減少ならfalse
445  */
446 void SpellHex::set_revenge_turn(byte turn, bool substitution)
447 {
448     if (substitution) {
449         this->spell_hex_data->revenge_turn = turn;
450     } else {
451         this->spell_hex_data->revenge_turn -= turn;
452     }
453 }
454
455 SpellHexRevengeType SpellHex::get_revenge_type() const
456 {
457     return this->spell_hex_data->revenge_type;
458 }
459
460 void SpellHex::set_revenge_type(SpellHexRevengeType type)
461 {
462     this->spell_hex_data->revenge_type = type;
463 }