2 * @brief プレイヤーのアイテムに関するコマンドの実装2 / Spell/Prayer commands
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
14 * This file includes code for eating food, drinking potions,
15 * reading scrolls, aiming wands, using staffs, zapping rods,
16 * and activating artifacts.
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".
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.
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.
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.
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.
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).
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"
89 static std::optional<BaseitemKey> check_magic_eater_spell_repeat(magic_eater_data_type *magic_eater_data)
92 if (!repeat_pull(&sn)) {
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;
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())) {
111 auto &item = item_group[sval];
112 /* Verify the spell */
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);
119 case ItemKindType::STAFF:
120 case ItemKindType::WAND:
121 if (item.charge >= EATER_CHARGE) {
122 return BaseitemKey(tval, sval);
133 * @brief 魔道具術師の取り込んだ魔力一覧から選択/閲覧する /
134 * @param only_browse 閲覧するだけならばTRUE
135 * @return 選択したアイテムのベースアイテムキー、キャンセルならばnullopt
137 static std::optional<BaseitemKey> select_magic_eater(PlayerType *player_ptr, bool only_browse)
139 bool flag, request_list;
140 auto tval = ItemKindType::NONE;
141 int menu_line = (use_menu ? 1 : 0);
143 auto magic_eater_data = PlayerClass(player_ptr).get_specific_data<magic_eater_data_type>();
145 if (auto result = check_magic_eater_spell_repeat(magic_eater_data.get());
146 result.has_value()) {
153 while (tval == ItemKindType::NONE) {
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);
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);
165 prt(_("どの種類の魔法を見ますか?", "Which type of magic do you browse?"), 0, 0);
167 prt(_("どの種類の魔法を使いますか?", "Which type of magic do you use?"), 0, 0);
170 const auto choice = inkey();
190 if (menu_line == 1) {
191 tval = ItemKindType::STAFF;
192 } else if (menu_line == 2) {
193 tval = ItemKindType::WAND;
195 tval = ItemKindType::ROD;
206 const auto new_choice = input_command(_("[A] 杖, [B] 魔法棒, [C] ロッド:", "[A] staff, [B] wand, [C] rod:"), true);
207 if (!new_choice.has_value()) {
211 const auto choice = new_choice.value();
212 if (choice == 'A' || choice == 'a') {
213 tval = ItemKindType::STAFF;
217 if (choice == 'B' || choice == 'b') {
218 tval = ItemKindType::WAND;
222 if (choice == 'C' || choice == 'c') {
223 tval = ItemKindType::ROD;
229 const auto &item_group = magic_eater_data->get_item_group(tval);
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!"));
238 menu_line = 1 + std::distance(std::begin(item_group), it);
242 /* Nothing chosen yet */
247 prompt = _("('*'で一覧, ESCで中断) どの魔力を見ますか?", "(*=List, ESC=exit) Browse which power? ");
249 prompt = _("('*'で一覧, ESCで中断) どの魔力を使いますか?", "(*=List, ESC=exit) Use which power? ");
253 request_list = always_show_list;
255 const int item_group_size = item_group.size();
259 if (request_list || use_menu) {
267 for (y = 1; y < 20; y++) {
273 /* Print header(s) */
275 prt(format(" %s 失率 %s 失率", (tval == ItemKindType::ROD ? " 状態 " : "使用回数"),
276 (tval == ItemKindType::ROD ? " 状態 " : "使用回数")),
279 prt(format(" %s Fail %s Fail", (tval == ItemKindType::ROD ? " Stat " : " Charges"),
280 (tval == ItemKindType::ROD ? " Stat " : " Charges")),
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) {
291 bi_id = lookup_baseitem_id({ tval, sval_ctr });
295 if (sval_ctr == (menu_line - 1)) {
296 dummy = _("》", "> ");
301 /* letter/number for power selection */
305 letter = I2A(sval_ctr);
307 letter = '0' + sval_ctr - 26;
309 dummy = format("%c)", letter);
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);
318 if (player_ptr->lev > level) {
319 chance -= 3 * (player_ptr->lev - level);
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();
329 chance = mod_spell_chance_2(player_ptr, chance);
334 if (tval == ItemKindType::ROD) {
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,
340 if (item.charge > baseitem.pval * (item.count - 1) * EATER_ROD_CHARGE) {
345 format(" %-22.22s %2d/%2d %3d%%", baseitem.name.data(), (int16_t)(item.charge / EATER_CHARGE),
348 if (item.charge < EATER_CHARGE) {
355 c_prt(col, dummy, y1, x1);
359 const auto choice_opt = input_command(prompt);
360 if (!choice_opt.has_value()) {
364 const auto choice = choice_opt.value();
365 auto should_redraw_cursor = true;
366 if (use_menu && choice != ' ') {
377 menu_line += item_group_size - 1;
378 if (menu_line > item_group_size) {
379 menu_line -= item_group_size;
381 } while (item_group[menu_line - 1].count == 0);
390 if (menu_line > item_group_size) {
391 menu_line -= item_group_size;
393 } while (item_group[menu_line - 1].count == 0);
403 bool reverse = false;
404 if ((choice == '4') || (choice == 'h') || (choice == 'H')) {
407 if (menu_line > item_group_size / 2) {
408 menu_line -= item_group_size / 2;
411 menu_line += item_group_size / 2;
413 while (item_group[menu_line - 1].count == 0) {
421 if (menu_line > item_group_size - 1) {
432 sval = menu_line - 1;
433 should_redraw_cursor = false;
440 if (use_menu && should_redraw_cursor) {
445 if (!use_menu && ((choice == ' ') || (choice == '*') || (choice == '?'))) {
449 request_list = false;
461 if (isalpha(choice)) {
464 sval = choice - '0' + 26;
468 /* Totally Illegal */
469 if ((sval < 0) || (sval > item_group_size) || item_group[sval].count == 0) {
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."));
483 if (item.charge < EATER_CHARGE) {
484 msg_print(_("その魔法は使用回数が切れている。", "The magic has no charges left."));
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);
499 display_wrap_around(baseitems_info[lookup_baseitem_id({ tval, sval })].text, 62, 21, 10);
512 COMMAND_CODE base = 0;
514 case ItemKindType::STAFF:
515 base = EATER_STAFF_BASE;
517 case ItemKindType::WAND:
518 base = EATER_WAND_BASE;
520 case ItemKindType::ROD:
521 base = EATER_ROD_BASE;
527 repeat_push(base + sval);
529 return BaseitemKey(tval, sval);
533 * @brief 取り込んだ魔力を利用するコマンドのメインルーチン /
534 * Use eaten rod, wand or staff
535 * @param only_browse 閲覧するだけならばTRUE
536 * @param powerful 強力発動中の処理ならばTRUE
537 * @return 実際にコマンドを実行したならばTRUEを返す。
539 bool do_cmd_magic_eater(PlayerType *player_ptr, bool only_browse, bool powerful)
541 bool use_charge = true;
543 if (cmd_limit_confused(player_ptr)) {
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();
553 auto &bi_key = result.value();
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);
561 if (player_ptr->lev > level) {
562 chance -= 3 * (player_ptr->lev - level);
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();
572 chance = mod_spell_chance_2(player_ptr, chance);
574 if (randint0(100) < chance) {
579 msg_print(_("呪文をうまく唱えられなかった!", "You failed to get the magic off!"));
581 if (randint1(100) >= chance) {
582 chg_virtue(player_ptr, Virtue::CHANCE, -1);
584 energy.set_player_turn_energy(100);
590 switch (bi_key.tval()) {
591 case ItemKindType::ROD: {
592 const auto sval = bi_key.sval();
593 if (!sval.has_value()) {
597 if (bi_key.is_aiming_rod() && !get_aim_dir(player_ptr, &dir)) {
601 (void)rod_effect(player_ptr, sval.value(), dir, &use_charge, powerful);
608 case ItemKindType::WAND: {
609 const auto sval = bi_key.sval();
610 if (!sval.has_value()) {
614 if (!get_aim_dir(player_ptr, &dir)) {
618 (void)wand_effect(player_ptr, sval.value(), dir, powerful, true);
622 const auto sval = bi_key.sval();
623 if (!sval.has_value()) {
627 (void)staff_effect(player_ptr, sval.value(), &use_charge, powerful, true, true);
635 if (randint1(100) < chance) {
636 chg_virtue(player_ptr, Virtue::CHANCE, 1);
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()) {
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];
650 energy.set_player_turn_energy(100);
651 if (tval == ItemKindType::ROD) {
652 item.charge += baseitem.pval * EATER_ROD_CHARGE;
654 item.charge -= EATER_CHARGE;