5 #include "mind/mind-elementalist.h"
6 #include "action/action-limited.h"
7 #include "avatar/avatar.h"
8 #include "cmd-action/cmd-mind.h"
9 #include "cmd-action/cmd-spell.h"
10 #include "cmd-io/cmd-gameoption.h"
11 #include "core/asking-player.h"
12 #include "core/stuff-handler.h"
13 #include "core/window-redrawer.h"
14 #include "effect/effect-characteristics.h"
15 #include "effect/effect-monster-util.h"
16 #include "effect/effect-processor.h"
17 #include "effect/spells-effect-util.h"
18 #include "floor/cave.h"
19 #include "floor/floor-util.h"
20 #include "floor/geometry.h"
21 #include "game-option/disturbance-options.h"
22 #include "game-option/input-options.h"
23 #include "game-option/text-display-options.h"
24 #include "grid/feature-flag-types.h"
25 #include "grid/grid.h"
26 #include "hpmp/hp-mp-processor.h"
27 #include "io/command-repeater.h"
28 #include "io/input-key-acceptor.h"
29 #include "io/input-key-requester.h"
30 #include "main/sound-definitions-table.h"
31 #include "main/sound-of-music.h"
32 #include "mind/mind-explanations-table.h"
33 #include "mind/mind-mindcrafter.h"
34 #include "monster-race/monster-race.h"
35 #include "monster-race/race-brightness-flags.h"
36 #include "monster-race/race-flags-resistance.h"
37 #include "monster-race/race-flags3.h"
38 #include "monster-race/race-flags7.h"
39 #include "monster/monster-describer.h"
40 #include "player-base/player-class.h"
41 #include "player-info/equipment-info.h"
42 #include "player-status/player-energy.h"
43 #include "player-status/player-status-base.h"
44 #include "player/player-status-table.h"
45 #include "racial/racial-util.h"
46 #include "spell-kind/earthquake.h"
47 #include "spell-kind/magic-item-recharger.h"
48 #include "spell-kind/spells-beam.h"
49 #include "spell-kind/spells-charm.h"
50 #include "spell-kind/spells-detection.h"
51 #include "spell-kind/spells-genocide.h"
52 #include "spell-kind/spells-launcher.h"
53 #include "spell-kind/spells-lite.h"
54 #include "spell-kind/spells-sight.h"
55 #include "spell-kind/spells-teleport.h"
56 #include "spell-kind/spells-world.h"
57 #include "status/bad-status-setter.h"
58 #include "status/base-status.h"
59 #include "system/floor-type-definition.h"
60 #include "system/game-option-types.h"
61 #include "system/grid-type-definition.h"
62 #include "system/monster-entity.h"
63 #include "system/monster-race-info.h"
64 #include "system/player-type-definition.h"
65 #include "system/redrawing-flags-updater.h"
66 #include "target/grid-selector.h"
67 #include "target/target-getter.h"
68 #include "term/screen-processor.h"
69 #include "term/term-color-types.h"
70 #include "term/z-form.h"
71 #include "timed-effect/player-stun.h"
72 #include "timed-effect/timed-effects.h"
73 #include "util/bit-flags-calculator.h"
74 #include "util/enum-converter.h"
75 #include "util/int-char-converter.h"
76 #include "view/display-messages.h"
77 #include "view/display-util.h"
80 #include <unordered_map>
85 enum class ElementSpells {
109 struct element_type {
110 std::string_view title; //!< 領域名
111 std::array<AttributeType, 3> type; //!< 属性タイプリスト
112 std::array<std::string_view, 3> name; //!< 属性名リスト
113 std::unordered_map<AttributeType, AttributeType> extra; //!< 追加属性タイプ
119 struct element_power {
120 int elem; //!< 使用属性番号
121 mind_type info; //!< 難易度構造体
124 using element_type_list = const std::unordered_map<ElementRealmType, element_type>;
125 using element_power_list = const std::unordered_map<ElementSpells, element_power>;
126 using element_tip_list = const std::unordered_map<ElementSpells, std::string_view>;
127 using element_text_list = const std::unordered_map<ElementRealmType, std::string_view>;
133 static element_type_list element_types = {
135 ElementRealmType::FIRE, {
137 { { AttributeType::FIRE, AttributeType::HELL_FIRE, AttributeType::PLASMA } },
138 { { _("火炎", "Fire"), _("業火", "Hell Fire"), _("プラズマ", "Plasma") } },
143 ElementRealmType::ICE, {
145 { { AttributeType::COLD, AttributeType::INERTIAL, AttributeType::TIME } },
146 { { _("冷気", "Ice"), _("遅鈍", "Inertia"), _("時間逆転", "Time Stream") } },
147 { { AttributeType::COLD, AttributeType::ICE} },
151 ElementRealmType::SKY, {
153 { { AttributeType::ELEC, AttributeType::LITE, AttributeType::MANA } },
154 { { _("電撃", "Lightning"), _("光", "Light"), _("魔力", "Mana") } },
159 ElementRealmType::SEA, {
161 { { AttributeType::ACID, AttributeType::WATER, AttributeType::DISINTEGRATE } },
162 { { _("酸", "Acid"), _("水", "Water"), _("分解", "Disintegration") } },
167 ElementRealmType::DARKNESS, {
169 { { AttributeType::DARK, AttributeType::NETHER, AttributeType::VOID_MAGIC } },
170 { { _("暗黒", "Darkness"), _("地獄", "Nether"), _("虚無", "void") } },
171 { { AttributeType::DARK, AttributeType::ABYSS } },
175 ElementRealmType::CHAOS, {
177 { { AttributeType::CONFUSION, AttributeType::CHAOS, AttributeType::NEXUS } },
178 { { _("混乱", "Confusion"), _("カオス", "Chaos"), _("因果混乱", "Nexus") } },
183 ElementRealmType::EARTH, {
185 { { AttributeType::SHARDS, AttributeType::FORCE, AttributeType::METEOR } },
186 { { _("破片", "Shards"), _("フォース", "Force"), _("隕石", "Meteor") } },
191 ElementRealmType::DEATH, {
193 { { AttributeType::POIS, AttributeType::HYPODYNAMIA, AttributeType::DISENCHANT } },
194 { { _("毒", "Poison"), _("吸血", "Drain Life"), _("劣化", "Disenchantment") } },
203 static const element_power_list element_powers = {
204 { ElementSpells::BOLT_1ST, { 0, { 1, 1, 15, _("%sの矢", "%s Bolt") }}},
205 { ElementSpells::MON_DETECT, { 0, { 2, 2, 20, _("モンスター感知", "Detect Monsters") }}},
206 { ElementSpells::PERCEPT, { 0, { 5, 5, 50, _("擬似鑑定", "Psychometry") }}},
207 { ElementSpells::CURE, { 0, { 6, 5, 35, _("傷の治癒", "Cure Wounds") }}},
208 { ElementSpells::BOLT_2ND, { 1, { 8, 6, 35, _("%sの矢", "%s Bolt") }}},
209 { ElementSpells::MAG_DETECT, { 0, { 10, 8, 60, _("魔法感知", "Detect Magical Objs") }}},
210 { ElementSpells::BALL_3RD, { 2, { 15, 10, 55, _("%s放射", "%s Spout") }}},
211 { ElementSpells::BALL_1ST, { 0, { 18, 13, 65, _("%sの球", "%s Ball") }}},
212 { ElementSpells::BREATH_2ND, { 1, { 21, 20, 70, _("%sのブレス", "Breath of %s") }}},
213 { ElementSpells::ANNIHILATE, { 0, { 24, 20, 75, _("モンスター消滅", "Annihilation") }}},
214 { ElementSpells::BOLT_3RD, { 2, { 25, 15, 60, _("%sの矢", "%s Bolt") }}},
215 { ElementSpells::WAVE_1ST, { 0, { 28, 30, 75, _("元素の波動", "Elemental Wave") }}},
216 { ElementSpells::BALL_2ND, { 1, { 28, 22, 75, _("%sの球", "%s Ball") }}},
217 { ElementSpells::BURST_1ST, { 0, { 33, 35, 75, _("精気乱射", "%s Blast") }}},
218 { ElementSpells::STORM_2ND, { 1, { 35, 30, 75, _("%sの嵐", "%s Storm") }}},
219 { ElementSpells::BREATH_1ST, { 0, { 42, 48, 75, _("%sのブレス", "Breath of %s") }}},
220 { ElementSpells::STORM_3ND, { 2, { 45, 60, 80, _("%sの嵐", "%s Storm") }}},
226 static element_tip_list element_tips = {
227 { ElementSpells::BOLT_1ST,
228 _("弱い%sの矢を放つ。", "Fire a weak bolt of %s.") },
229 { ElementSpells::MON_DETECT,
230 _("近くの全てのモンスターを感知する。", "Detects monsters.") },
231 { ElementSpells::PERCEPT,
232 _("アイテムの雰囲気を知る。", "Gives feeling of an item.") },
233 { ElementSpells::CURE,
234 _("怪我と体力を少し回復させる。", "Heals HP and wounds a bit.") },
235 { ElementSpells::BOLT_2ND,
236 _("%sの矢を放つ。", "Fire a bolt of %s.") },
237 { ElementSpells::MAG_DETECT,
238 _("近くの魔法のアイテムを感知する。", "Detects magic devices.") },
239 { ElementSpells::BALL_3RD,
240 _("高威力で射程が短い%sの球を放つ。", "Fire a strong, short-range, ball of %s.") },
241 { ElementSpells::BALL_1ST,
242 _("%sの球を放つ。", "Fire a ball of %s.") },
243 { ElementSpells::BREATH_2ND,
244 _("%sのブレスを吐く。", "Fire a breath of %s.") },
245 { ElementSpells::ANNIHILATE,
246 _("%s耐性のないモンスターを1体抹殺する。", "Erase a monster unless it resists %s.") },
247 { ElementSpells::BOLT_3RD,
248 _("%sの矢を放つ。", "Fire a bolt of %s.") },
249 { ElementSpells::WAVE_1ST,
250 _("視界内の全ての敵に%sによるダメージを与える。", "Inflict %s damage on all monsters in sight.") },
251 { ElementSpells::BALL_2ND,
252 _("%sの球を放つ。", "Fire a ball of %s.") },
253 { ElementSpells::BURST_1ST,
254 _("ランダムな方向に%sの矢を放つ。", "Fire some bolts of %s in random direction.") },
255 { ElementSpells::STORM_2ND,
256 _("%sの巨大な球を放つ。", "Fire a large ball of %s.") },
257 { ElementSpells::BREATH_1ST,
258 _("%sのブレスを吐く。", "Fire a breath of %s.") },
259 { ElementSpells::STORM_3ND,
260 _("%sの巨大な球を放つ。", "Fire a large ball of %s.") },
264 * @brief 元素魔法選択時説明文定義
266 static element_text_list element_texts = {
267 { ElementRealmType::FIRE,
268 _("炎系統は巨大なエネルギーで灼熱を生み出し、全ての敵を燃やし尽くそうとします。",
269 "Great energy of Fire system will be able to burn out all of your enemies.")},
270 { ElementRealmType::ICE,
271 _("氷系統の魔法はその冷たさで敵の動きを奪い尽くし、魂すらも止めてしまうでしょう。",
272 "Ice system will freeze your enemies, even their souls.")},
273 { ElementRealmType::SKY,
274 _("空系統は大いなる天空のエネルギーを駆使して敵の全てを撃滅できます。",
275 "Sky system can terminate all of your enemies powerfully with the energy of the great sky.")},
276 { ElementRealmType::SEA,
277 _("海系統はその敵の全てを溶かし、大いなる海へと返してしまいます。",
278 "Sea system melts all of your enemies and returns them to the great ocean.")},
279 { ElementRealmType::DARKNESS,
280 _("闇系統は恐るべき力を常闇から引き出し、敵を地獄へと叩き落とすでしょう。",
281 "Dark system draws terrifying power from the darkness and knocks your enemies into hell.")},
282 { ElementRealmType::CHAOS,
283 _("混沌系統は敵の意識も条理も捻じ曲げ、その存在をあの世に送ってしまいます。",
284 "Chaos system twists and wraps your enemies, even their souls, and scatters them as dust in the wind.")},
285 { ElementRealmType::EARTH,
286 _("地系統は偉大なる大地の力を呼び出して、数多の敵のことごとくを粉砕しようとします。",
287 "Earth system smashes all of your enemies massively using its huge powers.")},
288 { ElementRealmType::DEATH,
289 _("瘴気系統は全ての生ける者にとって途轍もない毒です。",
290 "Death system is a tremendous poison for all living enemies.")},
297 * @param realm_idx 領域番号
300 concptr get_element_title(int realm_idx)
302 auto realm = i2enum<ElementRealmType>(realm_idx);
303 return element_types.at(realm).title.data();
307 * @brief 元素魔法領域の属性リストを返す
308 * @param realm_idx 領域番号
309 * @return 領域で使用できる属性リスト
311 static std::array<AttributeType, 3> get_element_types(int realm_idx)
313 auto realm = i2enum<ElementRealmType>(realm_idx);
314 return element_types.at(realm).type;
318 * @brief 元素魔法領域のn番目の属性を返す
319 * @param realm_idx 領域番号
323 AttributeType get_element_type(int realm_idx, int n)
325 return get_element_types(realm_idx)[n];
329 * @brief 元素魔法領域のn番目の呪文用の属性を返す
330 * @param realm_idx 領域番号
334 static AttributeType get_element_spells_type(PlayerType *player_ptr, int n)
336 auto realm = element_types.at(i2enum<ElementRealmType>(player_ptr->element));
337 auto t = realm.type.at(n);
338 if (realm.extra.find(t) != realm.extra.end()) {
339 if (randint0(100) < player_ptr->lev * 2) {
340 return realm.extra.at(t);
347 * @brief 元素魔法領域の属性名リストを返す
348 * @param realm_idx 領域番号
349 * @return 領域で使用できる属性の名称リスト
351 static std::array<std::string_view, 3> get_element_names(int realm_idx)
353 auto realm = i2enum<ElementRealmType>(realm_idx);
354 return element_types.at(realm).name;
358 * @brief 元素魔法領域のn番目の属性名を返す
359 * @param realm_idx 領域番号
363 concptr get_element_name(int realm_idx, int n)
365 return get_element_names(realm_idx)[n].data();
370 * @param player_ptr プレイヤー情報への参照ポインタ
371 * @param spell_idx 呪文番号
374 static std::string get_element_tip(PlayerType *player_ptr, int spell_idx)
376 auto realm = i2enum<ElementRealmType>(player_ptr->element);
377 auto spell = i2enum<ElementSpells>(spell_idx);
378 auto elem = element_powers.at(spell).elem;
379 return format(element_tips.at(spell).data(), element_types.at(realm).name[elem].data());
384 * @param player_ptr プレイヤー情報への参照ポインタ
385 * @param spell_idx 呪文番号
388 static int get_elemental_elem(PlayerType *player_ptr, int spell_idx)
391 auto spell = i2enum<ElementSpells>(spell_idx);
392 return element_powers.at(spell).elem;
396 * @brief 元素魔法呪文の難易度データを取得
397 * @param player_ptr プレイヤー情報への参照ポインタ
398 * @param spell_idx 呪文番号
401 static mind_type get_elemental_info(PlayerType *player_ptr, int spell_idx)
404 auto spell = i2enum<ElementSpells>(spell_idx);
405 return element_powers.at(spell).info;
409 * @brief 元素魔法呪文の効果表示文字列を取得
410 * @param player_ptr プレイヤー情報への参照ポインタ
411 * @param spell_idx 呪文番号
412 * @return std::string 魔法の効果を表す文字列
414 static std::string get_element_effect_info(PlayerType *player_ptr, int spell_idx)
416 PLAYER_LEVEL plev = player_ptr->lev;
417 auto spell = i2enum<ElementSpells>(spell_idx);
421 case ElementSpells::BOLT_1ST:
422 return format(" %s%dd%d", KWD_DAM, 3 + ((plev - 1) / 5), 4);
423 case ElementSpells::CURE:
424 return format(" %s%dd%d", KWD_HEAL, 2, 8);
425 case ElementSpells::BOLT_2ND:
426 return format(" %s%dd%d", KWD_DAM, 8 + ((plev - 5) / 4), 8);
427 case ElementSpells::BALL_3RD:
428 return format(" %s%d", KWD_DAM, (50 + plev * 2));
429 case ElementSpells::BALL_1ST:
430 return format(" %s%d", KWD_DAM, 55 + plev);
431 case ElementSpells::BREATH_2ND:
432 dam = p_ptr->chp / 2;
433 return format(" %s%d", KWD_DAM, (dam > 150) ? 150 : dam);
434 case ElementSpells::ANNIHILATE:
435 return format(" %s%d", _("効力:", "pow "), 50 + plev);
436 case ElementSpells::BOLT_3RD:
437 return format(" %s%dd%d", KWD_DAM, 12 + ((plev - 5) / 4), 8);
438 case ElementSpells::WAVE_1ST:
439 return format(" %s50+d%d", KWD_DAM, plev * 3);
440 case ElementSpells::BALL_2ND:
441 return format(" %s%d", KWD_DAM, 75 + plev * 3 / 2);
442 case ElementSpells::BURST_1ST:
443 return format(" %s%dd%d", KWD_DAM, 6 + plev / 8, 7);
444 case ElementSpells::STORM_2ND:
445 return format(" %s%d", KWD_DAM, 115 + plev * 5 / 2);
446 case ElementSpells::BREATH_1ST:
447 return format(" %s%d", KWD_DAM, p_ptr->chp * 2 / 3);
448 case ElementSpells::STORM_3ND:
449 return format(" %s%d", KWD_DAM, 300 + plev * 5);
451 return std::string();
457 * @param player_ptr プレイヤー情報への参照ポインタ
458 * @param spell_idx 呪文番号
459 * @return 実行したらTRUE、キャンセルならFALSE
461 static bool cast_element_spell(PlayerType *player_ptr, SPELL_IDX spell_idx)
463 auto spell = i2enum<ElementSpells>(spell_idx);
464 auto &power = element_powers.at(spell);
467 PLAYER_LEVEL plev = player_ptr->lev;
473 case ElementSpells::BOLT_1ST:
474 if (!get_aim_dir(player_ptr, &dir)) {
477 dam = damroll(3 + ((plev - 1) / 5), 4);
478 typ = get_element_spells_type(player_ptr, power.elem);
479 (void)fire_bolt(player_ptr, typ, dir, dam);
481 case ElementSpells::MON_DETECT:
482 (void)detect_monsters_normal(player_ptr, DETECT_RAD_DEFAULT);
483 (void)detect_monsters_invis(player_ptr, DETECT_RAD_DEFAULT);
485 case ElementSpells::PERCEPT:
486 return psychometry(player_ptr);
487 case ElementSpells::CURE:
488 (void)hp_player(player_ptr, damroll(2, 8));
489 (void)BadStatusSetter(player_ptr).mod_cut(-10);
491 case ElementSpells::BOLT_2ND:
492 if (!get_aim_dir(player_ptr, &dir)) {
495 dam = damroll(8 + ((plev - 5) / 4), 8);
496 typ = get_element_spells_type(player_ptr, power.elem);
497 if (fire_bolt_or_beam(player_ptr, plev, typ, dir, dam)) {
498 if (typ == AttributeType::HYPODYNAMIA) {
499 (void)hp_player(player_ptr, dam / 2);
503 case ElementSpells::MAG_DETECT:
504 (void)detect_objects_magic(player_ptr, DETECT_RAD_DEFAULT);
506 case ElementSpells::BALL_3RD:
508 if (!get_aim_dir(player_ptr, &dir)) {
511 typ = get_element_spells_type(player_ptr, power.elem);
513 (void)fire_ball(player_ptr, typ, dir, dam, 1);
516 case ElementSpells::BALL_1ST:
517 if (!get_aim_dir(player_ptr, &dir)) {
521 typ = get_element_spells_type(player_ptr, power.elem);
522 (void)fire_ball(player_ptr, typ, dir, dam, 2);
524 case ElementSpells::BREATH_2ND:
525 if (!get_aim_dir(player_ptr, &dir)) {
528 dam = std::min(150, player_ptr->chp / 2);
529 typ = get_element_spells_type(player_ptr, power.elem);
530 if (fire_breath(player_ptr, typ, dir, dam, 3)) {
531 if (typ == AttributeType::HYPODYNAMIA) {
532 (void)hp_player(player_ptr, dam / 2);
536 case ElementSpells::ANNIHILATE:
537 if (!get_aim_dir(player_ptr, &dir)) {
540 fire_ball_hide(player_ptr, AttributeType::E_GENOCIDE, dir, plev + 50, 0);
542 case ElementSpells::BOLT_3RD:
543 if (!get_aim_dir(player_ptr, &dir)) {
546 dam = damroll(12 + ((plev - 5) / 4), 8);
547 typ = get_element_spells_type(player_ptr, power.elem);
548 fire_bolt_or_beam(player_ptr, plev, typ, dir, dam);
550 case ElementSpells::WAVE_1ST:
551 dam = 50 + randint1(plev * 3);
552 typ = get_element_spells_type(player_ptr, power.elem);
553 project_all_los(player_ptr, typ, dam);
555 case ElementSpells::BALL_2ND:
556 if (!get_aim_dir(player_ptr, &dir)) {
559 dam = 75 + plev * 3 / 2;
560 typ = get_element_spells_type(player_ptr, power.elem);
561 if (fire_ball(player_ptr, typ, dir, dam, 3)) {
562 if (typ == AttributeType::HYPODYNAMIA) {
563 (void)hp_player(player_ptr, dam / 2);
567 case ElementSpells::BURST_1ST:
571 typ = get_element_spells_type(player_ptr, power.elem);
572 for (int k = 0; k < num; k++) {
575 scatter(player_ptr, &y, &x, player_ptr->y, player_ptr->x, 4, PROJECT_NONE);
576 if (!cave_has_flag_bold(player_ptr->current_floor_ptr, y, x, TerrainCharacteristics::PROJECT)) {
579 if (!player_bold(player_ptr, y, x)) {
583 project(player_ptr, 0, 0, y, x, damroll(6 + plev / 8, 7), typ, (PROJECT_BEAM | PROJECT_THRU | PROJECT_GRID | PROJECT_KILL));
586 case ElementSpells::STORM_2ND:
587 if (!get_aim_dir(player_ptr, &dir)) {
590 dam = 115 + plev * 5 / 2;
591 typ = get_element_spells_type(player_ptr, power.elem);
592 if (fire_ball(player_ptr, typ, dir, dam, 4)) {
593 if (typ == AttributeType::HYPODYNAMIA) {
594 (void)hp_player(player_ptr, dam / 2);
598 case ElementSpells::BREATH_1ST:
599 if (!get_aim_dir(player_ptr, &dir)) {
602 dam = player_ptr->chp * 2 / 3;
603 typ = get_element_spells_type(player_ptr, power.elem);
604 (void)fire_breath(player_ptr, typ, dir, dam, 3);
606 case ElementSpells::STORM_3ND:
607 if (!get_aim_dir(player_ptr, &dir)) {
610 dam = 300 + plev * 5;
611 typ = get_element_spells_type(player_ptr, power.elem);
612 (void)fire_ball(player_ptr, typ, dir, dam, 5);
622 * @brief 元素魔法呪文の失敗率を計算
623 * @param player_ptr プレイヤー情報への参照ポインタ
624 * @param spell_idx 呪文番号
627 static PERCENTAGE decide_element_chance(PlayerType *player_ptr, mind_type spell)
629 PERCENTAGE chance = spell.fail;
631 chance -= 3 * (player_ptr->lev - spell.min_lev);
632 chance += player_ptr->to_m_chance;
633 chance -= 3 * (adj_mag_stat[player_ptr->stat_index[A_WIS]] - 1);
635 PERCENTAGE minfail = adj_mag_fail[player_ptr->stat_index[A_WIS]];
636 if (chance < minfail) {
640 auto player_stun = player_ptr->effects()->stun();
641 chance += player_stun->get_magic_chance_penalty();
642 if (heavy_armor(player_ptr)) {
646 if (player_ptr->is_icky_wield[0]) {
650 if (player_ptr->is_icky_wield[1]) {
662 * @brief 元素魔法呪文の消費MPを計算
663 * @param player_ptr プレイヤー情報への参照ポインタ
664 * @param spell_idx 呪文番号
667 static MANA_POINT decide_element_mana_cost(PlayerType *player_ptr, mind_type spell)
670 return spell.mana_cost;
674 * @brief 元素魔法呪文を選択して取得
675 * @param player_ptr プレイヤー情報への参照ポインタ
677 * @param only_browse 閲覧モードかどうか
678 * @return 選んだらTRUE、選ばなかったらFALSE
680 static bool get_element_power(PlayerType *player_ptr, SPELL_IDX *sn, bool only_browse)
686 PLAYER_LEVEL plev = player_ptr->lev;
689 int menu_line = (use_menu ? 1 : 0);
692 if (repeat_pull(&code)) {
693 *sn = (SPELL_IDX)code;
694 if (get_elemental_info(player_ptr, *sn).min_lev <= plev) {
699 concptr p = _("元素魔法", "power");
703 for (i = 0; i < static_cast<SPELL_IDX>(ElementSpells::MAX); i++) {
704 if (get_elemental_info(player_ptr, i).min_lev <= plev) {
711 fmt = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sについて知りますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
713 fmt = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sを使いますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
716 const auto prompt = format(fmt.data(), p, I2A(0), I2A(num - 1), p);
717 if (use_menu && !only_browse) {
723 auto choice = (always_show_list || use_menu) ? ESCAPE : 1;
725 if (choice == ESCAPE) {
728 const auto new_choice = input_command(prompt, true);
729 if (!new_choice.has_value()) {
733 choice = new_choice.value();
736 auto should_redraw_cursor = true;
737 if (use_menu && choice != ' ') {
747 menu_line += (num - 1);
759 should_redraw_cursor = false;
763 if (menu_line > num) {
768 constexpr auto spell_max = enum2i(ElementSpells::MAX);
769 if ((choice == ' ') || (choice == '*') || (choice == '?') || (use_menu && should_redraw_cursor)) {
770 if (!redraw || use_menu) {
772 if (!only_browse && !use_menu) {
777 put_str(_("名前", "Name"), y, x + 5);
778 put_str(_("Lv MP 失率 効果", "Lv MP Fail Info"), y, x + 35);
779 for (i = 0; i < spell_max; i++) {
780 elem = get_elemental_elem(player_ptr, i);
781 spell = get_elemental_info(player_ptr, i);
783 if (spell.min_lev > plev) {
787 PERCENTAGE chance = decide_element_chance(player_ptr, spell);
788 int mana_cost = decide_element_mana_cost(player_ptr, spell);
789 const auto comment = get_element_effect_info(player_ptr, i);
793 if (i == (menu_line - 1)) {
794 desc = _(" 》 ", " > ");
799 desc = format(" %c) ", I2A(i));
802 concptr s = get_element_name(player_ptr->element, elem);
803 std::string name = format(spell.name, s);
804 desc.append(format("%-30s%2d %4d %3d%%%s", name.data(), spell.min_lev, mana_cost, chance, comment.data()));
805 prt(desc, y + i + 1, x);
808 prt("", y + i + 1, x);
809 } else if (!only_browse) {
821 if ((i < 0) || (i >= num)) {
829 if (redraw && !only_browse) {
833 RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::SPELL);
834 handle_stuff(player_ptr);
840 repeat_push((COMMAND_CODE)i);
845 * @brief 元素魔法呪文をMPがなくても挑戦するか確認する
846 * @param player_ptr プレイヤー情報への参照ポインタ
847 * @param mana_cost 消費MP
848 * @return 詠唱するならTRUE、しないならFALSE
850 static bool check_element_mp_sufficiency(PlayerType *player_ptr, int mana_cost)
852 if (mana_cost <= player_ptr->csp) {
856 msg_print(_("MPが足りません。", "You do not have enough mana to use this power."));
861 return input_check(_("それでも挑戦しますか? ", "Attempt it anyway? "));
865 * @brief 元素魔法呪文の詠唱を試み、成功なら詠唱し、失敗ならファンブルする
866 * @param player_ptr プレイヤー情報への参照ポインタ
867 * @param spell_idx 呪文番号
869 * @return 詠唱して実行したらTRUE、されなかったらFALSE
871 static bool try_cast_element_spell(PlayerType *player_ptr, SPELL_IDX spell_idx, PERCENTAGE chance)
873 if (randint0(100) >= chance) {
875 return cast_element_spell(player_ptr, spell_idx);
882 msg_format(_("魔力の集中に失敗した!", "You failed to focus the elemental power!"));
885 if (randint1(100) < chance / 2) {
886 int plev = player_ptr->lev;
887 msg_print(_("元素の力が制御できない氾流となって解放された!", "The elemental power surges from you in an uncontrollable torrent!"));
888 const auto element = get_element_types(player_ptr->element)[0];
889 constexpr auto flags = PROJECT_JUMP | PROJECT_KILL | PROJECT_GRID | PROJECT_ITEM;
890 project(player_ptr, PROJECT_WHO_UNCTRL_POWER, 2 + plev / 10, player_ptr->y, player_ptr->x, plev * 2, element, flags);
891 player_ptr->csp = std::max(0, player_ptr->csp - player_ptr->msp * 10 / (20 + randint1(10)));
893 PlayerEnergy(player_ptr).set_player_turn_energy(100);
894 auto &rfu = RedrawingFlagsUpdater::get_instance();
895 rfu.set_flag(MainWindowRedrawingFlag::MP);
896 static constexpr auto flags_swrf = {
897 SubWindowRedrawingFlag::PLAYER,
898 SubWindowRedrawingFlag::SPELL,
900 rfu.set_flags(flags_swrf);
908 * @brief 元素魔法コマンドのメインルーチン
909 * @param player_ptr プレイヤー情報への参照ポインタ
911 void do_cmd_element(PlayerType *player_ptr)
914 if (cmd_limit_confused(player_ptr) || !get_element_power(player_ptr, &i, false)) {
918 mind_type spell = get_elemental_info(player_ptr, i);
919 PERCENTAGE chance = decide_element_chance(player_ptr, spell);
920 int mana_cost = decide_element_mana_cost(player_ptr, spell);
922 if (!check_element_mp_sufficiency(player_ptr, mana_cost)) {
926 if (!try_cast_element_spell(player_ptr, i, chance)) {
930 if (mana_cost <= player_ptr->csp) {
931 player_ptr->csp -= mana_cost;
933 int oops = mana_cost;
935 player_ptr->csp_frac = 0;
936 msg_print(_("精神を集中しすぎて気を失ってしまった!", "You faint from the effort!"));
937 (void)BadStatusSetter(player_ptr).mod_paralysis(randint1(5 * oops + 1));
938 chg_virtue(player_ptr, Virtue::KNOWLEDGE, -10);
939 if (randint0(100) < 50) {
940 bool perm = (randint0(100) < 25);
941 msg_print(_("体を悪くしてしまった!", "You have damaged your health!"));
942 (void)dec_stat(player_ptr, A_CON, 15 + randint1(10), perm);
946 PlayerEnergy(player_ptr).set_player_turn_energy(100);
947 auto &rfu = RedrawingFlagsUpdater::get_instance();
948 rfu.set_flag(MainWindowRedrawingFlag::MP);
949 static constexpr auto flags_swrf = {
950 SubWindowRedrawingFlag::PLAYER,
951 SubWindowRedrawingFlag::SPELL,
953 rfu.set_flags(flags_swrf);
957 * @brief 現在プレイヤーが使用可能な元素魔法の一覧表示
958 * @param player_ptr プレイヤー情報への参照ポインタ
960 void do_cmd_element_browse(PlayerType *player_ptr)
966 if (!get_element_power(player_ptr, &n, true)) {
971 term_erase(12, 21, 255);
972 term_erase(12, 20, 255);
973 term_erase(12, 19, 255);
974 term_erase(12, 18, 255);
975 term_erase(12, 17, 255);
976 term_erase(12, 16, 255);
977 display_wrap_around(get_element_tip(player_ptr, n), 62, 17, 15);
979 prt(_("何かキーを押して下さい。", "Hit any key."), 0, 0);
985 * @brief 元素魔法の単体抹殺が有効か確認する
986 * @param r_ptr モンスター種族への参照ポインタ
988 * @return 効果があるならTRUE、なければFALSE
990 bool is_elemental_genocide_effective(MonsterRaceInfo *r_ptr, AttributeType type)
993 case AttributeType::FIRE:
994 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
998 case AttributeType::COLD:
999 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
1003 case AttributeType::ELEC:
1004 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
1008 case AttributeType::ACID:
1009 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ACID)) {
1013 case AttributeType::DARK:
1014 if (r_ptr->resistance_flags.has(MonsterResistanceType::RESIST_DARK) || r_ptr->r_resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
1018 case AttributeType::CONFUSION:
1019 if (any_bits(r_ptr->flags3, RF3_NO_CONF)) {
1023 case AttributeType::SHARDS:
1024 if (r_ptr->resistance_flags.has(MonsterResistanceType::RESIST_SHARDS)) {
1028 case AttributeType::POIS:
1029 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_POISON)) {
1041 * @brief 元素魔法の単体抹殺の効果を発動する
1042 * @param player_ptr プレイヤー情報への参照ポインタ
1043 * @param em_ptr 魔法効果情報への参照ポインタ
1044 * @return 効果処理を続けるかどうか
1046 ProcessResult effect_monster_elemental_genocide(PlayerType *player_ptr, EffectMonster *em_ptr)
1048 auto type = get_element_type(player_ptr->element, 0);
1049 auto name = get_element_name(player_ptr->element, 0);
1050 bool b = is_elemental_genocide_effective(em_ptr->r_ptr, type);
1052 if (em_ptr->seen_msg) {
1053 msg_format(_("%sが%sを包み込んだ。", "The %s surrounds %s."), name, em_ptr->m_name);
1057 em_ptr->obvious = true;
1061 if (em_ptr->seen_msg) {
1062 msg_format(_("%sには効果がなかった。", "%s^ is unaffected."), em_ptr->m_name);
1065 return ProcessResult::PROCESS_TRUE;
1068 if (genocide_aux(player_ptr, em_ptr->g_ptr->m_idx, em_ptr->dam, !em_ptr->who, (em_ptr->r_ptr->level + 1) / 2, _("モンスター消滅", "Genocide One"))) {
1069 if (em_ptr->seen_msg) {
1070 msg_format(_("%sは消滅した!", "%s^ disappeared!"), em_ptr->m_name);
1073 chg_virtue(player_ptr, Virtue::VITALITY, -1);
1074 return ProcessResult::PROCESS_TRUE;
1077 em_ptr->skipped = true;
1078 return ProcessResult::PROCESS_CONTINUE;
1082 * @brief 元素領域とレベルの条件に見合うかチェックする
1083 * @param player_ptr プレイヤー情報への参照ポインタ
1085 * @param lev プレイヤーレベル
1086 * @return 見合うならTRUE、そうでなければFALSE
1088 * レベルに応じて取得する耐性などの判定に使用する
1090 bool has_element_resist(PlayerType *player_ptr, ElementRealmType realm, PLAYER_LEVEL lev)
1092 if (!PlayerClass(player_ptr).equals(PlayerClassType::ELEMENTALIST)) {
1096 auto prealm = i2enum<ElementRealmType>(player_ptr->element);
1097 return (prealm == realm) && (player_ptr->lev >= lev);
1101 * @brief 領域選択時のカーソル表示(シンボル+領域名)
1106 static void display_realm_cursor(int i, int n, term_color_type color)
1112 name = _("ランダム", "Random");
1115 name = element_types.at(i2enum<ElementRealmType>(i + 1)).title.data();
1118 c_put_str(color, format("%c) %s", sym, name), 12 + (i / 5), 2 + 15 * (i % 5));
1122 * @brief 領域選択時の移動キー処理
1128 static int interpret_realm_select_key(int cs, int n, char c)
1163 * @param player_ptr プレイヤー情報への参照ポインタ
1167 static int get_element_realm(PlayerType *player_ptr, int is, int n)
1169 int cs = std::max(0, is);
1173 std::string buf = format(_("領域を選んで下さい(%c-%c) ('='初期オプション設定): ", "Choose a realm (%c-%c) ('=' for options): "), I2A(0), I2A(n - 1));
1176 display_realm_cursor(os, n, TERM_WHITE);
1177 display_realm_cursor(cs, n, TERM_YELLOW);
1178 put_str(buf, 10, 10);
1182 cs = interpret_realm_select_key(cs, n, c);
1188 if (c == ' ' || c == '\r' || c == '\n') {
1190 display_realm_cursor(cs, n, TERM_WHITE);
1191 cs = randint0(n - 1);
1197 display_realm_cursor(cs, n, TERM_WHITE);
1198 cs = randint0(n - 1);
1202 k = islower(c) ? A2I(c) : -1;
1203 if (k >= 0 && k < n) {
1204 display_realm_cursor(cs, n, TERM_WHITE);
1209 k = isupper(c) ? (26 + c - 'A') : -1;
1210 if (k >= 26 && k < n) {
1211 display_realm_cursor(cs, n, TERM_WHITE);
1218 do_cmd_options_aux(player_ptr, OPT_PAGE_BIRTH, _("初期オプション((*)はスコアに影響)", "Birth Options ((*)) affect score"));
1220 } else if (c != '2' && c != '4' && c != '6' && c != '8') {
1225 display_realm_cursor(cs, n, TERM_YELLOW);
1231 * @param player_ptr プレイヤー情報への参照ポインタ
1234 byte select_element_realm(PlayerType *player_ptr)
1238 constexpr auto realm_max = enum2i(ElementRealmType::MAX);
1243 _("注意:元素系統の選択によりあなたが習得する呪文のタイプが決まります。", "Note: The system of element will determine which spells you can learn."),
1246 for (int i = 0; i < realm_max; i++) {
1247 display_realm_cursor(i, realm_max - 1, TERM_WHITE);
1250 realm_idx = get_element_realm(player_ptr, realm_idx - 1, realm_max - 1);
1251 if (realm_idx == 255) {
1255 auto realm = i2enum<ElementRealmType>(realm_idx);
1256 display_wrap_around(element_texts.at(realm), 74, row, 3);
1258 if (input_check_strict(player_ptr, _("よろしいですか?", "Are you sure? "), UserCheck::DEFAULT_Y)) {
1266 return (byte)realm_idx;
1270 * @brief クラスパワー情報を追加
1271 * @param player_ptr プレイヤー情報への参照ポインタ
1272 * @param rc_ptr レイシャルパワー情報への参照ポインタ
1274 void switch_element_racial(PlayerType *player_ptr, rc_type *rc_ptr)
1276 auto plev = player_ptr->lev;
1277 auto realm = i2enum<ElementRealmType>(player_ptr->element);
1280 case ElementRealmType::FIRE:
1281 rpi = rpi_type(_("ライト・エリア", "Light area"));
1282 rpi.text = _("光源が照らしている範囲か部屋全体を永久に明るくする。", "Lights up nearby area and the inside of a room permanently.");
1287 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1289 case ElementRealmType::ICE:
1290 rpi = rpi_type(_("周辺フリーズ", "Sleep monsters"));
1291 rpi.info = format("%s%d", KWD_POWER, 20 + plev * 3 / 2);
1292 rpi.text = _("視界内の全てのモンスターを眠らせる。抵抗されると無効。", "Attempts to put all monsters in sight to sleep.");
1297 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1299 case ElementRealmType::SKY:
1300 rpi = rpi_type(_("魔力充填", "Recharging"));
1301 rpi.info = format("%s%d", KWD_POWER, 120);
1302 rpi.text = _("杖/魔法棒の充填回数を増やすか、充填中のロッドの充填時間を減らす。", "Recharges staffs, wands or rods.");
1307 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1309 case ElementRealmType::SEA:
1310 rpi = rpi_type(_("岩石溶解", "Stone to mud"));
1311 rpi.text = _("壁を溶かして床にする。", "Turns one rock square to mud.");
1316 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1318 case ElementRealmType::DARKNESS:
1319 rpi = rpi_type(_("闇の扉", "Door to darkness"));
1320 rpi.info = format("%s%d", KWD_SPHERE, 15 + plev / 2);
1322 rpi.cost = 5 + plev / 7;
1325 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1327 case ElementRealmType::CHAOS:
1328 rpi = rpi_type(_("現実変容", "Alter reality"));
1329 rpi.text = _("現在の階を再構成する。", "Recreates current dungeon level.");
1334 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1336 case ElementRealmType::EARTH:
1337 rpi = rpi_type(_("地震", "Earthquake"));
1338 rpi.info = format("%s%d", KWD_SPHERE, 10);
1339 rpi.text = _("周囲のダンジョンを揺らし、壁と床をランダムに入れ変える。", "Shakes dungeon structure, and results in random swapping of floors and walls.");
1344 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1346 case ElementRealmType::DEATH:
1347 rpi = rpi_type(_("増殖阻止", "Sterilization"));
1348 rpi.text = _("この階の増殖するモンスターが増殖できなくなる。", "Prevents any breeders on current level from breeding.");
1353 rc_ptr->add_power(rpi, RC_IDX_CLASS_1);
1361 * @todo 宣言だけ。後日適切な場所に移動
1363 static bool door_to_darkness(PlayerType *player_ptr, POSITION dist);
1367 * @param player_ptr プレイヤー情報への参照ポインタ
1368 * @return 実行したらTRUE、しなかったらFALSE
1370 bool switch_element_execution(PlayerType *player_ptr)
1372 auto realm = i2enum<ElementRealmType>(player_ptr->element);
1373 PLAYER_LEVEL plev = player_ptr->lev;
1377 case ElementRealmType::FIRE:
1378 (void)lite_area(player_ptr, damroll(2, plev / 2), plev / 10);
1380 case ElementRealmType::ICE:
1381 (void)project(player_ptr, 0, 5, player_ptr->y, player_ptr->x, 1, AttributeType::COLD, PROJECT_ITEM);
1382 (void)project_all_los(player_ptr, AttributeType::OLD_SLEEP, 20 + plev * 3 / 2);
1384 case ElementRealmType::SKY:
1385 (void)recharge(player_ptr, 120);
1387 case ElementRealmType::SEA:
1388 if (!get_aim_dir(player_ptr, &dir)) {
1391 (void)wall_to_mud(player_ptr, dir, plev * 3 / 2);
1393 case ElementRealmType::DARKNESS:
1394 return door_to_darkness(player_ptr, 15 + plev / 2);
1396 case ElementRealmType::CHAOS:
1397 reserve_alter_reality(player_ptr, randint0(21) + 15);
1399 case ElementRealmType::EARTH:
1400 (void)earthquake(player_ptr, player_ptr->y, player_ptr->x, 10, 0);
1402 case ElementRealmType::DEATH:
1403 if (player_ptr->current_floor_ptr->num_repro <= MAX_REPRODUCTION) {
1404 player_ptr->current_floor_ptr->num_repro += MAX_REPRODUCTION;
1415 * @brief 指定したマスが暗いかどうか
1416 * @param f_ptr 階の情報への参照ポインタ
1419 * @return 暗いならTRUE、そうでないならFALSE
1421 static bool is_target_grid_dark(FloorType *f_ptr, POSITION y, POSITION x)
1423 if (any_bits(f_ptr->grid_array[y][x].info, CAVE_MNLT)) {
1427 bool is_dark = false;
1428 bool is_lite = any_bits(f_ptr->grid_array[y][x].info, CAVE_GLOW | CAVE_LITE);
1430 for (int dx = x - 2; dx <= x + 2; dx++) {
1431 for (int dy = y - 2; dy <= y + 2; dy++) {
1432 if (dx == x && dy == y) {
1435 if (!in_bounds(f_ptr, dy, dx)) {
1439 MONSTER_IDX m_idx = f_ptr->grid_array[dy][dx].m_idx;
1444 POSITION d = distance(dy, dx, y, x);
1445 auto *r_ptr = &monraces_info[f_ptr->m_list[m_idx].r_idx];
1446 if (d <= 1 && r_ptr->brightness_flags.has_any_of({ MonsterBrightnessType::HAS_LITE_1, MonsterBrightnessType::SELF_LITE_1 })) {
1449 if (d <= 2 && r_ptr->brightness_flags.has_any_of({ MonsterBrightnessType::HAS_LITE_2, MonsterBrightnessType::SELF_LITE_2 })) {
1452 if (d <= 1 && r_ptr->brightness_flags.has_any_of({ MonsterBrightnessType::HAS_DARK_1, MonsterBrightnessType::SELF_DARK_1 })) {
1455 if (d <= 2 && r_ptr->brightness_flags.has_any_of({ MonsterBrightnessType::HAS_DARK_2, MonsterBrightnessType::SELF_DARK_2 })) {
1461 return !is_lite || is_dark;
1465 * @breif 暗いところ限定での次元の扉
1466 * @param player_ptr プレイヤー情報への参照ポインタ
1468 static bool door_to_darkness(PlayerType *player_ptr, POSITION dist)
1470 POSITION y = player_ptr->y;
1471 POSITION x = player_ptr->x;
1474 for (int i = 0; i < 3; i++) {
1475 if (!tgt_pt(player_ptr, &x, &y)) {
1479 f_ptr = player_ptr->current_floor_ptr;
1481 if (distance(y, x, player_ptr->y, player_ptr->x) > dist) {
1482 msg_print(_("遠すぎる!", "That is too far!"));
1486 if (!is_cave_empty_bold(player_ptr, y, x) || f_ptr->grid_array[y][x].is_icky()) {
1487 msg_print(_("そこには移動できない。", "Can not teleport to there."));
1494 bool flag = cave_player_teleportable_bold(player_ptr, y, x, TELEPORT_SPONTANEOUS) && is_target_grid_dark(f_ptr, y, x);
1496 teleport_player_to(player_ptr, y, x, TELEPORT_SPONTANEOUS);
1498 msg_print(_("闇の扉は開かなかった!", "The door to darkness does not open!"));