OSDN Git Service

9eab51d299154ec2af62810d07107c4615a12989
[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<RF_ABILITY> 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<RF_ABILITY>(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<RF_ABILITY>> sweep_learnt_spells(const bluemage_data_type &bluemage_data, BlueMagicType type)
164 {
165     EnumClassFlagGroup<RF_ABILITY> 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<RF_ABILITY> blue_magics;
174     EnumClassFlagGroup<RF_ABILITY>::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<RF_ABILITY> 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 static int calculate_blue_magic_failure_probability(player_type *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(player_type *player_ptr, int menu_line, const bluemage_data_type &bluemage_data, const std::vector<RF_ABILITY> &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 comment[80];
333         learnt_info(player_ptr, comment, spell);
334         char psi_desc[80];
335         close_blue_magic_name(psi_desc, sizeof(psi_desc), i, menu_line);
336         angband_strcat(psi_desc, format(" %-26s %3d %3d%%%s", mp.name, need_mana, chance, comment), sizeof(psi_desc));
337         prt(psi_desc, y_base + i + 1, x_base);
338     }
339
340     prt("", y_base + blue_magics.size() + 1, x_base);
341 }
342
343 /*!
344  * @brief 青魔法を唱えるか確認する
345  *
346  * @param spell 唱える青魔法
347  * @return 唱えるなら ture、キャンセルするなら false を返す
348  */
349 static bool confirm_cast_blue_magic(RF_ABILITY spell)
350 {
351     char tmp_val[160];
352     (void)strnfmt(tmp_val, 78, _("%sの魔法を唱えますか?", "Use %s? "), monster_powers.at(spell).name);
353     return get_check(tmp_val);
354 }
355
356 /*!
357  * @brief 唱える青魔法を記号により選択する
358  *
359  * @param bluemage_data 青魔道士の固有データへの参照
360  * @param blue_magics 青魔法のリスト(覚えていないものも含まれているが、覚えていない物は候補に出ず選択できない)
361  * @return 選択した青魔法。選択をキャンセルした場合は std::nullopt
362  */
363 static std::optional<RF_ABILITY> select_learnt_spells_by_symbol(player_type *player_ptr, const bluemage_data_type &bluemage_data, std::vector<RF_ABILITY> spells)
364 {
365     char out_val[80];
366     (void)strnfmt(out_val, sizeof(out_val), _("(%c-%c, '*'で一覧, ESC) どの%sを唱えますか?", "(%c-%c, *=List, ESC=exit) Use which %s? "),
367         I2A(0), I2A(spells.size() - 1), _("魔法", "magic"));
368
369     bool first_show_list = always_show_list;
370     auto show_list = false;
371     std::optional<RF_ABILITY> selected_spell;
372
373     while (!selected_spell.has_value()) {
374         char choice;
375         if (!first_show_list && !get_com(out_val, &choice, true)) {
376             break;
377         }
378
379         if (first_show_list || (choice == ' ') || (choice == '*') || (choice == '?')) {
380             // 選択する青魔法一覧の表示/非表示切り替え
381             first_show_list = false;
382             show_list = !show_list;
383             if (show_list) {
384                 screen_save();
385                 describe_blue_magic_name(player_ptr, 0, bluemage_data, spells);
386             } else {
387                 screen_load();
388             }
389             continue;
390         }
391
392         auto confirm = isupper(choice) != 0;
393         uint index = A2I(tolower(choice));
394         if (spells.size() <= index || bluemage_data.learnt_blue_magics.has_not(spells[index])) {
395             bell();
396             continue;
397         }
398
399         if (confirm && !confirm_cast_blue_magic(spells[index])) {
400             continue;
401         }
402
403         selected_spell = spells[index];
404     }
405
406     if (show_list) {
407         screen_load();
408     }
409
410     return selected_spell;
411 }
412
413 /*!
414  * @brief 唱える青魔法をコマンドメニューにより選択する
415  *
416  * @param bluemage_data 青魔道士の固有データへの参照
417  * @param blue_magics 青魔法のリスト(覚えていないものも含まれているが、覚えていない物は候補に出ず選択できない)
418  * @return 選択した青魔法。選択をキャンセルした場合は std::nullopt
419  */
420 static std::optional<RF_ABILITY> select_learnt_spells_by_menu(player_type *player_ptr, const bluemage_data_type &bluemage_data, std::vector<RF_ABILITY> spells)
421 {
422     char out_val[80];
423     angband_strcpy(out_val, _("(ESC=中断) どの魔法を唱えますか?", "(ESC=exit) Use which magic? "), sizeof(out_val));
424
425     auto it = std::find_if(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<RF_ABILITY> 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<RF_ABILITY> get_learned_power(player_type *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 }