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/player-update-types.h"
14 #include "core/stuff-handler.h"
15 #include "core/window-redrawer.h"
16 #include "floor/geometry.h"
17 #include "game-option/text-display-options.h"
18 #include "grid/grid.h"
19 #include "inventory/inventory-slot-types.h"
20 #include "io/command-repeater.h"
21 #include "io/input-key-requester.h"
22 #include "main/sound-definitions-table.h"
23 #include "main/sound-of-music.h"
24 #include "mind/snipe-types.h"
25 #include "monster-race/monster-race.h"
26 #include "monster-race/race-flags-resistance.h"
27 #include "monster-race/race-flags3.h"
28 #include "object/tval-types.h"
29 #include "player-base/player-class.h"
30 #include "player-info/sniper-data-type.h"
31 #include "player-status/player-energy.h"
32 #include "system/item-entity.h"
33 #include "system/monster-race-info.h"
34 #include "system/monster-type-definition.h"
35 #include "system/player-type-definition.h"
36 #include "term/screen-processor.h"
37 #include "term/term-color-types.h"
38 #include "util/buffer-shaper.h"
39 #include "util/int-char-converter.h"
40 #include "view/display-messages.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" },
132 * @return 集中度を加算した場合は true、そうでなければ false
134 static bool snipe_concentrate(PlayerType *player_ptr)
136 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
141 if (sniper_data->concent < (2 + (player_ptr->lev + 5) / 10)) {
142 sniper_data->concent++;
145 msg_format(_("集中した。(集中度 %d)", "You concentrate deeply. (lvl %d)"), sniper_data->concent);
146 sniper_data->reset_concent = false;
148 player_ptr->update |= (PU_BONUS | PU_MONSTERS);
149 player_ptr->redraw |= (PR_STATUS);
154 * @brief スナイパーの集中度リセット
155 * スナイパーではない、もしくは集中度がすでに0であればなにもしない。
156 * @param msg TRUEならばメッセージを表示する
158 void reset_concentration(PlayerType *player_ptr, bool msg)
160 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
165 if (msg && (sniper_data->concent > 0)) {
166 msg_print(_("集中力が途切れてしまった。", "Stop concentrating."));
169 sniper_data->concent = 0;
170 sniper_data->reset_concent = false;
172 player_ptr->update |= (PU_BONUS | PU_MONSTERS);
173 player_ptr->redraw |= (PR_STATUS);
177 * @brief スナイパーの集中度によるダメージボーナスを加算する
178 * @param tdam 算出中のダメージ
179 * @return 集中度修正を加えたダメージ
181 int boost_concentration_damage(PlayerType *player_ptr, int tdam)
183 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
184 const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
186 tdam = tdam * (10 + sniper_concent) / 10;
192 * @brief スナイパーの技能リストを表示する
194 void display_snipe_list(PlayerType *player_ptr)
199 PLAYER_LEVEL plev = player_ptr->lev;
203 /* Display a list of spells */
205 put_str(_("名前", "Name"), y, x + 5);
206 put_str(_("Lv MP", "Lv Mana"), y, x + 35);
208 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
210 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
211 /* Access the available spell */
212 spell = snipe_powers[i];
213 if (spell.min_lev > plev) {
217 sprintf(psi_desc, " %c) %-30s%2d %4d", I2A(i), spell.name, spell.min_lev, spell.mana_cost);
219 if (spell.mana_cost > sniper_data->concent) {
220 term_putstr(x, y + i + 1, -1, TERM_SLATE, psi_desc);
222 term_putstr(x, y + i + 1, -1, TERM_WHITE, psi_desc);
228 * @brief スナイパー技能を選択する
229 * @param sn 選択した特殊技能ID、キャンセルの場合-1、不正な選択の場合-2を返す
230 * @param only_browse 一覧を見るだけの場合TRUEを返す
231 * @return 発動可能な魔法を選択した場合TRUE、キャンセル処理か不正な選択が行われた場合FALSEを返す。
232 * Allow user to choose a mindcrafter power.\n
234 * If a valid spell is chosen, saves it in '*sn' and returns TRUE\n
235 * If the user hits escape, returns FALSE, and set '*sn' to -1\n
236 * If there are no legal choices, returns FALSE, and sets '*sn' to -2\n
238 * The "prompt" should be "cast", "recite", or "study"\n
239 * The "known" should be TRUE for cast/pray, FALSE for study\n
241 * nb: This function has a (trivial) display bug which will be obvious\n
242 * when you run it. It's probably easy to fix but I haven't tried,\n
245 static int get_snipe_power(PlayerType *player_ptr, COMMAND_CODE *sn, bool only_browse)
251 PLAYER_LEVEL plev = player_ptr->lev;
254 concptr p = _("射撃術", "power");
260 /* Assume cancelled */
263 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
265 /* Repeat previous command */
266 /* Get the spell, if available */
267 if (repeat_pull(sn)) {
268 /* Verify the spell */
269 if ((snipe_powers[*sn].min_lev <= plev) && (snipe_powers[*sn].mana_cost <= sniper_data->concent)) {
278 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
279 if ((snipe_powers[i].min_lev <= plev) && ((only_browse) || (snipe_powers[i].mana_cost <= sniper_data->concent))) {
284 /* Build a prompt (accept all spells) */
287 out_val, 78, _("(%^s %c-%c, '*'で一覧, ESC) どの%sについて知りますか?", "(%^ss %c-%c, *=List, ESC=exit) Use which %s? "), p, I2A(0), I2A(num), p);
290 out_val, 78, _("(%^s %c-%c, '*'で一覧, ESC) どの%sを使いますか?", "(%^ss %c-%c, *=List, ESC=exit) Use which %s? "), p, I2A(0), I2A(num), p);
293 choice = always_show_list ? ESCAPE : 1;
295 if (choice == ESCAPE) {
297 } else if (!get_com(out_val, &choice, false)) {
302 if ((choice == ' ') || (choice == '*') || (choice == '?')) {
312 /* Display a list of spells */
314 put_str(_("名前", "Name"), y, x + 5);
315 put_str(_("Lv 集中度", "Lv Pow"), y, x + 35);
317 /* Dump the spells */
318 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
319 term_color_type tcol = TERM_WHITE;
320 term_erase(x, y + i + 1, 255);
322 /* Access the spell */
323 spell = snipe_powers[i];
325 /* Dump the spell --(-- */
326 if (spell.min_lev > plev) {
327 sprintf(psi_index, " ) ");
329 sprintf(psi_index, " %c) ", I2A(i));
332 sprintf(psi_desc, "%-30s%2d %4d", spell.name, spell.min_lev, spell.mana_cost);
334 if (spell.min_lev > plev) {
336 } else if (spell.mana_cost > sniper_data->concent) {
340 term_putstr(x, y + i + 1, -1, tcol, psi_index);
341 term_putstr(x + 5, y + i + 1, -1, tcol, psi_desc);
344 /* Clear the bottom line */
345 prt("", y + i + 1, x);
363 /* Totally Illegal */
364 if ((i < 0) || (i > num) || (!only_browse && (snipe_powers[i].mana_cost > sniper_data->concent))) {
369 /* Save the spell index */
370 spell = snipe_powers[i];
375 if (redraw && !only_browse) {
379 player_ptr->window_flags |= (PW_SPELL);
380 handle_stuff(player_ptr);
382 /* Abort if needed */
387 /* Save the choice */
397 * @brief スナイバー技能のスレイ倍率計算を行う /
398 * Calcurate magnification of snipe technics
399 * @param mult スナイバー技能のスレイ効果以前に算出している多要素の倍率(/10倍)
400 * @param m_ptr 目標となるモンスターの構造体参照ポインタ
401 * @return スレイの倍率(/10倍)
403 MULTIPLY calc_snipe_damage_with_slay(PlayerType *player_ptr, MULTIPLY mult, MonsterEntity *m_ptr, SPELL_IDX snipe_type)
405 auto *r_ptr = &monraces_info[m_ptr->r_idx];
406 bool seen = is_seen(player_ptr, m_ptr);
408 auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
409 const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
411 switch (snipe_type) {
413 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
414 MULTIPLY n = 20 + sniper_concent;
416 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
424 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
426 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_FIRE);
430 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_FIRE)) {
431 n = 22 + (sniper_concent * 4);
432 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_FIRE);
434 n = 15 + (sniper_concent * 3);
443 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
445 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_COLD);
449 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_COLD)) {
450 n = 22 + (sniper_concent * 4);
451 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_COLD);
453 n = 15 + (sniper_concent * 3);
462 if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
464 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_ELEC);
467 MULTIPLY n = 18 + (sniper_concent * 4);
474 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_ROCK)) {
475 MULTIPLY n = 15 + (sniper_concent * 2);
477 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_ROCK);
482 } else if (r_ptr->kind_flags.has(MonsterKindType::NONLIVING)) {
483 MULTIPLY n = 15 + (sniper_concent * 2);
485 r_ptr->r_kind_flags.set(MonsterKindType::NONLIVING);
493 if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
494 MULTIPLY n = 15 + (sniper_concent * 4);
496 r_ptr->r_kind_flags.set(MonsterKindType::GOOD);
504 if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
505 MULTIPLY n = 12 + (sniper_concent * 3);
507 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
509 if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
510 n += (sniper_concent * 3);
512 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
531 * @brief スナイパー技能の発動 /
532 * do_cmd_cast calls this function if the player's class is 'snipe'.
533 * @param spell 発動する特殊技能のID
534 * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
536 static bool cast_sniper_spell(PlayerType *player_ptr, int spell)
538 auto *o_ptr = &player_ptr->inventory_list[INVEN_BOW];
539 SPELL_IDX snipe_type = SP_NONE;
541 if (o_ptr->tval != ItemKindType::BOW) {
542 msg_print(_("弓を装備していない!", "You wield no bow!"));
548 case 0: /* Concentration */
550 if (!snipe_concentrate(player_ptr)) {
553 PlayerEnergy(player_ptr).set_player_turn_energy(100);
556 snipe_type = SP_LITE;
559 snipe_type = SP_AWAY;
562 snipe_type = SP_KILL_TRAP;
565 snipe_type = SP_FIRE;
568 snipe_type = SP_KILL_WALL;
571 snipe_type = SP_COLD;
574 snipe_type = SP_RUSH;
577 snipe_type = SP_PIERCE;
580 snipe_type = SP_EVILNESS;
583 snipe_type = SP_HOLYNESS;
586 snipe_type = SP_EXPLODE;
589 snipe_type = SP_DOUBLE;
592 snipe_type = SP_ELEC;
595 snipe_type = SP_NEEDLE;
598 snipe_type = SP_FINAL;
601 msg_print(_("なに?", "Zap?"));
605 do_cmd_fire(player_ptr, snipe_type);
607 return player_ptr->is_fired;
611 * @brief スナイパー技能コマンドのメインルーチン /
613 void do_cmd_snipe(PlayerType *player_ptr)
618 if (cmd_limit_confused(player_ptr)) {
621 if (cmd_limit_image(player_ptr)) {
624 if (cmd_limit_stun(player_ptr)) {
628 if (!get_snipe_power(player_ptr, &n, false)) {
632 cast = cast_sniper_spell(player_ptr, n);
637 player_ptr->redraw |= (PR_HP | PR_MANA);
638 player_ptr->window_flags |= (PW_PLAYER);
639 player_ptr->window_flags |= (PW_SPELL);
643 * @brief スナイパー技能コマンドの表示 /
645 void do_cmd_snipe_browse(PlayerType *player_ptr)
654 if (!get_snipe_power(player_ptr, &n, true)) {
659 /* Clear lines, position cursor (really should use strlen here) */
660 term_erase(12, 22, 255);
661 term_erase(12, 21, 255);
662 term_erase(12, 20, 255);
663 term_erase(12, 19, 255);
664 term_erase(12, 18, 255);
666 shape_buffer(snipe_tips[n], 62, temp, sizeof(temp));
667 for (j = 0, line = 19; temp[j]; j += (1 + strlen(&temp[j]))) {
668 prt(&temp[j], line, 15);