OSDN Git Service

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