OSDN Git Service

223eb185ac63c14449ffa8d760e8b9e34ea8de5c
[hengbandforosx/hengbandosx.git] / src / mind / mind-sniper.cpp
1 /*!
2  * @brief スナイパー技能の実装 / Sniping
3  * @date 2014/01/18
4  * @author
5  * 2014 Deskull rearranged comment for Doxygen.\n
6  */
7
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"
42
43 #define MAX_SNIPE_POWERS 16
44
45 /*! スナイパー技能情報の構造体 */
46 struct snipe_power {
47     PLAYER_LEVEL min_lev;
48     MANA_POINT mana_cost;
49     concptr name;
50 };
51
52 /*! スナイパー技能の解説メッセージ */
53 static concptr const snipe_tips[MAX_SNIPE_POWERS] = {
54 #ifdef JP
55     "精神を集中する。射撃の威力、精度が上がり、高度な射撃術が使用できるようになる。",
56     "光る矢を放つ。光に弱いモンスターに威力を発揮する。",
57     "射撃を行った後、短距離の瞬間移動を行う。",
58     "軌道上の罠をすべて無効にする低空飛行の矢を放つ。",
59     "火炎属性の矢を放つ。",
60     "壁を粉砕する矢を放つ。岩でできたモンスターと無生物のモンスターに威力を発揮する。",
61     "冷気属性の矢を放つ。",
62     "敵を突き飛ばす矢を放つ。",
63     "複数の敵を貫通する矢を放つ。",
64     "善良なモンスターに威力を発揮する矢を放つ。",
65     "邪悪なモンスターに威力を発揮する矢を放つ。",
66     "当たると爆発する矢を放つ。",
67     "2回射撃を行う。",
68     "電撃属性の矢を放つ。",
69     "敵の急所にめがけて矢を放つ。成功すると敵を一撃死させる。失敗すると1ダメージ。",
70     "全てのモンスターに高威力を発揮する矢を放つ。反動による副次効果を受ける。",
71 #else
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.",
88 #endif
89 };
90
91 /*! スナイパー技能テーブル */
92 static snipe_power const snipe_powers[MAX_SNIPE_POWERS] = {
93 /* Level gained,  cost,  name */
94 #ifdef JP
95     { 1, 0, "精神集中" },
96     { 2, 1, "フラッシュアロー" },
97     { 3, 1, "シュート&アウェイ" },
98     { 5, 1, "解除の矢" },
99     { 8, 2, "火炎の矢" },
100     { 10, 2, "岩砕き" },
101     { 13, 2, "冷気の矢" },
102     { 18, 2, "烈風弾" },
103     { 22, 3, "貫通弾" },
104     { 25, 4, "邪念弾" },
105     { 26, 4, "破魔矢" },
106     { 30, 3, "爆発の矢" },
107     { 32, 4, "ダブルショット" },
108     { 36, 3, "プラズマボルト" },
109     { 40, 3, "ニードルショット" },
110     { 48, 7, "セイントスターアロー" },
111 #else
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" },
128 #endif
129 };
130
131 static void reset_concentration_flag(sniper_data_type *sniper_data)
132 {
133     sniper_data->reset_concent = false;
134     auto &rfu = RedrawingFlagsUpdater::get_instance();
135     const auto flags = {
136         StatusRedrawingFlag::BONUS,
137         StatusRedrawingFlag::MONSTER_STATUSES,
138     };
139     rfu.set_flags(flags);
140     rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
141 }
142
143 /*!
144  * @brief スナイパーの集中度加算
145  * @return 集中度を加算した場合は true、そうでなければ false
146  */
147 static bool snipe_concentrate(PlayerType *player_ptr)
148 {
149     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
150     if (!sniper_data) {
151         return false;
152     }
153
154     if (sniper_data->concent < (2 + (player_ptr->lev + 5) / 10)) {
155         sniper_data->concent++;
156     }
157
158     msg_format(_("集中した。(集中度 %d)", "You concentrate deeply. (lvl %d)"), sniper_data->concent);
159     reset_concentration_flag(sniper_data.get());
160     return true;
161 }
162
163 /*!
164  * @brief スナイパーの集中度リセット
165  * スナイパーではない、もしくは集中度がすでに0であればなにもしない。
166  * @param msg TRUEならばメッセージを表示する
167  */
168 void reset_concentration(PlayerType *player_ptr, bool msg)
169 {
170     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
171     if (!sniper_data) {
172         return;
173     }
174
175     if (msg && (sniper_data->concent > 0)) {
176         msg_print(_("集中力が途切れてしまった。", "Stop concentrating."));
177     }
178
179     sniper_data->concent = 0;
180     reset_concentration_flag(sniper_data.get());
181 }
182
183 /*!
184  * @brief スナイパーの集中度によるダメージボーナスを加算する
185  * @param tdam 算出中のダメージ
186  * @return 集中度修正を加えたダメージ
187  */
188 int boost_concentration_damage(PlayerType *player_ptr, int tdam)
189 {
190     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
191     const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
192
193     tdam = tdam * (10 + sniper_concent) / 10;
194
195     return tdam;
196 }
197
198 /*!
199  * @brief スナイパーの技能リストを表示する
200  */
201 void display_snipe_list(PlayerType *player_ptr)
202 {
203     int i;
204     TERM_LEN y = 1;
205     TERM_LEN x = 1;
206     PLAYER_LEVEL plev = player_ptr->lev;
207     snipe_power spell;
208     char psi_desc[80];
209
210     /* Display a list of spells */
211     prt("", y, x);
212     put_str(_("名前", "Name"), y, x + 5);
213     put_str(_("Lv   MP", "Lv Mana"), y, x + 35);
214
215     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
216
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) {
221             continue;
222         }
223
224         strnfmt(psi_desc, sizeof(psi_desc), "  %c) %-30s%2d %4d", I2A(i), spell.name, spell.min_lev, spell.mana_cost);
225
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);
228     }
229 }
230
231 /*!
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
237  *\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
241  *\n
242  * The "prompt" should be "cast", "recite", or "study"\n
243  * The "known" should be TRUE for cast/pray, FALSE for study\n
244  *\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
247  * sorry.\n
248  */
249 static int get_snipe_power(PlayerType *player_ptr, COMMAND_CODE *sn, bool only_browse)
250 {
251     COMMAND_CODE i;
252     int num = 0;
253     TERM_LEN y = 1;
254     TERM_LEN x = 20;
255     PLAYER_LEVEL plev = player_ptr->lev;
256     char choice;
257     char out_val[160];
258     concptr p = _("射撃術", "power");
259     snipe_power spell;
260     bool flag, redraw;
261
262     repeat_push(*sn);
263
264     /* Assume cancelled */
265     *sn = (-1);
266
267     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
268
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)) {
274             /* Success */
275             return true;
276         }
277     }
278
279     flag = false;
280     redraw = false;
281
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))) {
284             num = i;
285         }
286     }
287
288     /* Build a prompt (accept all spells) */
289     if (only_browse) {
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);
292     } else {
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);
295     }
296
297     choice = always_show_list ? ESCAPE : 1;
298     while (!flag) {
299         if (choice == ESCAPE) {
300             choice = ' ';
301         } else if (!get_com(out_val, &choice, false)) {
302             break;
303         }
304
305         /* Request redraw */
306         if ((choice == ' ') || (choice == '*') || (choice == '?')) {
307             /* Show the list */
308             if (!redraw) {
309                 redraw = true;
310                 if (!only_browse) {
311                     screen_save();
312                 }
313
314                 /* Display a list of spells */
315                 prt("", y, x);
316                 put_str(_("名前", "Name"), y, x + 5);
317                 put_str(_("Lv   集中度", "Lv Pow"), y, x + 35);
318
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);
323
324                     /* Access the spell */
325                     spell = snipe_powers[i];
326
327                     /* Dump the spell --(-- */
328                     if (spell.min_lev > plev) {
329                         tcol = TERM_SLATE;
330                     } else if (spell.mana_cost > sniper_data->concent) {
331                         tcol = TERM_L_BLUE;
332                     }
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));
335                 }
336
337                 /* Clear the bottom line */
338                 prt("", y + i + 1, x);
339             }
340
341             /* Hide the list */
342             else {
343                 /* Hide list */
344                 redraw = false;
345                 if (!only_browse) {
346                     screen_load();
347                 }
348             }
349
350             /* Redo asking */
351             continue;
352         }
353
354         i = A2I(choice);
355
356         /* Totally Illegal */
357         if ((i < 0) || (i > num) || (!only_browse && (snipe_powers[i].mana_cost > sniper_data->concent))) {
358             bell();
359             continue;
360         }
361
362         /* Save the spell index */
363         spell = snipe_powers[i];
364
365         /* Stop the loop */
366         flag = true;
367     }
368     if (redraw && !only_browse) {
369         screen_load();
370     }
371
372     player_ptr->window_flags |= (PW_SPELL);
373     handle_stuff(player_ptr);
374
375     /* Abort if needed */
376     if (!flag) {
377         return false;
378     }
379
380     /* Save the choice */
381     (*sn) = i;
382
383     repeat_push(*sn);
384
385     /* Success */
386     return true;
387 }
388
389 /*!
390  * @brief スナイバー技能のスレイ倍率計算を行う /
391  * Calcurate magnification of snipe technics
392  * @param mult スナイバー技能のスレイ効果以前に算出している多要素の倍率(/10倍)
393  * @param m_ptr 目標となるモンスターの構造体参照ポインタ
394  * @return スレイの倍率(/10倍)
395  */
396 MULTIPLY calc_snipe_damage_with_slay(PlayerType *player_ptr, MULTIPLY mult, MonsterEntity *m_ptr, SPELL_IDX snipe_type)
397 {
398     auto *r_ptr = &monraces_info[m_ptr->r_idx];
399     bool seen = is_seen(player_ptr, m_ptr);
400
401     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
402     const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
403
404     switch (snipe_type) {
405     case SP_LITE:
406         if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
407             MULTIPLY n = 20 + sniper_concent;
408             if (seen) {
409                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
410             }
411             if (mult < n) {
412                 mult = n;
413             }
414         }
415         break;
416     case SP_FIRE:
417         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
418             if (seen) {
419                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_FIRE);
420             }
421         } else {
422             MULTIPLY n;
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);
426             } else {
427                 n = 15 + (sniper_concent * 3);
428             }
429
430             if (mult < n) {
431                 mult = n;
432             }
433         }
434         break;
435     case SP_COLD:
436         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
437             if (seen) {
438                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_COLD);
439             }
440         } else {
441             MULTIPLY n;
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);
445             } else {
446                 n = 15 + (sniper_concent * 3);
447             }
448
449             if (mult < n) {
450                 mult = n;
451             }
452         }
453         break;
454     case SP_ELEC:
455         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
456             if (seen) {
457                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_ELEC);
458             }
459         } else {
460             MULTIPLY n = 18 + (sniper_concent * 4);
461             if (mult < n) {
462                 mult = n;
463             }
464         }
465         break;
466     case SP_KILL_WALL:
467         if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_ROCK)) {
468             MULTIPLY n = 15 + (sniper_concent * 2);
469             if (seen) {
470                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_ROCK);
471             }
472             if (mult < n) {
473                 mult = n;
474             }
475         } else if (r_ptr->kind_flags.has(MonsterKindType::NONLIVING)) {
476             MULTIPLY n = 15 + (sniper_concent * 2);
477             if (seen) {
478                 r_ptr->r_kind_flags.set(MonsterKindType::NONLIVING);
479             }
480             if (mult < n) {
481                 mult = n;
482             }
483         }
484         break;
485     case SP_EVILNESS:
486         if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
487             MULTIPLY n = 15 + (sniper_concent * 4);
488             if (seen) {
489                 r_ptr->r_kind_flags.set(MonsterKindType::GOOD);
490             }
491             if (mult < n) {
492                 mult = n;
493             }
494         }
495         break;
496     case SP_HOLYNESS:
497         if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
498             MULTIPLY n = 12 + (sniper_concent * 3);
499             if (seen) {
500                 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
501             }
502             if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
503                 n += (sniper_concent * 3);
504                 if (seen) {
505                     r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
506                 }
507             }
508             if (mult < n) {
509                 mult = n;
510             }
511         }
512         break;
513     case SP_FINAL:
514         if (mult < 50) {
515             mult = 50;
516         }
517         break;
518     }
519
520     return mult;
521 }
522
523 /*!
524  * @brief スナイパー技能の発動 /
525  * do_cmd_cast calls this function if the player's class is 'snipe'.
526  * @param spell 発動する特殊技能のID
527  * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
528  */
529 static bool cast_sniper_spell(PlayerType *player_ptr, int spell)
530 {
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!"));
534         return false;
535     }
536
537     auto snipe_type = SP_NONE;
538     switch (spell) {
539     case 0: /* Concentration */
540         sound(SOUND_ZAP);
541         if (!snipe_concentrate(player_ptr)) {
542             return false;
543         }
544         PlayerEnergy(player_ptr).set_player_turn_energy(100);
545         return true;
546     case 1:
547         snipe_type = SP_LITE;
548         break;
549     case 2:
550         snipe_type = SP_AWAY;
551         break;
552     case 3:
553         snipe_type = SP_KILL_TRAP;
554         break;
555     case 4:
556         snipe_type = SP_FIRE;
557         break;
558     case 5:
559         snipe_type = SP_KILL_WALL;
560         break;
561     case 6:
562         snipe_type = SP_COLD;
563         break;
564     case 7:
565         snipe_type = SP_RUSH;
566         break;
567     case 8:
568         snipe_type = SP_PIERCE;
569         break;
570     case 9:
571         snipe_type = SP_EVILNESS;
572         break;
573     case 10:
574         snipe_type = SP_HOLYNESS;
575         break;
576     case 11:
577         snipe_type = SP_EXPLODE;
578         break;
579     case 12:
580         snipe_type = SP_DOUBLE;
581         break;
582     case 13:
583         snipe_type = SP_ELEC;
584         break;
585     case 14:
586         snipe_type = SP_NEEDLE;
587         break;
588     case 15:
589         snipe_type = SP_FINAL;
590         break;
591     default:
592         msg_print(_("なに?", "Zap?"));
593     }
594
595     command_cmd = 'f';
596     do_cmd_fire(player_ptr, snipe_type);
597
598     return player_ptr->is_fired;
599 }
600
601 /*!
602  * @brief スナイパー技能コマンドのメインルーチン /
603  */
604 void do_cmd_snipe(PlayerType *player_ptr)
605 {
606     if (cmd_limit_confused(player_ptr)) {
607         return;
608     }
609
610     if (cmd_limit_image(player_ptr)) {
611         return;
612     }
613
614     if (cmd_limit_stun(player_ptr)) {
615         return;
616     }
617
618     COMMAND_CODE n = 0;
619     if (!get_snipe_power(player_ptr, &n, false)) {
620         return;
621     }
622
623     if (!cast_sniper_spell(player_ptr, n)) {
624         return;
625     }
626
627     auto &rfu = RedrawingFlagsUpdater::get_instance();
628     const auto flags_mwrf = {
629         MainWindowRedrawingFlag::HP,
630         MainWindowRedrawingFlag::MP,
631     };
632     rfu.set_flags(flags_mwrf);
633     player_ptr->window_flags |= (PW_PLAYER);
634     player_ptr->window_flags |= (PW_SPELL);
635 }
636
637 /*!
638  * @brief スナイパー技能コマンドの表示 /
639  */
640 void do_cmd_snipe_browse(PlayerType *player_ptr)
641 {
642     COMMAND_CODE n = 0;
643
644     screen_save();
645
646     while (true) {
647         if (!get_snipe_power(player_ptr, &n, true)) {
648             screen_load();
649             return;
650         }
651
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);
658
659         display_wrap_around(snipe_tips[n], 62, 19, 15);
660     }
661 }