OSDN Git Service

[Implement] ウサウサストライカー召喚処理を追加
[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     concptr p = _("射撃術", "power");
256     snipe_power spell;
257     bool flag, redraw;
258
259     repeat_push(*sn);
260
261     /* Assume cancelled */
262     *sn = (-1);
263
264     auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
265
266     /* Repeat previous command */
267     /* Get the spell, if available */
268     if (repeat_pull(sn)) {
269         /* Verify the spell */
270         if ((snipe_powers[*sn].min_lev <= plev) && (snipe_powers[*sn].mana_cost <= sniper_data->concent)) {
271             /* Success */
272             return true;
273         }
274     }
275
276     flag = false;
277     redraw = false;
278
279     for (i = 0; i < MAX_SNIPE_POWERS; i++) {
280         if ((snipe_powers[i].min_lev <= plev) && ((only_browse) || (snipe_powers[i].mana_cost <= sniper_data->concent))) {
281             num = i;
282         }
283     }
284
285     std::string fmt;
286     if (only_browse) {
287         fmt = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sについて知りますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
288     } else {
289         fmt = _("(%s^ %c-%c, '*'で一覧, ESC) どの%sを使いますか?", "(%s^s %c-%c, *=List, ESC=exit) Use which %s? ");
290     }
291
292     const auto prompt = format(fmt.data(), p, I2A(0), I2A(num), p);
293     auto choice = always_show_list ? ESCAPE : '\1';
294     while (!flag) {
295         if (choice == ESCAPE) {
296             choice = ' ';
297         } else {
298             const auto new_choice = input_command(prompt);
299             if (!new_choice) {
300                 break;
301             }
302
303             choice = *new_choice;
304         }
305
306         /* Request redraw */
307         if ((choice == ' ') || (choice == '*') || (choice == '?')) {
308             /* Show the list */
309             if (!redraw) {
310                 redraw = true;
311                 if (!only_browse) {
312                     screen_save();
313                 }
314
315                 /* Display a list of spells */
316                 prt("", y, x);
317                 put_str(_("名前", "Name"), y, x + 5);
318                 put_str(_("Lv   集中度", "Lv Pow"), y, x + 35);
319
320                 /* Dump the spells */
321                 for (i = 0; i < MAX_SNIPE_POWERS; i++) {
322                     term_color_type tcol = TERM_WHITE;
323                     term_erase(x, y + i + 1);
324
325                     /* Access the spell */
326                     spell = snipe_powers[i];
327
328                     /* Dump the spell --(-- */
329                     if (spell.min_lev > plev) {
330                         tcol = TERM_SLATE;
331                     } else if (spell.mana_cost > sniper_data->concent) {
332                         tcol = TERM_L_BLUE;
333                     }
334                     term_putstr(x, y + i + 1, -1, tcol, (spell.min_lev > plev) ? "   ) " : format("  %c) ", I2A(i)));
335                     term_putstr(x + 5, y + i + 1, -1, tcol, format("%-30s%2d %4d", spell.name, spell.min_lev, spell.mana_cost));
336                 }
337
338                 /* Clear the bottom line */
339                 prt("", y + i + 1, x);
340             }
341
342             /* Hide the list */
343             else {
344                 /* Hide list */
345                 redraw = false;
346                 if (!only_browse) {
347                     screen_load();
348                 }
349             }
350
351             /* Redo asking */
352             continue;
353         }
354
355         i = A2I(choice);
356
357         /* Totally Illegal */
358         if ((i < 0) || (i > num) || (!only_browse && (snipe_powers[i].mana_cost > sniper_data->concent))) {
359             bell();
360             continue;
361         }
362
363         /* Save the spell index */
364         spell = snipe_powers[i];
365
366         /* Stop the loop */
367         flag = true;
368     }
369     if (redraw && !only_browse) {
370         screen_load();
371     }
372
373     RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::SPELL);
374     handle_stuff(player_ptr);
375
376     /* Abort if needed */
377     if (!flag) {
378         return false;
379     }
380
381     /* Save the choice */
382     (*sn) = i;
383
384     repeat_push(*sn);
385
386     /* Success */
387     return true;
388 }
389
390 /*!
391  * @brief スナイバー技能のスレイ倍率計算を行う /
392  * Calcurate magnification of snipe technics
393  * @param mult スナイバー技能のスレイ効果以前に算出している多要素の倍率(/10倍)
394  * @param m_ptr 目標となるモンスターの構造体参照ポインタ
395  * @return スレイの倍率(/10倍)
396  */
397 MULTIPLY calc_snipe_damage_with_slay(PlayerType *player_ptr, MULTIPLY mult, MonsterEntity *m_ptr, SPELL_IDX snipe_type)
398 {
399     auto *r_ptr = &m_ptr->get_monrace();
400     bool seen = is_seen(player_ptr, m_ptr);
401
402     auto sniper_data = PlayerClass(player_ptr).get_specific_data<SniperData>();
403     const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
404
405     switch (snipe_type) {
406     case SP_LITE:
407         if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
408             MULTIPLY n = 20 + sniper_concent;
409             if (seen) {
410                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
411             }
412             if (mult < n) {
413                 mult = n;
414             }
415         }
416         break;
417     case SP_FIRE:
418         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_FIRE)) {
419             if (seen) {
420                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_FIRE);
421             }
422         } else {
423             MULTIPLY n;
424             if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_FIRE)) {
425                 n = 22 + (sniper_concent * 4);
426                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_FIRE);
427             } else {
428                 n = 15 + (sniper_concent * 3);
429             }
430
431             if (mult < n) {
432                 mult = n;
433             }
434         }
435         break;
436     case SP_COLD:
437         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_COLD)) {
438             if (seen) {
439                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_COLD);
440             }
441         } else {
442             MULTIPLY n;
443             if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_COLD)) {
444                 n = 22 + (sniper_concent * 4);
445                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_COLD);
446             } else {
447                 n = 15 + (sniper_concent * 3);
448             }
449
450             if (mult < n) {
451                 mult = n;
452             }
453         }
454         break;
455     case SP_ELEC:
456         if (r_ptr->resistance_flags.has(MonsterResistanceType::IMMUNE_ELEC)) {
457             if (seen) {
458                 r_ptr->r_resistance_flags.set(MonsterResistanceType::IMMUNE_ELEC);
459             }
460         } else {
461             MULTIPLY n = 18 + (sniper_concent * 4);
462             if (mult < n) {
463                 mult = n;
464             }
465         }
466         break;
467     case SP_KILL_WALL:
468         if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_ROCK)) {
469             MULTIPLY n = 15 + (sniper_concent * 2);
470             if (seen) {
471                 r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_ROCK);
472             }
473             if (mult < n) {
474                 mult = n;
475             }
476         } else if (r_ptr->kind_flags.has(MonsterKindType::NONLIVING)) {
477             MULTIPLY n = 15 + (sniper_concent * 2);
478             if (seen) {
479                 r_ptr->r_kind_flags.set(MonsterKindType::NONLIVING);
480             }
481             if (mult < n) {
482                 mult = n;
483             }
484         }
485         break;
486     case SP_EVILNESS:
487         if (r_ptr->kind_flags.has(MonsterKindType::GOOD)) {
488             MULTIPLY n = 15 + (sniper_concent * 4);
489             if (seen) {
490                 r_ptr->r_kind_flags.set(MonsterKindType::GOOD);
491             }
492             if (mult < n) {
493                 mult = n;
494             }
495         }
496         break;
497     case SP_HOLYNESS:
498         if (r_ptr->kind_flags.has(MonsterKindType::EVIL)) {
499             MULTIPLY n = 12 + (sniper_concent * 3);
500             if (seen) {
501                 r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
502             }
503             if (r_ptr->resistance_flags.has(MonsterResistanceType::HURT_LITE)) {
504                 n += (sniper_concent * 3);
505                 if (seen) {
506                     r_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_LITE);
507                 }
508             }
509             if (mult < n) {
510                 mult = n;
511             }
512         }
513         break;
514     case SP_FINAL:
515         if (mult < 50) {
516             mult = 50;
517         }
518         break;
519     }
520
521     return mult;
522 }
523
524 /*!
525  * @brief スナイパー技能の発動 /
526  * do_cmd_cast calls this function if the player's class is 'snipe'.
527  * @param spell 発動する特殊技能のID
528  * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
529  */
530 static bool cast_sniper_spell(PlayerType *player_ptr, int spell)
531 {
532     auto *o_ptr = &player_ptr->inventory_list[INVEN_BOW];
533     if (o_ptr->bi_key.tval() != ItemKindType::BOW) {
534         msg_print(_("弓を装備していない!", "You wield no bow!"));
535         return false;
536     }
537
538     auto snipe_type = SP_NONE;
539     switch (spell) {
540     case 0: /* Concentration */
541         sound(SOUND_ZAP);
542         if (!snipe_concentrate(player_ptr)) {
543             return false;
544         }
545         PlayerEnergy(player_ptr).set_player_turn_energy(100);
546         return true;
547     case 1:
548         snipe_type = SP_LITE;
549         break;
550     case 2:
551         snipe_type = SP_AWAY;
552         break;
553     case 3:
554         snipe_type = SP_KILL_TRAP;
555         break;
556     case 4:
557         snipe_type = SP_FIRE;
558         break;
559     case 5:
560         snipe_type = SP_KILL_WALL;
561         break;
562     case 6:
563         snipe_type = SP_COLD;
564         break;
565     case 7:
566         snipe_type = SP_RUSH;
567         break;
568     case 8:
569         snipe_type = SP_PIERCE;
570         break;
571     case 9:
572         snipe_type = SP_EVILNESS;
573         break;
574     case 10:
575         snipe_type = SP_HOLYNESS;
576         break;
577     case 11:
578         snipe_type = SP_EXPLODE;
579         break;
580     case 12:
581         snipe_type = SP_DOUBLE;
582         break;
583     case 13:
584         snipe_type = SP_ELEC;
585         break;
586     case 14:
587         snipe_type = SP_NEEDLE;
588         break;
589     case 15:
590         snipe_type = SP_FINAL;
591         break;
592     default:
593         msg_print(_("なに?", "Zap?"));
594     }
595
596     command_cmd = 'f';
597     do_cmd_fire(player_ptr, snipe_type);
598
599     return player_ptr->is_fired;
600 }
601
602 /*!
603  * @brief スナイパー技能コマンドのメインルーチン /
604  */
605 void do_cmd_snipe(PlayerType *player_ptr)
606 {
607     if (cmd_limit_confused(player_ptr)) {
608         return;
609     }
610
611     if (cmd_limit_image(player_ptr)) {
612         return;
613     }
614
615     if (cmd_limit_stun(player_ptr)) {
616         return;
617     }
618
619     COMMAND_CODE n = 0;
620     if (!get_snipe_power(player_ptr, &n, false)) {
621         return;
622     }
623
624     if (!cast_sniper_spell(player_ptr, n)) {
625         return;
626     }
627
628     auto &rfu = RedrawingFlagsUpdater::get_instance();
629     static constexpr auto flags_mwrf = {
630         MainWindowRedrawingFlag::HP,
631         MainWindowRedrawingFlag::MP,
632     };
633     rfu.set_flags(flags_mwrf);
634     static constexpr auto flags_swrf = {
635         SubWindowRedrawingFlag::PLAYER,
636         SubWindowRedrawingFlag::SPELL,
637     };
638     rfu.set_flags(flags_swrf);
639 }
640
641 /*!
642  * @brief スナイパー技能コマンドの表示 /
643  */
644 void do_cmd_snipe_browse(PlayerType *player_ptr)
645 {
646     COMMAND_CODE n = 0;
647
648     screen_save();
649
650     while (true) {
651         if (!get_snipe_power(player_ptr, &n, true)) {
652             screen_load();
653             return;
654         }
655
656         /* Clear lines, position cursor  (really should use strlen here) */
657         term_erase(12, 22);
658         term_erase(12, 21);
659         term_erase(12, 20);
660         term_erase(12, 19);
661         term_erase(12, 18);
662
663         display_wrap_around(snipe_tips[n], 62, 19, 15);
664     }
665 }