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/player-redraw-types.h"
13 #include "core/stuff-handler.h"
14 #include "core/window-redrawer.h"
15 #include "floor/geometry.h"
16 #include "game-option/text-display-options.h"
17 #include "grid/grid.h"
18 #include "inventory/inventory-slot-types.h"
19 #include "io/command-repeater.h"
20 #include "io/input-key-requester.h"
21 #include "main/sound-definitions-table.h"
22 #include "main/sound-of-music.h"
23 #include "mind/snipe-types.h"
24 #include "monster-race/monster-race.h"
25 #include "monster-race/race-flags-resistance.h"
26 #include "monster-race/race-flags3.h"
27 #include "object/tval-types.h"
28 #include "player-base/player-class.h"
29 #include "player-info/sniper-data-type.h"
30 #include "player-status/player-energy.h"
31 #include "system/item-entity.h"
32 #include "system/monster-entity.h"
33 #include "system/monster-race-info.h"
34 #include "system/player-type-definition.h"
35 #include "system/redrawing-flags-updater.h"
36 #include "term/screen-processor.h"
37 #include "term/term-color-types.h"
38 #include "term/z-form.h"
39 #include "util/int-char-converter.h"
40 #include "view/display-messages.h"
41 #include "view/display-util.h"
43 #define MAX_SNIPE_POWERS 16
52 /*! スナイパー技能の解説メッセージ */
53 static concptr const snipe_tips[MAX_SNIPE_POWERS] = {
55 "精神を集中する。射撃の威力、精度が上がり、高度な射撃術が使用できるようになる。",
56 "光る矢を放つ。光に弱いモンスターに威力を発揮する。",
57 "射撃を行った後、短距離の瞬間移動を行う。",
58 "軌道上の罠をすべて無効にする低空飛行の矢を放つ。",
60 "壁を粉砕する矢を放つ。岩でできたモンスターと無生物のモンスターに威力を発揮する。",
64 "善良なモンスターに威力を発揮する矢を放つ。",
65 "邪悪なモンスターに威力を発揮する矢を放つ。",
69 "敵の急所にめがけて矢を放つ。成功すると敵を一撃死させる。失敗すると1ダメージ。",
70 "全てのモンスターに高威力を発揮する矢を放つ。反動による副次効果を受ける。",
72 "Concentrate your mind for shooting.",
73 "Shoot a glowing arrow effective against light-sensitive foes.",
74 "Blink after shooting.",
75 "Shoot an arrow able to shatter traps.",
76 "Deals extra damage of fire.",
77 "Shoot an arrow able to shatter rocks.",
78 "Deals extra damage of ice.",
79 "Shoot an arrow that pushes away the target.",
80 "Shoot an arrow that does not always stop at the first target on its path.",
81 "Deals more damage to good monsters.",
82 "Deals more damage to evil monsters.",
83 "Shoot an arrow that explodes when it hits a monster.",
84 "Shoot two arrows at once.",
85 "Deals extra damage of lightning.",
86 "Deals quick death or 1 damage.",
87 "Deals great damage to all monsters, and some side effects to you.",
92 static snipe_power const snipe_powers[MAX_SNIPE_POWERS] = {
93 /* Level gained, cost, name */
97 { 3, 1, "シュート&アウェイ" },
107 { 32, 4, "ダブルショット" },
108 { 36, 3, "プラズマボルト" },
109 { 40, 3, "ニードルショット" },
110 { 48, 7, "セイントスターアロー" },
112 { 1, 0, "Concentration" },
113 { 2, 1, "Flash Arrow" },
114 { 3, 1, "Shoot & Away" },
115 { 5, 1, "Disarm Shot" },
116 { 8, 2, "Fire Shot" },
117 { 10, 2, "Shatter Arrow" },
118 { 13, 2, "Ice Shot" },
119 { 18, 2, "Rushing Arrow" },
120 { 22, 3, "Piercing Shot" },
121 { 25, 4, "Evil Shot" },
122 { 26, 4, "Holy Shot" },
123 { 30, 3, "Missile" },
124 { 32, 4, "Double Shot" },
125 { 36, 3, "Plasma Bolt" },
126 { 40, 3, "Needle Shot" },
127 { 48, 7, "Saint Stars Arrow" },
131 static void reset_concentration_flag(sniper_data_type *sniper_data)
133 sniper_data->reset_concent = false;
134 auto &rfu = RedrawingFlagsUpdater::get_instance();
136 StatusRedrawingFlag::BONUS,
137 StatusRedrawingFlag::MONSTER_STATUSES,
139 rfu.set_flags(flags);
140 rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
145 * @return 集中度を加算した場合は true、そうでなければ false
147 static bool snipe_concentrate(PlayerType *player_ptr)
149 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
154 if (sniper_data->concent < (2 + (player_ptr->lev + 5) / 10)) {
155 sniper_data->concent++;
158 msg_format(_("集中した。(集中度 %d)", "You concentrate deeply. (lvl %d)"), sniper_data->concent);
159 reset_concentration_flag(sniper_data.get());
164 * @brief スナイパーの集中度リセット
165 * スナイパーではない、もしくは集中度がすでに0であればなにもしない。
166 * @param msg TRUEならばメッセージを表示する
168 void reset_concentration(PlayerType *player_ptr, bool msg)
170 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
175 if (msg && (sniper_data->concent > 0)) {
176 msg_print(_("集中力が途切れてしまった。", "Stop concentrating."));
179 sniper_data->concent = 0;
180 reset_concentration_flag(sniper_data.get());
184 * @brief スナイパーの集中度によるダメージボーナスを加算する
185 * @param tdam 算出中のダメージ
186 * @return 集中度修正を加えたダメージ
188 int boost_concentration_damage(PlayerType *player_ptr, int tdam)
190 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
191 const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
193 tdam = tdam * (10 + sniper_concent) / 10;
199 * @brief スナイパーの技能リストを表示する
201 void display_snipe_list(PlayerType *player_ptr)
206 PLAYER_LEVEL plev = player_ptr->lev;
210 /* Display a list of spells */
212 put_str(_("名前", "Name"), y, x + 5);
213 put_str(_("Lv MP", "Lv Mana"), y, x + 35);
215 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
217 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
218 /* Access the available spell */
219 spell = snipe_powers[i];
220 if (spell.min_lev > plev) {
224 strnfmt(psi_desc, sizeof(psi_desc), " %c) %-30s%2d %4d", I2A(i), spell.name, spell.min_lev, spell.mana_cost);
226 TERM_COLOR tcol = (spell.mana_cost > sniper_data->concent) ? TERM_SLATE : TERM_WHITE;
227 term_putstr(x, y + i + 1, -1, tcol, psi_desc);
232 * @brief スナイパー技能を選択する
233 * @param sn 選択した特殊技能ID、キャンセルの場合-1、不正な選択の場合-2を返す
234 * @param only_browse 一覧を見るだけの場合TRUEを返す
235 * @return 発動可能な魔法を選択した場合TRUE、キャンセル処理か不正な選択が行われた場合FALSEを返す。
236 * Allow user to choose a mindcrafter power.\n
238 * If a valid spell is chosen, saves it in '*sn' and returns TRUE\n
239 * If the user hits escape, returns FALSE, and set '*sn' to -1\n
240 * If there are no legal choices, returns FALSE, and sets '*sn' to -2\n
242 * The "prompt" should be "cast", "recite", or "study"\n
243 * The "known" should be TRUE for cast/pray, FALSE for study\n
245 * nb: This function has a (trivial) display bug which will be obvious\n
246 * when you run it. It's probably easy to fix but I haven't tried,\n
249 static int get_snipe_power(PlayerType *player_ptr, COMMAND_CODE *sn, bool only_browse)
255 PLAYER_LEVEL plev = player_ptr->lev;
258 concptr p = _("射撃術", "power");
264 /* Assume cancelled */
267 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
269 /* Repeat previous command */
270 /* Get the spell, if available */
271 if (repeat_pull(sn)) {
272 /* Verify the spell */
273 if ((snipe_powers[*sn].min_lev <= plev) && (snipe_powers[*sn].mana_cost <= sniper_data->concent)) {
282 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
283 if ((snipe_powers[i].min_lev <= plev) && ((only_browse) || (snipe_powers[i].mana_cost <= sniper_data->concent))) {
288 /* Build a prompt (accept all spells) */
290 constexpr auto mes = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sについて知りますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
291 (void)strnfmt(out_val, 78, mes, p, I2A(0), I2A(num), p);
293 constexpr auto mes = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sを使いますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
294 (void)strnfmt(out_val, 78, mes, p, I2A(0), I2A(num), p);
297 choice = always_show_list ? ESCAPE : 1;
299 if (choice == ESCAPE) {
301 } else if (!get_com(out_val, &choice, false)) {
306 if ((choice == ' ') || (choice == '*') || (choice == '?')) {
314 /* Display a list of spells */
316 put_str(_("名前", "Name"), y, x + 5);
317 put_str(_("Lv 集中度", "Lv Pow"), y, x + 35);
319 /* Dump the spells */
320 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
321 term_color_type tcol = TERM_WHITE;
322 term_erase(x, y + i + 1, 255);
324 /* Access the spell */
325 spell = snipe_powers[i];
327 /* Dump the spell --(-- */
328 if (spell.min_lev > plev) {
330 } else if (spell.mana_cost > sniper_data->concent) {
333 term_putstr(x, y + i + 1, -1, tcol, (spell.min_lev > plev) ? " ) " : format(" %c) ", I2A(i)));
334 term_putstr(x + 5, y + i + 1, -1, tcol, format("%-30s%2d %4d", spell.name, spell.min_lev, spell.mana_cost));
337 /* Clear the bottom line */
338 prt("", y + i + 1, x);
356 /* Totally Illegal */
357 if ((i < 0) || (i > num) || (!only_browse && (snipe_powers[i].mana_cost > sniper_data->concent))) {
362 /* Save the spell index */
363 spell = snipe_powers[i];
368 if (redraw && !only_browse) {
372 player_ptr->window_flags |= (PW_SPELL);
373 handle_stuff(player_ptr);
375 /* Abort if needed */
380 /* Save the choice */
390 * @brief スナイバー技能のスレイ倍率計算を行う /
391 * Calcurate magnification of snipe technics
392 * @param mult スナイバー技能のスレイ効果以前に算出している多要素の倍率(/10倍)
393 * @param m_ptr 目標となるモンスターの構造体参照ポインタ
394 * @return スレイの倍率(/10倍)
396 MULTIPLY calc_snipe_damage_with_slay(PlayerType *player_ptr, MULTIPLY mult, MonsterEntity *m_ptr, SPELL_IDX snipe_type)
398 auto *r_ptr = &monraces_info[m_ptr->r_idx];
399 bool seen = is_seen(player_ptr, m_ptr);
401 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
402 const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
404 switch (snipe_type) {
406 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
407 MULTIPLY n = 20 + sniper_concent;
409 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
417 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
419 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_FIRE);
423 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_FIRE)) {
424 n = 22 + (sniper_concent * 4);
425 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_FIRE);
427 n = 15 + (sniper_concent * 3);
436 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
438 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_COLD);
442 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_COLD)) {
443 n = 22 + (sniper_concent * 4);
444 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_COLD);
446 n = 15 + (sniper_concent * 3);
455 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
457 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_ELEC);
460 MULTIPLY n = 18 + (sniper_concent * 4);
467 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_ROCK)) {
468 MULTIPLY n = 15 + (sniper_concent * 2);
470 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_ROCK);
475 } else if (r_ptr->kind_flags.has(MonsterKindType::NONLIVING)) {
476 MULTIPLY n = 15 + (sniper_concent * 2);
478 r_ptr->r_kind_flags.set(MonsterKindType::NONLIVING);
486 if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
487 MULTIPLY n = 15 + (sniper_concent * 4);
489 r_ptr->r_kind_flags.set(MonsterKindType::GOOD);
497 if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
498 MULTIPLY n = 12 + (sniper_concent * 3);
500 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
502 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
503 n += (sniper_concent * 3);
505 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
524 * @brief スナイパー技能の発動 /
525 * do_cmd_cast calls this function if the player's class is 'snipe'.
526 * @param spell 発動する特殊技能のID
527 * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
529 static bool cast_sniper_spell(PlayerType *player_ptr, int spell)
531 auto *o_ptr = &player_ptr->inventory_list[INVEN_BOW];
532 if (o_ptr->bi_key.tval() != ItemKindType::BOW) {
533 msg_print(_("弓を装備していない!", "You wield no bow!"));
537 auto snipe_type = SP_NONE;
539 case 0: /* Concentration */
541 if (!snipe_concentrate(player_ptr)) {
544 PlayerEnergy(player_ptr).set_player_turn_energy(100);
547 snipe_type = SP_LITE;
550 snipe_type = SP_AWAY;
553 snipe_type = SP_KILL_TRAP;
556 snipe_type = SP_FIRE;
559 snipe_type = SP_KILL_WALL;
562 snipe_type = SP_COLD;
565 snipe_type = SP_RUSH;
568 snipe_type = SP_PIERCE;
571 snipe_type = SP_EVILNESS;
574 snipe_type = SP_HOLYNESS;
577 snipe_type = SP_EXPLODE;
580 snipe_type = SP_DOUBLE;
583 snipe_type = SP_ELEC;
586 snipe_type = SP_NEEDLE;
589 snipe_type = SP_FINAL;
592 msg_print(_("なに?", "Zap?"));
596 do_cmd_fire(player_ptr, snipe_type);
598 return player_ptr->is_fired;
602 * @brief スナイパー技能コマンドのメインルーチン /
604 void do_cmd_snipe(PlayerType *player_ptr)
606 if (cmd_limit_confused(player_ptr)) {
610 if (cmd_limit_image(player_ptr)) {
614 if (cmd_limit_stun(player_ptr)) {
619 if (!get_snipe_power(player_ptr, &n, false)) {
623 if (!cast_sniper_spell(player_ptr, n)) {
627 auto &rfu = RedrawingFlagsUpdater::get_instance();
628 const auto flags_mwrf = {
629 MainWindowRedrawingFlag::HP,
630 MainWindowRedrawingFlag::MP,
632 rfu.set_flags(flags_mwrf);
633 player_ptr->window_flags |= (PW_PLAYER);
634 player_ptr->window_flags |= (PW_SPELL);
638 * @brief スナイパー技能コマンドの表示 /
640 void do_cmd_snipe_browse(PlayerType *player_ptr)
647 if (!get_snipe_power(player_ptr, &n, true)) {
652 /* Clear lines, position cursor (really should use strlen here) */
653 term_erase(12, 22, 255);
654 term_erase(12, 21, 255);
655 term_erase(12, 20, 255);
656 term_erase(12, 19, 255);
657 term_erase(12, 18, 255);
659 display_wrap_around(snipe_tips[n], 62, 19, 15);