OSDN Git Service

[Refactor] #2807 Renamed monster-race-definition.h to monster-race-info.h
[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/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"
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 /*!
131  * @brief スナイパーの集中度加算
132  * @return 集中度を加算した場合は true、そうでなければ false
133  */
134 static bool snipe_concentrate(PlayerType *player_ptr)
135 {
136     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
137     if (!sniper_data) {
138         return false;
139     }
140
141     if (sniper_data->concent < (2 + (player_ptr->lev + 5) / 10)) {
142         sniper_data->concent++;
143     }
144
145     msg_format(_("集中した。(集中度 %d)", "You concentrate deeply. (lvl %d)"), sniper_data->concent);
146     sniper_data->reset_concent = false;
147
148     player_ptr->update |= (PU_BONUS | PU_MONSTERS);
149     player_ptr->redraw |= (PR_STATUS);
150     return true;
151 }
152
153 /*!
154  * @brief スナイパーの集中度リセット
155  * スナイパーではない、もしくは集中度がすでに0であればなにもしない。
156  * @param msg TRUEならばメッセージを表示する
157  */
158 void reset_concentration(PlayerType *player_ptr, bool msg)
159 {
160     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
161     if (!sniper_data) {
162         return;
163     }
164
165     if (msg && (sniper_data->concent > 0)) {
166         msg_print(_("集中力が途切れてしまった。", "Stop concentrating."));
167     }
168
169     sniper_data->concent = 0;
170     sniper_data->reset_concent = false;
171
172     player_ptr->update |= (PU_BONUS | PU_MONSTERS);
173     player_ptr->redraw |= (PR_STATUS);
174 }
175
176 /*!
177  * @brief スナイパーの集中度によるダメージボーナスを加算する
178  * @param tdam 算出中のダメージ
179  * @return 集中度修正を加えたダメージ
180  */
181 int boost_concentration_damage(PlayerType *player_ptr, int tdam)
182 {
183     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
184     const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
185
186     tdam = tdam * (10 + sniper_concent) / 10;
187
188     return tdam;
189 }
190
191 /*!
192  * @brief スナイパーの技能リストを表示する
193  */
194 void display_snipe_list(PlayerType *player_ptr)
195 {
196     int i;
197     TERM_LEN y = 1;
198     TERM_LEN x = 1;
199     PLAYER_LEVEL plev = player_ptr->lev;
200     snipe_power spell;
201     char psi_desc[80];
202
203     /* Display a list of spells */
204     prt("", y, x);
205     put_str(_("名前", "Name"), y, x + 5);
206     put_str(_("Lv   MP", "Lv Mana"), y, x + 35);
207
208     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
209
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) {
214             continue;
215         }
216
217         sprintf(psi_desc, "  %c) %-30s%2d %4d", I2A(i), spell.name, spell.min_lev, spell.mana_cost);
218
219         if (spell.mana_cost > sniper_data->concent) {
220             term_putstr(x, y + i + 1, -1, TERM_SLATE, psi_desc);
221         } else {
222             term_putstr(x, y + i + 1, -1, TERM_WHITE, psi_desc);
223         }
224     }
225 }
226
227 /*!
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
233  *\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
237  *\n
238  * The "prompt" should be "cast", "recite", or "study"\n
239  * The "known" should be TRUE for cast/pray, FALSE for study\n
240  *\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
243  * sorry.\n
244  */
245 static int get_snipe_power(PlayerType *player_ptr, COMMAND_CODE *sn, bool only_browse)
246 {
247     COMMAND_CODE i;
248     int num = 0;
249     TERM_LEN y = 1;
250     TERM_LEN x = 20;
251     PLAYER_LEVEL plev = player_ptr->lev;
252     char choice;
253     char out_val[160];
254     concptr p = _("射撃術", "power");
255     snipe_power spell;
256     bool flag, redraw;
257
258     repeat_push(*sn);
259
260     /* Assume cancelled */
261     *sn = (-1);
262
263     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
264
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)) {
270             /* Success */
271             return true;
272         }
273     }
274
275     flag = false;
276     redraw = false;
277
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))) {
280             num = i;
281         }
282     }
283
284     /* Build a prompt (accept all spells) */
285     if (only_browse) {
286         (void)strnfmt(
287             out_val, 78, _("(%^s %c-%c, '*'で一覧, ESC) どの%sについて知りますか?", "(%^ss %c-%c, *=List, ESC=exit) Use which %s? "), p, I2A(0), I2A(num), p);
288     } else {
289         (void)strnfmt(
290             out_val, 78, _("(%^s %c-%c, '*'で一覧, ESC) どの%sを使いますか?", "(%^ss %c-%c, *=List, ESC=exit) Use which %s? "), p, I2A(0), I2A(num), p);
291     }
292
293     choice = always_show_list ? ESCAPE : 1;
294     while (!flag) {
295         if (choice == ESCAPE) {
296             choice = ' ';
297         } else if (!get_com(out_val, &choice, false)) {
298             break;
299         }
300
301         /* Request redraw */
302         if ((choice == ' ') || (choice == '*') || (choice == '?')) {
303             /* Show the list */
304             if (!redraw) {
305                 char psi_index[6];
306                 char psi_desc[75];
307                 redraw = true;
308                 if (!only_browse) {
309                     screen_save();
310                 }
311
312                 /* Display a list of spells */
313                 prt("", y, x);
314                 put_str(_("名前", "Name"), y, x + 5);
315                 put_str(_("Lv   集中度", "Lv Pow"), y, x + 35);
316
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);
321
322                     /* Access the spell */
323                     spell = snipe_powers[i];
324
325                     /* Dump the spell --(-- */
326                     if (spell.min_lev > plev) {
327                         sprintf(psi_index, "   ) ");
328                     } else {
329                         sprintf(psi_index, "  %c) ", I2A(i));
330                     }
331
332                     sprintf(psi_desc, "%-30s%2d %4d", spell.name, spell.min_lev, spell.mana_cost);
333
334                     if (spell.min_lev > plev) {
335                         tcol = TERM_SLATE;
336                     } else if (spell.mana_cost > sniper_data->concent) {
337                         tcol = TERM_L_BLUE;
338                     }
339
340                     term_putstr(x, y + i + 1, -1, tcol, psi_index);
341                     term_putstr(x + 5, y + i + 1, -1, tcol, psi_desc);
342                 }
343
344                 /* Clear the bottom line */
345                 prt("", y + i + 1, x);
346             }
347
348             /* Hide the list */
349             else {
350                 /* Hide list */
351                 redraw = false;
352                 if (!only_browse) {
353                     screen_load();
354                 }
355             }
356
357             /* Redo asking */
358             continue;
359         }
360
361         i = A2I(choice);
362
363         /* Totally Illegal */
364         if ((i < 0) || (i > num) || (!only_browse && (snipe_powers[i].mana_cost > sniper_data->concent))) {
365             bell();
366             continue;
367         }
368
369         /* Save the spell index */
370         spell = snipe_powers[i];
371
372         /* Stop the loop */
373         flag = true;
374     }
375     if (redraw && !only_browse) {
376         screen_load();
377     }
378
379     player_ptr->window_flags |= (PW_SPELL);
380     handle_stuff(player_ptr);
381
382     /* Abort if needed */
383     if (!flag) {
384         return false;
385     }
386
387     /* Save the choice */
388     (*sn) = i;
389
390     repeat_push(*sn);
391
392     /* Success */
393     return true;
394 }
395
396 /*!
397  * @brief スナイバー技能のスレイ倍率計算を行う /
398  * Calcurate magnification of snipe technics
399  * @param mult スナイバー技能のスレイ効果以前に算出している多要素の倍率(/10倍)
400  * @param m_ptr 目標となるモンスターの構造体参照ポインタ
401  * @return スレイの倍率(/10倍)
402  */
403 MULTIPLY calc_snipe_damage_with_slay(PlayerType *player_ptr, MULTIPLY mult, MonsterEntity *m_ptr, SPELL_IDX snipe_type)
404 {
405     auto *r_ptr = &monraces_info[m_ptr->r_idx];
406     bool seen = is_seen(player_ptr, m_ptr);
407
408     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
409     const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
410
411     switch (snipe_type) {
412     case SP_LITE:
413         if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
414             MULTIPLY n = 20 + sniper_concent;
415             if (seen) {
416                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
417             }
418             if (mult < n) {
419                 mult = n;
420             }
421         }
422         break;
423     case SP_FIRE:
424         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
425             if (seen) {
426                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_FIRE);
427             }
428         } else {
429             MULTIPLY n;
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);
433             } else {
434                 n = 15 + (sniper_concent * 3);
435             }
436
437             if (mult < n) {
438                 mult = n;
439             }
440         }
441         break;
442     case SP_COLD:
443         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
444             if (seen) {
445                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_COLD);
446             }
447         } else {
448             MULTIPLY n;
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);
452             } else {
453                 n = 15 + (sniper_concent * 3);
454             }
455
456             if (mult < n) {
457                 mult = n;
458             }
459         }
460         break;
461     case SP_ELEC:
462         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
463             if (seen) {
464                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_ELEC);
465             }
466         } else {
467             MULTIPLY n = 18 + (sniper_concent * 4);
468             if (mult < n) {
469                 mult = n;
470             }
471         }
472         break;
473     case SP_KILL_WALL:
474         if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_ROCK)) {
475             MULTIPLY n = 15 + (sniper_concent * 2);
476             if (seen) {
477                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_ROCK);
478             }
479             if (mult < n) {
480                 mult = n;
481             }
482         } else if (r_ptr->kind_flags.has(MonsterKindType::NONLIVING)) {
483             MULTIPLY n = 15 + (sniper_concent * 2);
484             if (seen) {
485                 r_ptr->r_kind_flags.set(MonsterKindType::NONLIVING);
486             }
487             if (mult < n) {
488                 mult = n;
489             }
490         }
491         break;
492     case SP_EVILNESS:
493         if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
494             MULTIPLY n = 15 + (sniper_concent * 4);
495             if (seen) {
496                 r_ptr->r_kind_flags.set(MonsterKindType::GOOD);
497             }
498             if (mult < n) {
499                 mult = n;
500             }
501         }
502         break;
503     case SP_HOLYNESS:
504         if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
505             MULTIPLY n = 12 + (sniper_concent * 3);
506             if (seen) {
507                 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
508             }
509             if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
510                 n += (sniper_concent * 3);
511                 if (seen) {
512                     r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
513                 }
514             }
515             if (mult < n) {
516                 mult = n;
517             }
518         }
519         break;
520     case SP_FINAL:
521         if (mult < 50) {
522             mult = 50;
523         }
524         break;
525     }
526
527     return mult;
528 }
529
530 /*!
531  * @brief スナイパー技能の発動 /
532  * do_cmd_cast calls this function if the player's class is 'snipe'.
533  * @param spell 発動する特殊技能のID
534  * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
535  */
536 static bool cast_sniper_spell(PlayerType *player_ptr, int spell)
537 {
538     auto *o_ptr = &player_ptr->inventory_list[INVEN_BOW];
539     SPELL_IDX snipe_type = SP_NONE;
540
541     if (o_ptr->tval != ItemKindType::BOW) {
542         msg_print(_("弓を装備していない!", "You wield no bow!"));
543         return false;
544     }
545
546     /* spell code */
547     switch (spell) {
548     case 0: /* Concentration */
549         sound(SOUND_ZAP);
550         if (!snipe_concentrate(player_ptr)) {
551             return false;
552         }
553         PlayerEnergy(player_ptr).set_player_turn_energy(100);
554         return true;
555     case 1:
556         snipe_type = SP_LITE;
557         break;
558     case 2:
559         snipe_type = SP_AWAY;
560         break;
561     case 3:
562         snipe_type = SP_KILL_TRAP;
563         break;
564     case 4:
565         snipe_type = SP_FIRE;
566         break;
567     case 5:
568         snipe_type = SP_KILL_WALL;
569         break;
570     case 6:
571         snipe_type = SP_COLD;
572         break;
573     case 7:
574         snipe_type = SP_RUSH;
575         break;
576     case 8:
577         snipe_type = SP_PIERCE;
578         break;
579     case 9:
580         snipe_type = SP_EVILNESS;
581         break;
582     case 10:
583         snipe_type = SP_HOLYNESS;
584         break;
585     case 11:
586         snipe_type = SP_EXPLODE;
587         break;
588     case 12:
589         snipe_type = SP_DOUBLE;
590         break;
591     case 13:
592         snipe_type = SP_ELEC;
593         break;
594     case 14:
595         snipe_type = SP_NEEDLE;
596         break;
597     case 15:
598         snipe_type = SP_FINAL;
599         break;
600     default:
601         msg_print(_("なに?", "Zap?"));
602     }
603
604     command_cmd = 'f';
605     do_cmd_fire(player_ptr, snipe_type);
606
607     return player_ptr->is_fired;
608 }
609
610 /*!
611  * @brief スナイパー技能コマンドのメインルーチン /
612  */
613 void do_cmd_snipe(PlayerType *player_ptr)
614 {
615     COMMAND_CODE n = 0;
616     bool cast;
617
618     if (cmd_limit_confused(player_ptr)) {
619         return;
620     }
621     if (cmd_limit_image(player_ptr)) {
622         return;
623     }
624     if (cmd_limit_stun(player_ptr)) {
625         return;
626     }
627
628     if (!get_snipe_power(player_ptr, &n, false)) {
629         return;
630     }
631
632     cast = cast_sniper_spell(player_ptr, n);
633
634     if (!cast) {
635         return;
636     }
637     player_ptr->redraw |= (PR_HP | PR_MANA);
638     player_ptr->window_flags |= (PW_PLAYER);
639     player_ptr->window_flags |= (PW_SPELL);
640 }
641
642 /*!
643  * @brief スナイパー技能コマンドの表示 /
644  */
645 void do_cmd_snipe_browse(PlayerType *player_ptr)
646 {
647     COMMAND_CODE n = 0;
648     int j, line;
649     char temp[62 * 4];
650
651     screen_save();
652
653     while (true) {
654         if (!get_snipe_power(player_ptr, &n, true)) {
655             screen_load();
656             return;
657         }
658
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);
665
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);
669             line++;
670         }
671     }
672 }