OSDN Git Service

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