OSDN Git Service

Merge pull request #3569 from sikabane-works/release/3.0.0.88-alpha
[hengbandforosx/hengbandosx.git] / src / cmd-action / cmd-racial.cpp
1 /*
2  * @brief クラス、種族、突然変異に関するコマンド処理
3  * @author Hourier
4  * @date 2022/02/24
5  */
6
7 #include "cmd-action/cmd-racial.h"
8 #include "action/action-limited.h"
9 #include "action/mutation-execution.h"
10 #include "action/racial-execution.h"
11 #include "core/asking-player.h"
12 #include "core/window-redrawer.h"
13 #include "game-option/text-display-options.h"
14 #include "io/command-repeater.h"
15 #include "io/input-key-acceptor.h"
16 #include "io/input-key-requester.h"
17 #include "main/sound-of-music.h"
18 #include "mutation/mutation-flag-types.h"
19 #include "player-base/player-class.h"
20 #include "player-info/race-info.h"
21 #include "player-info/samurai-data-type.h"
22 #include "player-status/player-energy.h"
23 #include "player/attack-defense-types.h"
24 #include "player/player-damage.h"
25 #include "player/special-defense-types.h"
26 #include "racial/class-racial-switcher.h"
27 #include "racial/mutation-racial-selector.h"
28 #include "racial/race-racial-command-setter.h"
29 #include "racial/racial-util.h"
30 #include "status/action-setter.h"
31 #include "system/angband-exceptions.h"
32 #include "system/player-type-definition.h"
33 #include "system/redrawing-flags-updater.h"
34 #include "term/screen-processor.h"
35 #include "term/z-form.h"
36 #include "util/bit-flags-calculator.h"
37 #include "util/int-char-converter.h"
38 #include "view/display-messages.h"
39 #include "view/display-util.h"
40 #include <string>
41
42 constexpr auto RC_PAGE_SIZE = 18;
43
44 static void racial_power_display_cursor(rc_type *rc_ptr)
45 {
46     auto y = rc_ptr->menu_line % RC_PAGE_SIZE;
47     put_str(_(" 》 ", " >  "), 2 + y, 11);
48 }
49
50 static void racial_power_erase_cursor(rc_type *rc_ptr)
51 {
52     auto y = rc_ptr->menu_line % RC_PAGE_SIZE;
53     put_str(_("    ", "    "), 2 + y, 11);
54 }
55
56 /*!
57  * @brief レイシャルパワー一覧を表示
58  * @param player_ptr プレイヤー情報への参照ポインタ
59  * @param rc_ptr レイシャルパワー情報への参照ポインタ
60  */
61 static void racial_power_display_list(PlayerType *player_ptr, rc_type *rc_ptr)
62 {
63     TERM_LEN x = 11;
64     prt(_("                                   Lv   MP 失率 効果", "                                   Lv   MP Fail Effect"), 1, x);
65     auto y = 0;
66     for (; y < RC_PAGE_SIZE; y++) {
67         auto ctr = RC_PAGE_SIZE * rc_ptr->page + y;
68         if (ctr >= rc_ptr->power_count()) {
69             break;
70         }
71
72         std::string dummy;
73         if (use_menu) {
74             dummy = "    ";
75         } else {
76             char letter;
77             if (ctr < 26) {
78                 letter = I2A(ctr);
79             } else {
80                 letter = '0' + ctr - 26;
81             }
82
83             dummy = format(" %c) ", letter);
84         }
85
86         auto &rpi = rc_ptr->power_desc[ctr];
87         dummy.append(
88             format("%-30.30s %2d %4d %3d%% %s", rpi.racial_name.data(), rpi.min_level, rpi.cost, 100 - racial_chance(player_ptr, &rc_ptr->power_desc[ctr]),
89                 rpi.info.data())
90                 .data());
91
92         prt(dummy, 2 + y, x);
93     }
94
95     prt("", 2 + y, x);
96     if (use_menu) {
97         racial_power_display_cursor(rc_ptr);
98     }
99 }
100
101 /*!
102  * @brief レイシャルパワー選択用のプロンプトを作成する
103  * @param rc_ptr レイシャルパワー情報への参照ポインタ
104  */
105 static void racial_power_make_prompt(rc_type *rc_ptr)
106 {
107     concptr fmt;
108
109     if (rc_ptr->browse_mode) {
110         fmt = _(
111             "(特殊能力 %c-%c, '*':一覧, '/'で使用, ESCで中断) どの能力について知りますか?", "(Powers %c-%c, *=List. /=Use, ESC=exit) Browse which power? ");
112     } else {
113         fmt = _("(特殊能力 %c-%c, '*'で一覧, '/'で閲覧, ESCで中断) どの能力を使いますか?", "(Powers %c-%c, *=List, /=Browse, ESC=exit) Use which power? ");
114     }
115
116     (void)strnfmt(rc_ptr->out_val, 78, fmt, I2A(0), (rc_ptr->power_count() <= 26) ? I2A(rc_ptr->power_count() - 1) : '0' + rc_ptr->power_count() - 27);
117 }
118
119 /*!
120  * @brief レイシャルパワー選択用のカーソル位置を進める
121  * @param player_ptr プレイヤー情報への参照ポインタ
122  * @param rc_ptr レイシャルパワー情報への参照ポインタ
123  * @param i カーソル増分
124  */
125 static void racial_power_add_index(PlayerType *player_ptr, rc_type *rc_ptr, int i)
126 {
127     auto n = rc_ptr->menu_line + i;
128     if (i < -1 || i > 1) {
129         if (n < 0 || n >= rc_ptr->power_count()) {
130             return;
131         }
132     }
133
134     if (n < 0) {
135         n = rc_ptr->power_count() - 1;
136     }
137
138     if (n >= rc_ptr->power_count()) {
139         n = 0;
140     }
141
142     auto p = n / RC_PAGE_SIZE;
143     racial_power_erase_cursor(rc_ptr);
144     rc_ptr->menu_line = n;
145     if (rc_ptr->page != p) {
146         rc_ptr->page = p;
147         screen_load();
148         screen_save();
149         racial_power_display_list(player_ptr, rc_ptr);
150     } else {
151         racial_power_display_cursor(rc_ptr);
152     }
153 }
154
155 /*!
156  * @brief メニューによる選択のキーを処理する
157  * @param rc_ptr レイシャルパワー情報への参照ポインタ
158  * @return キャンセルならfalse、それ以外ならtrue
159  */
160 static bool racial_power_interpret_menu_keys(PlayerType *player_ptr, rc_type *rc_ptr)
161 {
162     switch (rc_ptr->choice) {
163     case '0':
164         return false;
165     case '8':
166     case 'k':
167     case 'K':
168         racial_power_add_index(player_ptr, rc_ptr, -1);
169         return true;
170     case '2':
171     case 'j':
172     case 'J':
173         racial_power_add_index(player_ptr, rc_ptr, 1);
174         return true;
175     case '6':
176     case 'l':
177     case 'L':
178         racial_power_add_index(player_ptr, rc_ptr, RC_PAGE_SIZE);
179         return true;
180     case '4':
181     case 'h':
182     case 'H':
183         racial_power_add_index(player_ptr, rc_ptr, 0 - RC_PAGE_SIZE);
184         return true;
185     case 'x':
186     case 'X':
187     case '\r':
188         rc_ptr->command_code = (COMMAND_CODE)rc_ptr->menu_line;
189         rc_ptr->is_chosen = true;
190         rc_ptr->ask = false;
191         return true;
192     case '/':
193         rc_ptr->browse_mode = !rc_ptr->browse_mode;
194         racial_power_make_prompt(rc_ptr);
195         return true;
196     default:
197         return true;
198     }
199 }
200
201 /*!
202  * @brief メニューからの選択決定を処理
203  * @param player_ptr プレイヤー情報への参照ポインタ
204  * @param rc_ptr レイシャルパワー情報への参照ポインタ
205  * @return キャンセルしたらfalse、それ以外ならtrue
206  */
207 static bool racial_power_select_by_menu(PlayerType *player_ptr, rc_type *rc_ptr)
208 {
209     if (!use_menu || rc_ptr->choice == ' ') {
210         return true;
211     }
212
213     if (!racial_power_interpret_menu_keys(player_ptr, rc_ptr)) {
214         return false;
215     }
216
217     if (rc_ptr->menu_line > rc_ptr->power_count()) {
218         rc_ptr->menu_line -= rc_ptr->power_count();
219     }
220
221     return true;
222 }
223
224 /*!
225  * @brief レイシャルパワーの選択を解釈
226  * @param player_ptr プレイヤー情報への参照ポインタ
227  * @param rc_ptr レイシャルパワー情報への参照ポインタ
228  * @return コマンド選択していたらtrue、していなかったらfalse
229  */
230 static bool racial_power_interpret_choise(PlayerType *player_ptr, rc_type *rc_ptr)
231 {
232     if (use_menu) {
233         return false;
234     }
235
236     if (rc_ptr->choice == ' ' || rc_ptr->choice == '*') {
237         rc_ptr->page++;
238         if (rc_ptr->page > rc_ptr->max_page) {
239             rc_ptr->page = 0;
240         }
241
242         screen_load();
243         screen_save();
244         racial_power_display_list(player_ptr, rc_ptr);
245         return false;
246     }
247
248     if (rc_ptr->choice == '/') {
249         rc_ptr->browse_mode = !rc_ptr->browse_mode;
250         racial_power_make_prompt(rc_ptr);
251         return false;
252     }
253
254     if (rc_ptr->choice == '?') {
255         return true;
256     }
257
258     return true;
259 }
260
261 static void decide_racial_command(rc_type *rc_ptr)
262 {
263     if (use_menu) {
264         return;
265     }
266
267     if (rc_ptr->choice == '\r' && rc_ptr->power_count() == 1) {
268         rc_ptr->choice = 'a';
269     }
270
271     if (!isalpha(rc_ptr->choice)) {
272         rc_ptr->ask = false;
273         rc_ptr->command_code = rc_ptr->choice - '0' + 26;
274         return;
275     }
276
277     rc_ptr->ask = (isupper(rc_ptr->choice));
278     if (rc_ptr->ask) {
279         rc_ptr->choice = (char)tolower(rc_ptr->choice);
280     }
281
282     rc_ptr->command_code = (islower(rc_ptr->choice) ? A2I(rc_ptr->choice) : -1);
283 }
284
285 static bool ask_invoke_racial_power(rc_type *rc_ptr)
286 {
287     if ((rc_ptr->command_code < 0) || (rc_ptr->command_code >= rc_ptr->power_count())) {
288         bell();
289         return false;
290     }
291
292     if (!rc_ptr->ask) {
293         return true;
294     }
295
296     char tmp_val[160];
297     (void)strnfmt(tmp_val, 78, _("%sを使いますか? ", "Use %s? "), rc_ptr->power_desc[rc_ptr->command_code].racial_name.data());
298     return input_check(tmp_val);
299 }
300
301 static void racial_power_display_explanation(PlayerType *player_ptr, rc_type *rc_ptr)
302 {
303     auto &rpi = rc_ptr->power_desc[rc_ptr->command_code];
304
305     term_erase(12, 21);
306     term_erase(12, 20);
307     term_erase(12, 19);
308     term_erase(12, 18);
309     term_erase(12, 17);
310     term_erase(12, 16);
311     display_wrap_around(rpi.text, 62, 17, 15);
312
313     prt(_("何かキーを押して下さい。", "Hit any key."), 0, 0);
314     (void)inkey();
315
316     screen_load();
317     screen_save();
318     racial_power_display_list(player_ptr, rc_ptr);
319     rc_ptr->is_chosen = false;
320 }
321
322 /*!
323  * @brief レイシャルパワー選択処理のメインループ
324  * @param player_ptr プレイヤー情報への参照ポインタ
325  * @param rc_ptr レイシャルパワー情報への参照ポインタ
326  * @return コマンド選択したらtrue、キャンセルしたらfalse
327  */
328 static bool racial_power_process_input(PlayerType *player_ptr, rc_type *rc_ptr)
329 {
330     rc_ptr->choice = (always_show_list || use_menu) ? ESCAPE : 1;
331
332     while (true) {
333         if (rc_ptr->choice == ESCAPE) {
334             rc_ptr->choice = ' ';
335         } else {
336             const auto choice = input_command(rc_ptr->out_val);
337             if (!choice.has_value()) {
338                 return false;
339             }
340
341             rc_ptr->choice = choice.value();
342         }
343
344         if (!racial_power_select_by_menu(player_ptr, rc_ptr)) {
345             return false;
346         }
347
348         if (!rc_ptr->is_chosen && racial_power_interpret_choise(player_ptr, rc_ptr)) {
349             decide_racial_command(rc_ptr);
350             if (ask_invoke_racial_power(rc_ptr)) {
351                 rc_ptr->is_chosen = true;
352             }
353         }
354
355         if (rc_ptr->is_chosen) {
356             if (rc_ptr->browse_mode) {
357                 racial_power_display_explanation(player_ptr, rc_ptr);
358             } else {
359                 break;
360             }
361         }
362     }
363
364     return true;
365 }
366
367 /*!
368  * @brief レイシャル/クラスパワー選択を処理
369  * @param player_ptr プレイヤー情報への参照ポインタ
370  * @param rc_ptr レイシャルパワー情報への参照ポインタ
371  * @return コマンド選択したらtrue、キャンセルしたらfalse
372  */
373 static bool racial_power_select_power(PlayerType *player_ptr, rc_type *rc_ptr)
374 {
375     if (repeat_pull(&rc_ptr->command_code) && rc_ptr->command_code >= 0 && rc_ptr->command_code < rc_ptr->power_count()) {
376         return true;
377     }
378
379     screen_save();
380     if (use_menu) {
381         racial_power_display_list(player_ptr, rc_ptr);
382     }
383
384     const auto is_selected = racial_power_process_input(player_ptr, rc_ptr);
385     screen_load();
386     if (!is_selected) {
387         return false;
388     }
389
390     repeat_push(rc_ptr->command_code);
391     return true;
392 }
393
394 /*!
395  * @brief レイシャルパワーの使用を試みる
396  * @param player_ptr プレイヤー情報への参照ポインタ
397  * @param rc_ptr レイシャルパワー情報への参照ポインタ
398  * @return レイシャルパワーの使用有無
399  */
400 static bool racial_power_cast_power(PlayerType *player_ptr, rc_type *rc_ptr)
401 {
402     auto *rpi_ptr = &rc_ptr->power_desc[rc_ptr->command_code];
403     switch (check_racial_level(player_ptr, rpi_ptr)) {
404     case RACIAL_SUCCESS:
405         if (rpi_ptr->number < 0) {
406             return exe_racial_power(player_ptr, rpi_ptr->number);
407         }
408
409         return exe_mutation_power(player_ptr, i2enum<PlayerMutationType>(rpi_ptr->number));
410     case RACIAL_FAILURE:
411         return true;
412     case RACIAL_CANCEL:
413         return false;
414     default:
415         THROW_EXCEPTION(std::logic_error, "Invalid racial level check!");
416     }
417 }
418
419 /*!
420  * @brief レイシャルパワーのコストを減らす
421  * @param player_ptr プレイヤー情報への参照ポインタ
422  * @param rc_ptr レイシャルパワー情報への参照ポインタ
423  * @return コストを減らしたらtrue、減らさなかったらfalse
424  * @details
425  * MPが足りない場合はHPを減らす。
426  * 戻り値はHP/MPの再描画が必要か判定するのに使用。
427  */
428 static bool racial_power_reduce_mana(PlayerType *player_ptr, rc_type *rc_ptr)
429 {
430     int racial_cost = rc_ptr->power_desc[rc_ptr->command_code].racial_cost;
431     if (racial_cost == 0) {
432         return false;
433     }
434
435     int actual_racial_cost = racial_cost / 2 + randint1(racial_cost / 2);
436     if (player_ptr->csp >= actual_racial_cost) {
437         player_ptr->csp -= actual_racial_cost;
438     } else {
439         actual_racial_cost -= player_ptr->csp;
440         player_ptr->csp = 0;
441         take_hit(player_ptr, DAMAGE_USELIFE, actual_racial_cost, _("過度の集中", "concentrating too hard"));
442     }
443
444     return true;
445 }
446
447 /*!
448  * @brief レイシャル・パワーコマンドのメインルーチン
449  * @param player_ptr プレイヤーへの参照ポインタ
450  */
451 void do_cmd_racial_power(PlayerType *player_ptr)
452 {
453     if (player_ptr->wild_mode) {
454         return;
455     }
456
457     PlayerEnergy energy(player_ptr);
458     if (cmd_limit_confused(player_ptr)) {
459         energy.reset_player_turn();
460         return;
461     }
462
463     PlayerClass(player_ptr).break_samurai_stance({ SamuraiStanceType::MUSOU, SamuraiStanceType::KOUKIJIN });
464     auto tmp_r = rc_type(player_ptr);
465     auto *rc_ptr = &tmp_r;
466     switch_class_racial(player_ptr, rc_ptr);
467     if (player_ptr->mimic_form != MimicKindType::NONE) {
468         set_mimic_racial_command(player_ptr, rc_ptr);
469     } else {
470         set_race_racial_command(player_ptr, rc_ptr);
471     }
472
473     select_mutation_racial(player_ptr, rc_ptr);
474     if (rc_ptr->power_count() == 0) {
475         msg_print(_("特殊能力はありません。", "You have no special powers."));
476         return;
477     }
478
479     rc_ptr->max_page = 1 + (rc_ptr->power_count() - 1) / RC_PAGE_SIZE;
480     rc_ptr->page = use_menu ? 0 : -1;
481     racial_power_make_prompt(rc_ptr);
482     auto should_cast = false;
483     if (racial_power_select_power(player_ptr, rc_ptr)) {
484         should_cast = racial_power_cast_power(player_ptr, rc_ptr);
485     }
486
487     if (!should_cast) {
488         energy.reset_player_turn();
489         return;
490     }
491
492     if (!racial_power_reduce_mana(player_ptr, rc_ptr)) {
493         return;
494     }
495
496     auto &rfu = RedrawingFlagsUpdater::get_instance();
497     const auto &flags_mwrf = {
498         MainWindowRedrawingFlag::HP,
499         MainWindowRedrawingFlag::MP,
500     };
501     rfu.set_flags(flags_mwrf);
502     static constexpr auto flags_swrf = {
503         SubWindowRedrawingFlag::PLAYER,
504         SubWindowRedrawingFlag::SPELL,
505     };
506     rfu.set_flags(flags_swrf);
507 }