2 * @brief スナイパー技能の実装 / Sniping
5 * 2014 Deskull rearranged comment for Doxygen.\n
8 #include "mind/mind-sniper.h"
9 #include "action/action-limited.h"
10 #include "cmd-action/cmd-shoot.h"
11 #include "core/asking-player.h"
12 #include "core/stuff-handler.h"
13 #include "core/window-redrawer.h"
14 #include "floor/geometry.h"
15 #include "game-option/text-display-options.h"
16 #include "grid/grid.h"
17 #include "inventory/inventory-slot-types.h"
18 #include "io/command-repeater.h"
19 #include "io/input-key-requester.h"
20 #include "main/sound-definitions-table.h"
21 #include "main/sound-of-music.h"
22 #include "mind/snipe-types.h"
23 #include "monster-race/monster-race.h"
24 #include "monster-race/race-flags-resistance.h"
25 #include "monster-race/race-flags3.h"
26 #include "object/tval-types.h"
27 #include "player-base/player-class.h"
28 #include "player-info/sniper-data-type.h"
29 #include "player-status/player-energy.h"
30 #include "system/item-entity.h"
31 #include "system/monster-entity.h"
32 #include "system/monster-race-info.h"
33 #include "system/player-type-definition.h"
34 #include "system/redrawing-flags-updater.h"
35 #include "term/screen-processor.h"
36 #include "term/term-color-types.h"
37 #include "term/z-form.h"
38 #include "util/int-char-converter.h"
39 #include "view/display-messages.h"
40 #include "view/display-util.h"
42 #define MAX_SNIPE_POWERS 16
51 /*! スナイパー技能の解説メッセージ */
52 static concptr const snipe_tips[MAX_SNIPE_POWERS] = {
54 "精神を集中する。射撃の威力、精度が上がり、高度な射撃術が使用できるようになる。",
55 "光る矢を放つ。光に弱いモンスターに威力を発揮する。",
56 "射撃を行った後、短距離の瞬間移動を行う。",
57 "軌道上の罠をすべて無効にする低空飛行の矢を放つ。",
59 "壁を粉砕する矢を放つ。岩でできたモンスターと無生物のモンスターに威力を発揮する。",
63 "善良なモンスターに威力を発揮する矢を放つ。",
64 "邪悪なモンスターに威力を発揮する矢を放つ。",
68 "敵の急所にめがけて矢を放つ。成功すると敵を一撃死させる。失敗すると1ダメージ。",
69 "全てのモンスターに高威力を発揮する矢を放つ。反動による副次効果を受ける。",
71 "Concentrate your mind for shooting.",
72 "Shoot a glowing arrow effective against light-sensitive foes.",
73 "Blink after shooting.",
74 "Shoot an arrow able to shatter traps.",
75 "Deals extra damage of fire.",
76 "Shoot an arrow able to shatter rocks.",
77 "Deals extra damage of ice.",
78 "Shoot an arrow that pushes away the target.",
79 "Shoot an arrow that does not always stop at the first target on its path.",
80 "Deals more damage to good monsters.",
81 "Deals more damage to evil monsters.",
82 "Shoot an arrow that explodes when it hits a monster.",
83 "Shoot two arrows at once.",
84 "Deals extra damage of lightning.",
85 "Deals quick death or 1 damage.",
86 "Deals great damage to all monsters, and some side effects to you.",
91 static snipe_power const snipe_powers[MAX_SNIPE_POWERS] = {
92 /* Level gained, cost, name */
96 { 3, 1, "シュート&アウェイ" },
106 { 32, 4, "ダブルショット" },
107 { 36, 3, "プラズマボルト" },
108 { 40, 3, "ニードルショット" },
109 { 48, 7, "セイントスターアロー" },
111 { 1, 0, "Concentration" },
112 { 2, 1, "Flash Arrow" },
113 { 3, 1, "Shoot & Away" },
114 { 5, 1, "Disarm Shot" },
115 { 8, 2, "Fire Shot" },
116 { 10, 2, "Shatter Arrow" },
117 { 13, 2, "Ice Shot" },
118 { 18, 2, "Rushing Arrow" },
119 { 22, 3, "Piercing Shot" },
120 { 25, 4, "Evil Shot" },
121 { 26, 4, "Holy Shot" },
122 { 30, 3, "Missile" },
123 { 32, 4, "Double Shot" },
124 { 36, 3, "Plasma Bolt" },
125 { 40, 3, "Needle Shot" },
126 { 48, 7, "Saint Stars Arrow" },
130 void SniperData::reset_concentration_flag()
132 this->reset_concent = false;
133 auto &rfu = RedrawingFlagsUpdater::get_instance();
134 static constexpr auto flags = {
135 StatusRecalculatingFlag::BONUS,
136 StatusRecalculatingFlag::MONSTER_STATUSES,
138 rfu.set_flags(flags);
139 rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
144 * @return 集中度を加算した場合は true、そうでなければ false
146 static bool snipe_concentrate(PlayerType *player_ptr)
148 auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
153 if (sniper_data->concent < (2 + (player_ptr->lev + 5) / 10)) {
154 sniper_data->concent++;
157 msg_format(_("集中した。(集中度 %d)", "You concentrate deeply. (lvl %d)"), sniper_data->concent);
158 sniper_data->reset_concentration_flag();
163 * @brief スナイパーの集中度リセット
164 * スナイパーではない、もしくは集中度がすでに0であればなにもしない。
165 * @param msg TRUEならばメッセージを表示する
167 void reset_concentration(PlayerType *player_ptr, bool msg)
169 auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
174 if (msg && (sniper_data->concent > 0)) {
175 msg_print(_("集中力が途切れてしまった。", "Stop concentrating."));
178 sniper_data->concent = 0;
179 sniper_data->reset_concentration_flag();
183 * @brief スナイパーの集中度によるダメージボーナスを加算する
184 * @param tdam 算出中のダメージ
185 * @return 集中度修正を加えたダメージ
187 int boost_concentration_damage(PlayerType *player_ptr, int tdam)
189 auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
190 const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
192 tdam = tdam * (10 + sniper_concent) / 10;
198 * @brief スナイパーの技能リストを表示する
200 void display_snipe_list(PlayerType *player_ptr)
205 PLAYER_LEVEL plev = player_ptr->lev;
209 /* Display a list of spells */
211 put_str(_("名前", "Name"), y, x + 5);
212 put_str(_("Lv MP", "Lv Mana"), y, x + 35);
214 auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
216 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
217 /* Access the available spell */
218 spell = snipe_powers[i];
219 if (spell.min_lev > plev) {
223 strnfmt(psi_desc, sizeof(psi_desc), " %c) %-30s%2d %4d", I2A(i), spell.name, spell.min_lev, spell.mana_cost);
225 TERM_COLOR tcol = (spell.mana_cost > sniper_data->concent) ? TERM_SLATE : TERM_WHITE;
226 term_putstr(x, y + i + 1, -1, tcol, psi_desc);
231 * @brief スナイパー技能を選択する
232 * @param sn 選択した特殊技能ID、キャンセルの場合-1、不正な選択の場合-2を返す
233 * @param only_browse 一覧を見るだけの場合TRUEを返す
234 * @return 発動可能な魔法を選択した場合TRUE、キャンセル処理か不正な選択が行われた場合FALSEを返す。
235 * Allow user to choose a mindcrafter power.\n
237 * If a valid spell is chosen, saves it in '*sn' and returns TRUE\n
238 * If the user hits escape, returns FALSE, and set '*sn' to -1\n
239 * If there are no legal choices, returns FALSE, and sets '*sn' to -2\n
241 * The "prompt" should be "cast", "recite", or "study"\n
242 * The "known" should be TRUE for cast/pray, FALSE for study\n
244 * nb: This function has a (trivial) display bug which will be obvious\n
245 * when you run it. It's probably easy to fix but I haven't tried,\n
248 static int get_snipe_power(PlayerType *player_ptr, COMMAND_CODE *sn, bool only_browse)
254 PLAYER_LEVEL plev = player_ptr->lev;
257 concptr p = _("射撃術", "power");
263 /* Assume cancelled */
266 auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
268 /* Repeat previous command */
269 /* Get the spell, if available */
270 if (repeat_pull(sn)) {
271 /* Verify the spell */
272 if ((snipe_powers[*sn].min_lev <= plev) && (snipe_powers[*sn].mana_cost <= sniper_data->concent)) {
281 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
282 if ((snipe_powers[i].min_lev <= plev) && ((only_browse) || (snipe_powers[i].mana_cost <= sniper_data->concent))) {
287 /* Build a prompt (accept all spells) */
289 constexpr auto mes = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sについて知りますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
290 (void)strnfmt(out_val, 78, mes, p, I2A(0), I2A(num), p);
292 constexpr auto mes = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sを使いますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
293 (void)strnfmt(out_val, 78, mes, p, I2A(0), I2A(num), p);
296 choice = always_show_list ? ESCAPE : 1;
298 if (choice == ESCAPE) {
300 } else if (!get_com(out_val, &choice, false)) {
305 if ((choice == ' ') || (choice == '*') || (choice == '?')) {
313 /* Display a list of spells */
315 put_str(_("名前", "Name"), y, x + 5);
316 put_str(_("Lv 集中度", "Lv Pow"), y, x + 35);
318 /* Dump the spells */
319 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
320 term_color_type tcol = TERM_WHITE;
321 term_erase(x, y + i + 1, 255);
323 /* Access the spell */
324 spell = snipe_powers[i];
326 /* Dump the spell --(-- */
327 if (spell.min_lev > plev) {
329 } else if (spell.mana_cost > sniper_data->concent) {
332 term_putstr(x, y + i + 1, -1, tcol, (spell.min_lev > plev) ? " ) " : format(" %c) ", I2A(i)));
333 term_putstr(x + 5, y + i + 1, -1, tcol, format("%-30s%2d %4d", spell.name, spell.min_lev, spell.mana_cost));
336 /* Clear the bottom line */
337 prt("", y + i + 1, x);
355 /* Totally Illegal */
356 if ((i < 0) || (i > num) || (!only_browse && (snipe_powers[i].mana_cost > sniper_data->concent))) {
361 /* Save the spell index */
362 spell = snipe_powers[i];
367 if (redraw && !only_browse) {
371 RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::SPELL);
372 handle_stuff(player_ptr);
374 /* Abort if needed */
379 /* Save the choice */
389 * @brief スナイバー技能のスレイ倍率計算を行う /
390 * Calcurate magnification of snipe technics
391 * @param mult スナイバー技能のスレイ効果以前に算出している多要素の倍率(/10倍)
392 * @param m_ptr 目標となるモンスターの構造体参照ポインタ
393 * @return スレイの倍率(/10倍)
395 MULTIPLY calc_snipe_damage_with_slay(PlayerType *player_ptr, MULTIPLY mult, MonsterEntity *m_ptr, SPELL_IDX snipe_type)
397 auto *r_ptr = &monraces_info[m_ptr->r_idx];
398 bool seen = is_seen(player_ptr, m_ptr);
400 auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
401 const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
403 switch (snipe_type) {
405 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
406 MULTIPLY n = 20 + sniper_concent;
408 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
416 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
418 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_FIRE);
422 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_FIRE)) {
423 n = 22 + (sniper_concent * 4);
424 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_FIRE);
426 n = 15 + (sniper_concent * 3);
435 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
437 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_COLD);
441 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_COLD)) {
442 n = 22 + (sniper_concent * 4);
443 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_COLD);
445 n = 15 + (sniper_concent * 3);
454 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
456 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_ELEC);
459 MULTIPLY n = 18 + (sniper_concent * 4);
466 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_ROCK)) {
467 MULTIPLY n = 15 + (sniper_concent * 2);
469 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_ROCK);
474 } else if (r_ptr->kind_flags.has(MonsterKindType::NONLIVING)) {
475 MULTIPLY n = 15 + (sniper_concent * 2);
477 r_ptr->r_kind_flags.set(MonsterKindType::NONLIVING);
485 if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
486 MULTIPLY n = 15 + (sniper_concent * 4);
488 r_ptr->r_kind_flags.set(MonsterKindType::GOOD);
496 if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
497 MULTIPLY n = 12 + (sniper_concent * 3);
499 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
501 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
502 n += (sniper_concent * 3);
504 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
523 * @brief スナイパー技能の発動 /
524 * do_cmd_cast calls this function if the player's class is 'snipe'.
525 * @param spell 発動する特殊技能のID
526 * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
528 static bool cast_sniper_spell(PlayerType *player_ptr, int spell)
530 auto *o_ptr = &player_ptr->inventory_list[INVEN_BOW];
531 if (o_ptr->bi_key.tval() != ItemKindType::BOW) {
532 msg_print(_("弓を装備していない!", "You wield no bow!"));
536 auto snipe_type = SP_NONE;
538 case 0: /* Concentration */
540 if (!snipe_concentrate(player_ptr)) {
543 PlayerEnergy(player_ptr).set_player_turn_energy(100);
546 snipe_type = SP_LITE;
549 snipe_type = SP_AWAY;
552 snipe_type = SP_KILL_TRAP;
555 snipe_type = SP_FIRE;
558 snipe_type = SP_KILL_WALL;
561 snipe_type = SP_COLD;
564 snipe_type = SP_RUSH;
567 snipe_type = SP_PIERCE;
570 snipe_type = SP_EVILNESS;
573 snipe_type = SP_HOLYNESS;
576 snipe_type = SP_EXPLODE;
579 snipe_type = SP_DOUBLE;
582 snipe_type = SP_ELEC;
585 snipe_type = SP_NEEDLE;
588 snipe_type = SP_FINAL;
591 msg_print(_("なに?", "Zap?"));
595 do_cmd_fire(player_ptr, snipe_type);
597 return player_ptr->is_fired;
601 * @brief スナイパー技能コマンドのメインルーチン /
603 void do_cmd_snipe(PlayerType *player_ptr)
605 if (cmd_limit_confused(player_ptr)) {
609 if (cmd_limit_image(player_ptr)) {
613 if (cmd_limit_stun(player_ptr)) {
618 if (!get_snipe_power(player_ptr, &n, false)) {
622 if (!cast_sniper_spell(player_ptr, n)) {
626 auto &rfu = RedrawingFlagsUpdater::get_instance();
627 static constexpr auto flags_mwrf = {
628 MainWindowRedrawingFlag::HP,
629 MainWindowRedrawingFlag::MP,
631 rfu.set_flags(flags_mwrf);
632 static constexpr auto flags_swrf = {
633 SubWindowRedrawingFlag::PLAYER,
634 SubWindowRedrawingFlag::SPELL,
636 rfu.set_flags(flags_swrf);
640 * @brief スナイパー技能コマンドの表示 /
642 void do_cmd_snipe_browse(PlayerType *player_ptr)
649 if (!get_snipe_power(player_ptr, &n, true)) {
654 /* Clear lines, position cursor (really should use strlen here) */
655 term_erase(12, 22, 255);
656 term_erase(12, 21, 255);
657 term_erase(12, 20, 255);
658 term_erase(12, 19, 255);
659 term_erase(12, 18, 255);
661 display_wrap_around(snipe_tips[n], 62, 19, 15);