OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / blue-magic / learnt-power-getter.cpp
1 /*!
2  * @file learnt-power-getter.cpp
3  * @brief 青魔法の処理実行定義
4  */
5
6 #include "blue-magic/learnt-power-getter.h"
7 #include "blue-magic/blue-magic-checker.h"
8 #include "blue-magic/learnt-info.h"
9 #include "core/asking-player.h"
10 #include "core/stuff-handler.h"
11 #include "core/window-redrawer.h"
12 #include "game-option/text-display-options.h"
13 #include "io/command-repeater.h"
14 #include "io/input-key-acceptor.h"
15 #include "io/input-key-requester.h"
16 #include "main/sound-of-music.h"
17 #include "mind/mind-blue-mage.h"
18 #include "monster-race/race-ability-flags.h"
19 #include "mspell/monster-power-table.h"
20 #include "player-base/player-class.h"
21 #include "player-info/bluemage-data-type.h"
22 #include "player/player-status-table.h"
23 #include "realm/realm-types.h"
24 #include "spell/spell-info.h"
25 #include "system/player-type-definition.h"
26 #include "term/screen-processor.h"
27 #include "timed-effect/player-stun.h"
28 #include "timed-effect/timed-effects.h"
29 #include "util/enum-converter.h"
30 #include "util/flag-group.h"
31 #include "util/int-char-converter.h"
32 #include "util/string-processor.h"
33 #include "view/display-messages.h"
34
35 #include <algorithm>
36 #include <iterator>
37 #include <optional>
38 #include <vector>
39
40 /*!
41  * @brief コマンド反復チェック
42  * @return 反復可能な青魔法があればそれを返す。なければ std::nullopt を返す。
43  */
44 static std::optional<MonsterAbilityType> check_blue_magic_repeat()
45 {
46     COMMAND_CODE code;
47     if (!repeat_pull(&code)) {
48         return std::nullopt;
49     }
50
51     if (auto spell = static_cast<MonsterAbilityType>(code);
52         monster_powers.find(spell) != monster_powers.end()) {
53         return spell;
54     }
55
56     return std::nullopt;
57 }
58
59 /*!
60  * @brief 青魔法のタイプをコマンドメニューにより選択する
61  *
62  * @return 選択した青魔法のタイプ
63  * 選択をキャンセルした場合は std::nullopt
64  */
65 static std::optional<BlueMagicType> select_blue_magic_type_by_menu()
66 {
67     auto menu_line = 1;
68     std::optional<BlueMagicType> type;
69
70     screen_save();
71
72     while (!type.has_value()) {
73         prt(format(_(" %s ボルト", " %s bolt"), (menu_line == 1) ? _("》", "> ") : "  "), 2, 14);
74         prt(format(_(" %s ボール", " %s ball"), (menu_line == 2) ? _("》", "> ") : "  "), 3, 14);
75         prt(format(_(" %s ブレス", " %s breath"), (menu_line == 3) ? _("》", "> ") : "  "), 4, 14);
76         prt(format(_(" %s 召喚", " %s sommoning"), (menu_line == 4) ? _("》", "> ") : "  "), 5, 14);
77         prt(format(_(" %s その他", " %s others"), (menu_line == 5) ? _("》", "> ") : "  "), 6, 14);
78         prt(_("どの種類の魔法を使いますか?", "use which type of magic? "), 0, 0);
79
80         auto choice = inkey();
81         switch (choice) {
82         case ESCAPE:
83         case 'z':
84         case 'Z':
85             screen_load();
86             return std::nullopt;
87             break;
88         case '2':
89         case 'j':
90         case 'J':
91             menu_line++;
92             break;
93         case '8':
94         case 'k':
95         case 'K':
96             menu_line += 4;
97             break;
98         case '\r':
99         case 'x':
100         case 'X':
101             type = i2enum<BlueMagicType>(menu_line);
102             break;
103         }
104
105         if (menu_line > 5) {
106             menu_line -= 5;
107         }
108     }
109
110     screen_load();
111
112     return type;
113 }
114
115 /*!
116  * @brief 青魔法のタイプを記号により選択する
117  *
118  * @return 選択した青魔法のタイプ
119  * 選択をキャンセルした場合は std::nullopt
120  */
121 static std::optional<BlueMagicType> select_blue_magic_kind_by_symbol()
122 {
123     auto candidate_desc = _("[A]ボルト, [B]ボール, [C]ブレス, [D]召喚, [E]その他:", "[A] bolt, [B] ball, [C] breath, [D] summoning, [E] others:");
124
125     while (true) {
126         char ch;
127         if (!get_com(candidate_desc, &ch, true)) {
128             return std::nullopt;
129         }
130
131         switch (ch) {
132         case 'A':
133         case 'a':
134             return BlueMagicType::BOLT;
135         case 'B':
136         case 'b':
137             return BlueMagicType::BALL;
138         case 'C':
139         case 'c':
140             return BlueMagicType::BREATH;
141         case 'D':
142         case 'd':
143             return BlueMagicType::SUMMON;
144         case 'E':
145         case 'e':
146             return BlueMagicType::OTHER;
147         default:
148             break;
149         }
150     }
151
152     return std::nullopt;
153 }
154
155 /*!
156  * @brief 指定したタイプの青魔法のリストを(覚えていないものも含め)返す
157  *
158  * @param bluemage_data 青魔道士の固有データへの参照
159  * @param type 青魔法のタイプ
160  * @return 指定したタイプの青魔法のリストを(覚えていないものも含め)返す
161  * 但し、そのタイプの魔法を1つも覚えていない場合は std::nullopt を返す
162  */
163 static std::optional<std::vector<MonsterAbilityType>> sweep_learnt_spells(const bluemage_data_type &bluemage_data, BlueMagicType type)
164 {
165     EnumClassFlagGroup<MonsterAbilityType> ability_flags;
166     set_rf_masks(ability_flags, type);
167
168     if (bluemage_data.learnt_blue_magics.has_none_of(ability_flags)) {
169         msg_print(_("その種類の魔法は覚えていない!", "You don't know any spell of this type."));
170         return std::nullopt;
171     }
172
173     std::vector<MonsterAbilityType> blue_magics;
174     EnumClassFlagGroup<MonsterAbilityType>::get_flags(ability_flags, std::back_inserter(blue_magics));
175
176     return blue_magics;
177 }
178
179 /*!
180  * @brief 入力されたキーに従いコマンドメニューで選択中の青魔法を切り替える
181  *
182  * @param key 入力されたキー
183  * @param menu_line 選択中の青魔法の行
184  * @param bluemage_data 青魔道士の固有データへの参照
185  * @param blue_magics 青魔法のリスト(覚えていないものも含まれているが、カーソル移動時に選択をスキップする)
186  * @return 選択確定キーが入力された場合は true、そうでなければ false
187  */
188 static bool switch_blue_magic_choice(char key, int &menu_line, const bluemage_data_type &bluemage_data, const std::vector<MonsterAbilityType> blue_magics)
189 {
190     const auto &learnt_blue_magics = bluemage_data.learnt_blue_magics;
191     const int blue_magics_count = blue_magics.size();
192
193     switch (key) {
194     case '8':
195     case 'k':
196     case 'K':
197         do {
198             menu_line += (blue_magics_count - 1);
199             if (menu_line > blue_magics_count) {
200                 menu_line -= blue_magics_count;
201             }
202         } while (learnt_blue_magics.has_not(blue_magics[menu_line - 1]));
203         return false;
204
205     case '2':
206     case 'j':
207     case 'J':
208         do {
209             menu_line++;
210             if (menu_line > blue_magics_count) {
211                 menu_line -= blue_magics_count;
212             }
213         } while (learnt_blue_magics.has_not(blue_magics[menu_line - 1]));
214         return false;
215
216     case '6':
217     case 'l':
218     case 'L':
219         menu_line = blue_magics_count;
220         while (learnt_blue_magics.has_not(blue_magics[menu_line - 1])) {
221             menu_line--;
222         }
223
224         return false;
225
226     case '4':
227     case 'h':
228     case 'H':
229         menu_line = 1;
230         while (learnt_blue_magics.has_not(blue_magics[menu_line - 1])) {
231             menu_line++;
232         }
233
234         return false;
235
236     case 'x':
237     case 'X':
238     case '\r':
239         return true;
240
241     default:
242         return false;
243     }
244 }
245
246 /*!
247  * @brief 青魔法の失敗率を計算する
248  *
249  * @param mp 失敗率を計算する青魔法に対応した monster_power 構造体への参照
250  * @param need_mana 青魔法を使うのに必要なMP
251  * @return int 失敗率(%)を返す
252  */
253 int calculate_blue_magic_failure_probability(PlayerType *player_ptr, const monster_power &mp, int need_mana)
254 {
255     auto chance = mp.fail;
256     if (player_ptr->lev > mp.level) {
257         chance -= 3 * (player_ptr->lev - mp.level);
258     } else {
259         chance += (mp.level - player_ptr->lev);
260     }
261
262     chance -= 3 * (adj_mag_stat[player_ptr->stat_index[A_INT]] - 1);
263     chance = mod_spell_chance_1(player_ptr, chance);
264     if (need_mana > player_ptr->csp) {
265         chance += 5 * (need_mana - player_ptr->csp);
266     }
267
268     PERCENTAGE minfail = adj_mag_fail[player_ptr->stat_index[A_INT]];
269     if (chance < minfail) {
270         chance = minfail;
271     }
272
273     auto player_stun = player_ptr->effects()->stun();
274     chance += player_stun->get_magic_chance_penalty();
275     if (chance > 95) {
276         chance = 95;
277     }
278
279     chance = mod_spell_chance_2(player_ptr, chance);
280
281     return chance;
282 }
283
284 /*!
285  * @brief 青魔法リストに表示する青魔法の先頭のヘッダを出力する
286  * 記号で選択する場合、" 記号)" が出力される
287  * コマンドメニューで選択する場合、選択中の青魔法にはカーソルが、そうでない青魔法には空白が出力される
288  *
289  * @param buf 出力するバッファ
290  * @param buf_size バッファのサイズ
291  * @param index 選択する青魔法のリスト上の番号
292  * @param menu_line コマンドメニューで選択する場合、選択中の行。記号で選択する場合は使用されない。
293  */
294 static void close_blue_magic_name(char *buf, size_t buf_size, int index, int menu_line)
295 {
296     if (!use_menu) {
297         snprintf(buf, buf_size, "  %c)", I2A(index));
298         return;
299     }
300
301     if (index == menu_line - 1) {
302         snprintf(buf, buf_size, _("  》", "  > "));
303     } else {
304         snprintf(buf, buf_size, "    ");
305     }
306 }
307
308 /*!
309  * @brief 使用できる青魔法のリストを表示する
310  *
311  * @param menu_line 選択中の青魔法の行
312  * @param bluemage_data 青魔道士の固有データへの参照
313  * @param blue_magics 青魔法のリスト(覚えていないものも含まれているが、覚えていないものは表示をスキップする)
314  */
315 static void describe_blue_magic_name(PlayerType *player_ptr, int menu_line, const bluemage_data_type &bluemage_data, const std::vector<MonsterAbilityType> &blue_magics)
316 {
317     constexpr TERM_LEN y_base = 1;
318     constexpr TERM_LEN x_base = 18;
319     prt("", y_base, x_base);
320     put_str(_("名前", "Name"), y_base, x_base + 5);
321     put_str(_("MP 失率 効果", "SP Fail Info"), y_base, x_base + 33);
322     for (auto i = 0U; i < blue_magics.size(); ++i) {
323         prt("", y_base + i + 1, x_base);
324         const auto &spell = blue_magics[i];
325         if (bluemage_data.learnt_blue_magics.has_not(spell)) {
326             continue;
327         }
328
329         const auto &mp = monster_powers.at(spell);
330         auto need_mana = mod_need_mana(player_ptr, mp.smana, 0, REALM_NONE);
331         auto chance = calculate_blue_magic_failure_probability(player_ptr, mp, need_mana);
332         char header[80];
333         close_blue_magic_name(header, sizeof(header), i, menu_line);
334         const auto info = learnt_info(player_ptr, spell);
335         const auto psi_desc = format("%s %-26s %3d %3d%%%s", header, mp.name, need_mana, chance, info.data());
336         prt(psi_desc, y_base + i + 1, x_base);
337     }
338
339     prt("", y_base + blue_magics.size() + 1, x_base);
340 }
341
342 /*!
343  * @brief 青魔法を唱えるか確認する
344  *
345  * @param spell 唱える青魔法
346  * @return 唱えるなら ture、キャンセルするなら false を返す
347  */
348 static bool confirm_cast_blue_magic(MonsterAbilityType spell)
349 {
350     char tmp_val[160];
351     (void)strnfmt(tmp_val, 78, _("%sの魔法を唱えますか?", "Use %s? "), monster_powers.at(spell).name);
352     return get_check(tmp_val);
353 }
354
355 /*!
356  * @brief 唱える青魔法を記号により選択する
357  *
358  * @param bluemage_data 青魔道士の固有データへの参照
359  * @param blue_magics 青魔法のリスト(覚えていないものも含まれているが、覚えていない物は候補に出ず選択できない)
360  * @return 選択した青魔法。選択をキャンセルした場合は std::nullopt
361  */
362 static std::optional<MonsterAbilityType> select_learnt_spells_by_symbol(PlayerType *player_ptr, const bluemage_data_type &bluemage_data, std::vector<MonsterAbilityType> spells)
363 {
364     char out_val[80];
365     (void)strnfmt(out_val, sizeof(out_val), _("(%c-%c, '*'で一覧, ESC) どの%sを唱えますか?", "(%c-%c, *=List, ESC=exit) Use which %s? "),
366         I2A(0), I2A(spells.size() - 1), _("魔法", "magic"));
367
368     bool first_show_list = always_show_list;
369     auto show_list = false;
370     std::optional<MonsterAbilityType> selected_spell;
371
372     while (!selected_spell.has_value()) {
373         char choice = 0;
374         if (!first_show_list && !get_com(out_val, &choice, true)) {
375             break;
376         }
377
378         if (first_show_list || (choice == ' ') || (choice == '*') || (choice == '?')) {
379             // 選択する青魔法一覧の表示/非表示切り替え
380             first_show_list = false;
381             show_list = !show_list;
382             if (show_list) {
383                 screen_save();
384                 describe_blue_magic_name(player_ptr, 0, bluemage_data, spells);
385             } else {
386                 screen_load();
387             }
388             continue;
389         }
390
391         auto confirm = isupper(choice) != 0;
392         uint index = A2I(tolower(choice));
393         if (spells.size() <= index || bluemage_data.learnt_blue_magics.has_not(spells[index])) {
394             bell();
395             continue;
396         }
397
398         if (confirm && !confirm_cast_blue_magic(spells[index])) {
399             continue;
400         }
401
402         selected_spell = spells[index];
403     }
404
405     if (show_list) {
406         screen_load();
407     }
408
409     return selected_spell;
410 }
411
412 /*!
413  * @brief 唱える青魔法をコマンドメニューにより選択する
414  *
415  * @param bluemage_data 青魔道士の固有データへの参照
416  * @param blue_magics 青魔法のリスト(覚えていないものも含まれているが、覚えていない物は候補に出ず選択できない)
417  * @return 選択した青魔法。選択をキャンセルした場合は std::nullopt
418  */
419 static std::optional<MonsterAbilityType> select_learnt_spells_by_menu(PlayerType *player_ptr, const bluemage_data_type &bluemage_data, std::vector<MonsterAbilityType> spells)
420 {
421     char out_val[80];
422     angband_strcpy(out_val, _("(ESC=中断) どの魔法を唱えますか?", "(ESC=exit) Use which magic? "), sizeof(out_val));
423
424     auto it = std::find_if(
425         spells.begin(), spells.end(), [&bluemage_data](const auto &spell) { return bluemage_data.learnt_blue_magics.has(spell); });
426     int menu_line = std::distance(spells.begin(), it) + 1;
427     std::optional<MonsterAbilityType> selected_spell;
428
429     screen_save();
430
431     while (!selected_spell.has_value()) {
432         describe_blue_magic_name(player_ptr, menu_line, bluemage_data, spells);
433
434         char choice;
435         if (!get_com(out_val, &choice, true) || choice == '0') {
436             break;
437         }
438
439         if (!switch_blue_magic_choice(choice, menu_line, bluemage_data, spells)) {
440             continue;
441         }
442
443         uint index = menu_line - 1;
444         if (spells.size() <= index || bluemage_data.learnt_blue_magics.has_not(spells[index])) {
445             bell();
446             continue;
447         }
448
449         selected_spell = spells[index];
450     }
451
452     screen_load();
453
454     return selected_spell;
455 }
456
457 /*!
458  * @brief 使用可能な青魔法を選択する /
459  * Allow user to choose a imitation.
460  * @param player_ptr プレイヤーへの参照ポインタ
461  * @param sn 選択したモンスター攻撃ID、キャンセルの場合-1、不正な選択の場合-2を返す
462  * @return 発動可能な魔法を選択した場合TRUE、キャンセル処理か不正な選択が行われた場合FALSEを返す。
463  * @details
464  * If a valid spell is chosen, saves it in '*sn' and returns TRUE\n
465  * If the user hits escape, returns FALSE, and set '*sn' to -1\n
466  * If there are no legal choices, returns FALSE, and sets '*sn' to -2\n
467  *\n
468  * The "prompt" should be "cast", "recite", or "study"\n
469  * The "known" should be TRUE for cast/pray, FALSE for study\n
470  *\n
471  * nb: This function has a (trivial) display bug which will be obvious\n
472  * when you run it. It's probably easy to fix but I haven't tried,\n
473  * sorry.\n
474  */
475 std::optional<MonsterAbilityType> get_learned_power(PlayerType *player_ptr)
476 {
477     auto bluemage_data = PlayerClass(player_ptr).get_specific_data<bluemage_data_type>();
478     if (!bluemage_data) {
479         return std::nullopt;
480     }
481
482     if (auto repeat_spell = check_blue_magic_repeat();
483         repeat_spell.has_value()) {
484         return repeat_spell;
485     }
486
487     auto type = (use_menu)
488                     ? select_blue_magic_type_by_menu()
489                     : select_blue_magic_kind_by_symbol();
490     if (!type.has_value()) {
491         return std::nullopt;
492     }
493
494     auto spells = sweep_learnt_spells(*bluemage_data, type.value());
495     if (!spells.has_value() || spells->empty()) {
496         return std::nullopt;
497     }
498
499     auto selected_spell = (use_menu)
500                               ? select_learnt_spells_by_menu(player_ptr, *bluemage_data, spells.value())
501                               : select_learnt_spells_by_symbol(player_ptr, *bluemage_data, spells.value());
502
503     player_ptr->window_flags |= PW_SPELL;
504     handle_stuff(player_ptr);
505
506     if (!selected_spell.has_value()) {
507         return std::nullopt;
508     }
509
510     repeat_push(static_cast<COMMAND_CODE>(selected_spell.value()));
511     return selected_spell;
512 }