OSDN Git Service

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