OSDN Git Service

Merge pull request #3532 from sikabane-works/release/3.0.0.87-alpha
[hengbandforosx/hengbandosx.git] / src / cmd-item / cmd-magiceat.cpp
1 /*!
2  * @brief プレイヤーのアイテムに関するコマンドの実装2 / Spell/Prayer commands
3  * @date 2014/01/27
4  * @author
5  * <pre>
6  * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
7  * This software may be copied and distributed for educational, research,
8  * and not for profit purposes provided that this copyright and statement
9  * are included in all such copies.  Other copyrights may also apply.
10  * 2014 Deskull rearranged comment for Doxygen.\n
11  * </pre>
12  * @details
13  * <pre>
14  * This file includes code for eating food, drinking potions,
15  * reading scrolls, aiming wands, using staffs, zapping rods,
16  * and activating artifacts.
17  *
18  * In all cases, if the player becomes "aware" of the item's use
19  * by testing it, mark it as "aware" and reward some experience
20  * based on the object's level, always rounding up.  If the player
21  * remains "unaware", mark that object "kind" as "tried".
22  *
23  * This code now correctly handles the unstacking of wands, staffs,
24  * and rods.  Note the overly paranoid warning about potential pack
25  * overflow, which allows the player to use and drop a stacked item.
26  *
27  * In all "unstacking" scenarios, the "used" object is "carried" as if
28  * the player had just picked it up.  In particular, this means that if
29  * the use of an item induces pack overflow, that item will be dropped.
30  *
31  * For simplicity, these routines induce a full "pack reorganization"
32  * which not only combines similar items, but also reorganizes various
33  * items to obey the current "sorting" method.  This may require about
34  * 400 item comparisons, but only occasionally.
35  *
36  * There may be a BIG problem with any "effect" that can cause "changes"
37  * to the inventory.  For example, a "scroll of recharging" can cause
38  * a wand/staff to "disappear", moving the inventory up.  Luckily, the
39  * scrolls all appear BEFORE the staffs/wands, so this is not a problem.
40  * But, for example, a "staff of recharging" could cause MAJOR problems.
41  * In such a case, it will be best to either (1) "postpone" the effect
42  * until the end of the function, or (2) "change" the effect, say, into
43  * giving a staff "negative" charges, or "turning a staff into a stick".
44  * It seems as though a "rod of recharging" might in fact cause problems.
45  * The basic problem is that the act of recharging (and destroying) an
46  * item causes the inducer of that action to "move", causing "o_ptr" to
47  * no longer point at the correct item, with horrifying results.
48  *
49  * Note that food/potions/scrolls no longer use bit-flags for effects,
50  * but instead use the "sval" (which is also used to sort the objects).
51  * </pre>
52  */
53
54 #include "cmd-item/cmd-magiceat.h"
55 #include "action/action-limited.h"
56 #include "avatar/avatar.h"
57 #include "cmd-item/cmd-usestaff.h"
58 #include "cmd-item/cmd-zaprod.h"
59 #include "cmd-item/cmd-zapwand.h"
60 #include "core/asking-player.h"
61 #include "game-option/disturbance-options.h"
62 #include "game-option/text-display-options.h"
63 #include "io/command-repeater.h"
64 #include "io/input-key-acceptor.h"
65 #include "io/input-key-requester.h"
66 #include "main/sound-definitions-table.h"
67 #include "main/sound-of-music.h"
68 #include "object/object-kind-hook.h"
69 #include "player-base/player-class.h"
70 #include "player-info/class-info.h"
71 #include "player-info/magic-eater-data-type.h"
72 #include "player-status/player-energy.h"
73 #include "player/player-status-table.h"
74 #include "spell/spell-info.h"
75 #include "system/baseitem-info.h"
76 #include "system/player-type-definition.h"
77 #include "target/target-getter.h"
78 #include "term/screen-processor.h"
79 #include "term/term-color-types.h"
80 #include "term/z-form.h"
81 #include "timed-effect/player-stun.h"
82 #include "timed-effect/timed-effects.h"
83 #include "util/int-char-converter.h"
84 #include "view/display-messages.h"
85 #include "view/display-util.h"
86 #include <algorithm>
87 #include <optional>
88
89 static std::optional<BaseitemKey> check_magic_eater_spell_repeat(magic_eater_data_type *magic_eater_data)
90 {
91     COMMAND_CODE sn;
92     if (!repeat_pull(&sn)) {
93         return std::nullopt;
94     }
95
96     auto tval = ItemKindType::NONE;
97     if (EATER_STAFF_BASE <= sn && sn < EATER_STAFF_BASE + EATER_ITEM_GROUP_SIZE) {
98         tval = ItemKindType::STAFF;
99     } else if (EATER_WAND_BASE <= sn && sn < EATER_WAND_BASE + EATER_ITEM_GROUP_SIZE) {
100         tval = ItemKindType::WAND;
101     } else if (EATER_ROD_BASE <= sn && sn < EATER_ROD_BASE + EATER_ITEM_GROUP_SIZE) {
102         tval = ItemKindType::ROD;
103     }
104
105     const auto &item_group = magic_eater_data->get_item_group(tval);
106     auto sval = sn % EATER_ITEM_GROUP_SIZE;
107     if (sval >= static_cast<int>(item_group.size())) {
108         return std::nullopt;
109     }
110
111     auto &item = item_group[sval];
112     /* Verify the spell */
113     switch (tval) {
114     case ItemKindType::ROD:
115         if (item.charge <= baseitems_info[lookup_baseitem_id({ ItemKindType::ROD, sval })].pval * (item.count - 1) * EATER_ROD_CHARGE) {
116             return BaseitemKey(tval, sval);
117         }
118         break;
119     case ItemKindType::STAFF:
120     case ItemKindType::WAND:
121         if (item.charge >= EATER_CHARGE) {
122             return BaseitemKey(tval, sval);
123         }
124         break;
125     default:
126         break;
127     }
128
129     return std::nullopt;
130 }
131
132 /*!
133  * @brief 魔道具術師の取り込んだ魔力一覧から選択/閲覧する /
134  * @param only_browse 閲覧するだけならばTRUE
135  * @return 選択したアイテムのベースアイテムキー、キャンセルならばnullopt
136  */
137 static std::optional<BaseitemKey> select_magic_eater(PlayerType *player_ptr, bool only_browse)
138 {
139     bool flag, request_list;
140     auto tval = ItemKindType::NONE;
141     int menu_line = (use_menu ? 1 : 0);
142
143     auto magic_eater_data = PlayerClass(player_ptr).get_specific_data<magic_eater_data_type>();
144
145     if (auto result = check_magic_eater_spell_repeat(magic_eater_data.get());
146         result.has_value()) {
147         return result;
148     }
149
150     if (use_menu) {
151         screen_save();
152
153         while (tval == ItemKindType::NONE) {
154 #ifdef JP
155             prt(format(" %s 杖", (menu_line == 1) ? "》" : "  "), 2, 14);
156             prt(format(" %s 魔法棒", (menu_line == 2) ? "》" : "  "), 3, 14);
157             prt(format(" %s ロッド", (menu_line == 3) ? "》" : "  "), 4, 14);
158 #else
159             prt(format(" %s staff", (menu_line == 1) ? "> " : "  "), 2, 14);
160             prt(format(" %s wand", (menu_line == 2) ? "> " : "  "), 3, 14);
161             prt(format(" %s rod", (menu_line == 3) ? "> " : "  "), 4, 14);
162 #endif
163
164             if (only_browse) {
165                 prt(_("どの種類の魔法を見ますか?", "Which type of magic do you browse?"), 0, 0);
166             } else {
167                 prt(_("どの種類の魔法を使いますか?", "Which type of magic do you use?"), 0, 0);
168             }
169
170             const auto choice = inkey();
171             switch (choice) {
172             case ESCAPE:
173             case 'z':
174             case 'Z':
175                 screen_load();
176                 return std::nullopt;
177             case '2':
178             case 'j':
179             case 'J':
180                 menu_line++;
181                 break;
182             case '8':
183             case 'k':
184             case 'K':
185                 menu_line += 2;
186                 break;
187             case '\r':
188             case 'x':
189             case 'X':
190                 if (menu_line == 1) {
191                     tval = ItemKindType::STAFF;
192                 } else if (menu_line == 2) {
193                     tval = ItemKindType::WAND;
194                 } else {
195                     tval = ItemKindType::ROD;
196                 }
197                 break;
198             }
199             if (menu_line > 3) {
200                 menu_line -= 3;
201             }
202         }
203         screen_load();
204     } else {
205         while (true) {
206             const auto new_choice = input_command(_("[A] 杖, [B] 魔法棒, [C] ロッド:", "[A] staff, [B] wand, [C] rod:"), true);
207             if (!new_choice.has_value()) {
208                 return std::nullopt;
209             }
210
211             const auto choice = new_choice.value();
212             if (choice == 'A' || choice == 'a') {
213                 tval = ItemKindType::STAFF;
214                 break;
215             }
216
217             if (choice == 'B' || choice == 'b') {
218                 tval = ItemKindType::WAND;
219                 break;
220             }
221
222             if (choice == 'C' || choice == 'c') {
223                 tval = ItemKindType::ROD;
224                 break;
225             }
226         }
227     }
228
229     const auto &item_group = magic_eater_data->get_item_group(tval);
230
231     if (auto it = std::find_if(item_group.begin(), item_group.end(),
232             [](const auto &item) { return item.count > 0; });
233         it == item_group.end()) {
234         msg_print(_("その種類の魔法は覚えていない!", "You don't have that type of magic!"));
235         return std::nullopt;
236     } else {
237         if (use_menu) {
238             menu_line = 1 + std::distance(std::begin(item_group), it);
239         }
240     }
241
242     /* Nothing chosen yet */
243     flag = false;
244
245     std::string prompt;
246     if (only_browse) {
247         prompt = _("('*'で一覧, ESCで中断) どの魔力を見ますか?", "(*=List, ESC=exit) Browse which power? ");
248     } else {
249         prompt = _("('*'で一覧, ESCで中断) どの魔力を使いますか?", "(*=List, ESC=exit) Use which power? ");
250     }
251
252     screen_save();
253     request_list = always_show_list;
254
255     const int item_group_size = item_group.size();
256     auto sval = 0;
257     while (!flag) {
258         /* Show the list */
259         if (request_list || use_menu) {
260             byte y, x = 0;
261             PERCENTAGE chance;
262             short bi_id;
263             POSITION x1, y1;
264             DEPTH level;
265             byte col;
266
267             for (y = 1; y < 20; y++) {
268                 prt("", y, x);
269             }
270
271             y = 1;
272
273             /* Print header(s) */
274 #ifdef JP
275             prt(format("                           %s 失率                           %s 失率", (tval == ItemKindType::ROD ? "  状態  " : "使用回数"),
276                     (tval == ItemKindType::ROD ? "  状態  " : "使用回数")),
277                 y++, x);
278 #else
279             prt(format("                           %s Fail                           %s Fail", (tval == ItemKindType::ROD ? "  Stat  " : " Charges"),
280                     (tval == ItemKindType::ROD ? "  Stat  " : " Charges")),
281                 y++, x);
282 #endif
283
284             /* Print list */
285             for (auto sval_ctr = 0; sval_ctr < item_group_size; sval_ctr++) {
286                 auto &item = item_group[sval_ctr];
287                 if (item.count == 0) {
288                     continue;
289                 }
290
291                 bi_id = lookup_baseitem_id({ tval, sval_ctr });
292
293                 std::string dummy;
294                 if (use_menu) {
295                     if (sval_ctr == (menu_line - 1)) {
296                         dummy = _("》", "> ");
297                     } else {
298                         dummy = "  ";
299                     }
300                 }
301                 /* letter/number for power selection */
302                 else {
303                     char letter;
304                     if (sval_ctr < 26) {
305                         letter = I2A(sval_ctr);
306                     } else {
307                         letter = '0' + sval_ctr - 26;
308                     }
309                     dummy = format("%c)", letter);
310                 }
311                 x1 = ((sval_ctr < item_group_size / 2) ? x : x + 40);
312                 y1 = ((sval_ctr < item_group_size / 2) ? y + sval_ctr : y + sval_ctr - item_group_size / 2);
313                 const auto &baseitem = baseitems_info[bi_id];
314                 level = (tval == ItemKindType::ROD ? baseitem.level * 5 / 6 - 5 : baseitem.level);
315                 chance = level * 4 / 5 + 20;
316                 chance -= 3 * (adj_mag_stat[player_ptr->stat_index[mp_ptr->spell_stat]] - 1);
317                 level /= 2;
318                 if (player_ptr->lev > level) {
319                     chance -= 3 * (player_ptr->lev - level);
320                 }
321                 chance = mod_spell_chance_1(player_ptr, chance);
322                 chance = std::max<int>(chance, adj_mag_fail[player_ptr->stat_index[mp_ptr->spell_stat]]);
323                 auto player_stun = player_ptr->effects()->stun();
324                 chance += player_stun->get_magic_chance_penalty();
325                 if (chance > 95) {
326                     chance = 95;
327                 }
328
329                 chance = mod_spell_chance_2(player_ptr, chance);
330
331                 col = TERM_WHITE;
332
333                 if (bi_id) {
334                     if (tval == ItemKindType::ROD) {
335                         dummy.append(
336                             format(_(" %-22.22s 充填:%2d/%2d%3d%%", " %-22.22s   (%2d/%2d) %3d%%"), baseitem.name.data(),
337                                 item.charge ? (item.charge - 1) / (EATER_ROD_CHARGE * baseitem.pval) + 1 : 0,
338                                 item.count, chance)
339                                 .data());
340                         if (item.charge > baseitem.pval * (item.count - 1) * EATER_ROD_CHARGE) {
341                             col = TERM_RED;
342                         }
343                     } else {
344                         dummy.append(
345                             format(" %-22.22s    %2d/%2d %3d%%", baseitem.name.data(), (int16_t)(item.charge / EATER_CHARGE),
346                                 item.count, chance)
347                                 .data());
348                         if (item.charge < EATER_CHARGE) {
349                             col = TERM_RED;
350                         }
351                     }
352                 } else {
353                     dummy.clear();
354                 }
355                 c_prt(col, dummy, y1, x1);
356             }
357         }
358
359         const auto choice_opt = input_command(prompt);
360         if (!choice_opt.has_value()) {
361             break;
362         }
363
364         const auto choice = choice_opt.value();
365         auto should_redraw_cursor = true;
366         if (use_menu && choice != ' ') {
367             switch (choice) {
368             case '0': {
369                 screen_load();
370                 return std::nullopt;
371             }
372
373             case '8':
374             case 'k':
375             case 'K': {
376                 do {
377                     menu_line += item_group_size - 1;
378                     if (menu_line > item_group_size) {
379                         menu_line -= item_group_size;
380                     }
381                 } while (item_group[menu_line - 1].count == 0);
382                 break;
383             }
384
385             case '2':
386             case 'j':
387             case 'J': {
388                 do {
389                     menu_line++;
390                     if (menu_line > item_group_size) {
391                         menu_line -= item_group_size;
392                     }
393                 } while (item_group[menu_line - 1].count == 0);
394                 break;
395             }
396
397             case '4':
398             case 'h':
399             case 'H':
400             case '6':
401             case 'l':
402             case 'L': {
403                 bool reverse = false;
404                 if ((choice == '4') || (choice == 'h') || (choice == 'H')) {
405                     reverse = true;
406                 }
407                 if (menu_line > item_group_size / 2) {
408                     menu_line -= item_group_size / 2;
409                     reverse = true;
410                 } else {
411                     menu_line += item_group_size / 2;
412                 }
413                 while (item_group[menu_line - 1].count == 0) {
414                     if (reverse) {
415                         menu_line--;
416                         if (menu_line < 2) {
417                             reverse = false;
418                         }
419                     } else {
420                         menu_line++;
421                         if (menu_line > item_group_size - 1) {
422                             reverse = true;
423                         }
424                     }
425                 }
426                 break;
427             }
428
429             case 'x':
430             case 'X':
431             case '\r': {
432                 sval = menu_line - 1;
433                 should_redraw_cursor = false;
434                 break;
435             }
436             }
437         }
438
439         /* Request redraw */
440         if (use_menu && should_redraw_cursor) {
441             continue;
442         }
443
444         /* Request redraw */
445         if (!use_menu && ((choice == ' ') || (choice == '*') || (choice == '?'))) {
446             /* Hide the list */
447             if (request_list) {
448                 /* Hide list */
449                 request_list = false;
450                 screen_load();
451                 screen_save();
452             } else {
453                 request_list = true;
454             }
455
456             /* Redo asking */
457             continue;
458         }
459
460         if (!use_menu) {
461             if (isalpha(choice)) {
462                 sval = A2I(choice);
463             } else {
464                 sval = choice - '0' + 26;
465             }
466         }
467
468         /* Totally Illegal */
469         if ((sval < 0) || (sval > item_group_size) || item_group[sval].count == 0) {
470             bell();
471             continue;
472         }
473
474         if (!only_browse) {
475             auto &item = item_group[sval];
476             if (tval == ItemKindType::ROD) {
477                 if (item.charge > baseitems_info[lookup_baseitem_id({ tval, sval })].pval * (item.count - 1) * EATER_ROD_CHARGE) {
478                     msg_print(_("その魔法はまだ充填している最中だ。", "The magic is still charging."));
479                     msg_print(nullptr);
480                     continue;
481                 }
482             } else {
483                 if (item.charge < EATER_CHARGE) {
484                     msg_print(_("その魔法は使用回数が切れている。", "The magic has no charges left."));
485                     msg_print(nullptr);
486                     continue;
487                 }
488             }
489         }
490
491         /* Browse */
492         else {
493             /* Clear lines, position cursor  (really should use strlen here) */
494             term_erase(7, 23, 255);
495             term_erase(7, 22, 255);
496             term_erase(7, 21, 255);
497             term_erase(7, 20, 255);
498
499             display_wrap_around(baseitems_info[lookup_baseitem_id({ tval, sval })].text, 62, 21, 10);
500             continue;
501         }
502
503         /* Stop the loop */
504         flag = true;
505     }
506     screen_load();
507
508     if (!flag) {
509         return std::nullopt;
510     }
511
512     COMMAND_CODE base = 0;
513     switch (tval) {
514     case ItemKindType::STAFF:
515         base = EATER_STAFF_BASE;
516         break;
517     case ItemKindType::WAND:
518         base = EATER_WAND_BASE;
519         break;
520     case ItemKindType::ROD:
521         base = EATER_ROD_BASE;
522         break;
523     default:
524         break;
525     }
526
527     repeat_push(base + sval);
528
529     return BaseitemKey(tval, sval);
530 }
531
532 /*!
533  * @brief 取り込んだ魔力を利用するコマンドのメインルーチン /
534  * Use eaten rod, wand or staff
535  * @param only_browse 閲覧するだけならばTRUE
536  * @param powerful 強力発動中の処理ならばTRUE
537  * @return 実際にコマンドを実行したならばTRUEを返す。
538  */
539 bool do_cmd_magic_eater(PlayerType *player_ptr, bool only_browse, bool powerful)
540 {
541     bool use_charge = true;
542
543     if (cmd_limit_confused(player_ptr)) {
544         return false;
545     }
546
547     auto result = select_magic_eater(player_ptr, only_browse);
548     PlayerEnergy energy(player_ptr);
549     if (!result.has_value()) {
550         energy.reset_player_turn();
551         return false;
552     }
553     auto &bi_key = result.value();
554
555     const auto bi_id = lookup_baseitem_id(bi_key);
556     const auto &baseitem = baseitems_info[bi_id];
557     auto level = (bi_key.tval() == ItemKindType::ROD ? baseitem.level * 5 / 6 - 5 : baseitem.level);
558     auto chance = level * 4 / 5 + 20;
559     chance -= 3 * (adj_mag_stat[player_ptr->stat_index[mp_ptr->spell_stat]] - 1);
560     level /= 2;
561     if (player_ptr->lev > level) {
562         chance -= 3 * (player_ptr->lev - level);
563     }
564     chance = mod_spell_chance_1(player_ptr, chance);
565     chance = std::max<int>(chance, adj_mag_fail[player_ptr->stat_index[mp_ptr->spell_stat]]);
566     auto player_stun = player_ptr->effects()->stun();
567     chance += player_stun->get_magic_chance_penalty();
568     if (chance > 95) {
569         chance = 95;
570     }
571
572     chance = mod_spell_chance_2(player_ptr, chance);
573
574     if (randint0(100) < chance) {
575         if (flush_failure) {
576             flush();
577         }
578
579         msg_print(_("呪文をうまく唱えられなかった!", "You failed to get the magic off!"));
580         sound(SOUND_FAIL);
581         if (randint1(100) >= chance) {
582             chg_virtue(player_ptr, Virtue::CHANCE, -1);
583         }
584         energy.set_player_turn_energy(100);
585
586         return true;
587     } else {
588         DIRECTION dir = 0;
589
590         switch (bi_key.tval()) {
591         case ItemKindType::ROD: {
592             const auto sval = bi_key.sval();
593             if (!sval.has_value()) {
594                 return false;
595             }
596
597             if (bi_key.is_aiming_rod() && !get_aim_dir(player_ptr, &dir)) {
598                 return false;
599             }
600
601             (void)rod_effect(player_ptr, sval.value(), dir, &use_charge, powerful);
602             if (!use_charge) {
603                 return false;
604             }
605
606             break;
607         }
608         case ItemKindType::WAND: {
609             const auto sval = bi_key.sval();
610             if (!sval.has_value()) {
611                 return false;
612             }
613
614             if (!get_aim_dir(player_ptr, &dir)) {
615                 return false;
616             }
617
618             (void)wand_effect(player_ptr, sval.value(), dir, powerful, true);
619             break;
620         }
621         default:
622             const auto sval = bi_key.sval();
623             if (!sval.has_value()) {
624                 return false;
625             }
626
627             (void)staff_effect(player_ptr, sval.value(), &use_charge, powerful, true, true);
628             if (!use_charge) {
629                 return false;
630             }
631
632             break;
633         }
634
635         if (randint1(100) < chance) {
636             chg_virtue(player_ptr, Virtue::CHANCE, 1);
637         }
638     }
639
640     auto magic_eater_data = PlayerClass(player_ptr).get_specific_data<magic_eater_data_type>();
641     const auto opt_sval = bi_key.sval();
642     if (!opt_sval.has_value()) {
643         return false;
644     }
645
646     const auto tval = bi_key.tval();
647     const auto sval = opt_sval.value();
648     auto &item = magic_eater_data->get_item_group(tval)[sval];
649
650     energy.set_player_turn_energy(100);
651     if (tval == ItemKindType::ROD) {
652         item.charge += baseitem.pval * EATER_ROD_CHARGE;
653     } else {
654         item.charge -= EATER_CHARGE;
655     }
656
657     return true;
658 }