OSDN Git Service

[Refactor] #3286 Removed player-redraw-types.h
[hengbandforosx/hengbandosx.git] / src / mind / mind-ninja.cpp
1 #include "mind/mind-ninja.h"
2 #include "cmd-action/cmd-attack.h"
3 #include "cmd-item/cmd-throw.h"
4 #include "combat/combat-options-type.h"
5 #include "core/disturbance.h"
6 #include "effect/attribute-types.h"
7 #include "effect/effect-characteristics.h"
8 #include "effect/effect-processor.h"
9 #include "effect/spells-effect-util.h"
10 #include "floor/cave.h"
11 #include "floor/floor-object.h"
12 #include "floor/floor-util.h"
13 #include "floor/geometry.h"
14 #include "game-option/disturbance-options.h"
15 #include "grid/grid.h"
16 #include "inventory/inventory-slot-types.h"
17 #include "mind/mind-mirror-master.h"
18 #include "mind/mind-numbers.h"
19 #include "mind/mind-warrior.h"
20 #include "monster-race/monster-race.h"
21 #include "monster-race/race-flags-resistance.h"
22 #include "monster-race/race-indice-types.h"
23 #include "monster/monster-describer.h"
24 #include "monster/monster-status.h"
25 #include "monster/monster-update.h"
26 #include "object-enchant/trc-types.h"
27 #include "object/object-kind-hook.h"
28 #include "player-attack/player-attack-util.h"
29 #include "player-base/player-class.h"
30 #include "player-info/equipment-info.h"
31 #include "player-info/ninja-data-type.h"
32 #include "player-status/player-energy.h"
33 #include "player/attack-defense-types.h"
34 #include "player/player-status-flags.h"
35 #include "player/special-defense-types.h"
36 #include "spell-kind/spells-detection.h"
37 #include "spell-kind/spells-fetcher.h"
38 #include "spell-kind/spells-floor.h"
39 #include "spell-kind/spells-grid.h"
40 #include "spell-kind/spells-launcher.h"
41 #include "spell-kind/spells-lite.h"
42 #include "spell-kind/spells-perception.h"
43 #include "spell-kind/spells-teleport.h"
44 #include "spell/spells-status.h"
45 #include "status/action-setter.h"
46 #include "status/body-improvement.h"
47 #include "status/element-resistance.h"
48 #include "status/temporary-resistance.h"
49 #include "system/baseitem-info.h"
50 #include "system/floor-type-definition.h"
51 #include "system/grid-type-definition.h"
52 #include "system/item-entity.h"
53 #include "system/monster-entity.h"
54 #include "system/monster-race-info.h"
55 #include "system/player-type-definition.h"
56 #include "system/redrawing-flags-updater.h"
57 #include "system/terrain-type-definition.h"
58 #include "target/projection-path-calculator.h"
59 #include "target/target-checker.h"
60 #include "target/target-getter.h"
61 #include "timed-effect/player-blindness.h"
62 #include "timed-effect/player-confusion.h"
63 #include "timed-effect/player-hallucination.h"
64 #include "timed-effect/player-paralysis.h"
65 #include "timed-effect/player-stun.h"
66 #include "timed-effect/timed-effects.h"
67 #include "util/bit-flags-calculator.h"
68 #include "view/display-messages.h"
69
70 /*!
71  * @brief 変わり身処理
72  * @param player_ptr プレイヤーへの参照ポインタ
73  * @param success 判定成功上の処理ならばTRUE
74  * @return 作用が実際にあった場合TRUEを返す
75  */
76 bool kawarimi(PlayerType *player_ptr, bool success)
77 {
78     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
79     if (!ninja_data || !ninja_data->kawarimi) {
80         return false;
81     }
82
83     ItemEntity forge;
84     auto *q_ptr = &forge;
85     if (player_ptr->is_dead) {
86         return false;
87     }
88
89     const auto effects = player_ptr->effects();
90     const auto is_confused = effects->confusion()->is_confused();
91     const auto is_blind = effects->blindness()->is_blind();
92     const auto is_hallucinated = effects->hallucination()->is_hallucinated();
93     const auto is_paralyzed = effects->paralysis()->is_paralyzed();
94     if (is_confused || is_blind || is_paralyzed || is_hallucinated) {
95         return false;
96     }
97
98     if (effects->stun()->current() > randint0(200)) {
99         return false;
100     }
101
102     auto &rfu = RedrawingFlagsUpdater::get_instance();
103     if (!success && one_in_(3)) {
104         msg_print(_("変わり身失敗!逃げられなかった。", "Kawarimi failed! You couldn't run away."));
105         ninja_data->kawarimi = false;
106         rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
107         return false;
108     }
109
110     POSITION y = player_ptr->y;
111     POSITION x = player_ptr->x;
112
113     teleport_player(player_ptr, 10 + randint1(90), TELEPORT_SPONTANEOUS);
114     q_ptr->wipe();
115     const int sv_wooden_statue = 0;
116     q_ptr->prep(lookup_baseitem_id({ ItemKindType::STATUE, sv_wooden_statue }));
117
118     q_ptr->pval = enum2i(MonsterRaceId::NINJA);
119     (void)drop_near(player_ptr, q_ptr, -1, y, x);
120
121     if (success) {
122         msg_print(_("攻撃を受ける前に素早く身をひるがえした。", "You have turned around just before the attack hit you."));
123     } else {
124         msg_print(_("変わり身失敗!攻撃を受けてしまった。", "Kawarimi failed! You are hit by the attack."));
125     }
126
127     ninja_data->kawarimi = false;
128     rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
129     return true;
130 }
131
132 /*!
133  * @brief 入身処理 / "Rush Attack" routine for Samurai or Ninja
134  * @param player_ptr プレイヤーへの参照ポインタ
135  * @param mdeath 目標モンスターが死亡したかを返す
136  * @return 作用が実際にあった場合TRUEを返す /  Return value is for checking "done"
137  */
138 bool rush_attack(PlayerType *player_ptr, bool *mdeath)
139 {
140     if (mdeath) {
141         *mdeath = false;
142     }
143
144     project_length = 5;
145     DIRECTION dir;
146     if (!get_aim_dir(player_ptr, &dir)) {
147         return false;
148     }
149
150     int tx = player_ptr->x + project_length * ddx[dir];
151     int ty = player_ptr->y + project_length * ddy[dir];
152
153     if ((dir == 5) && target_okay(player_ptr)) {
154         tx = target_col;
155         ty = target_row;
156     }
157
158     int tm_idx = 0;
159     auto *floor_ptr = player_ptr->current_floor_ptr;
160     if (in_bounds(floor_ptr, ty, tx)) {
161         tm_idx = floor_ptr->grid_array[ty][tx].m_idx;
162     }
163
164     projection_path path_g(player_ptr, project_length, player_ptr->y, player_ptr->x, ty, tx, PROJECT_STOP | PROJECT_KILL);
165     project_length = 0;
166     if (path_g.path_num() == 0) {
167         return true;
168     }
169
170     ty = player_ptr->y;
171     tx = player_ptr->x;
172     bool tmp_mdeath = false;
173     bool moved = false;
174     for (const auto &[ny, nx] : path_g) {
175         MonsterEntity *m_ptr;
176
177         if (is_cave_empty_bold(player_ptr, ny, nx) && player_can_enter(player_ptr, floor_ptr->grid_array[ny][nx].feat, 0)) {
178             ty = ny;
179             tx = nx;
180             continue;
181         }
182
183         if (!floor_ptr->grid_array[ny][nx].m_idx) {
184             if (tm_idx) {
185                 msg_print(_("失敗!", "Failed!"));
186             } else {
187                 msg_print(_("ここには入身では入れない。", "You can't move to that place."));
188             }
189
190             break;
191         }
192
193         if (!player_bold(player_ptr, ty, tx)) {
194             teleport_player_to(player_ptr, ty, tx, TELEPORT_NONMAGICAL);
195         }
196         update_monster(player_ptr, floor_ptr->grid_array[ny][nx].m_idx, true);
197
198         m_ptr = &floor_ptr->m_list[floor_ptr->grid_array[ny][nx].m_idx];
199         if (tm_idx != floor_ptr->grid_array[ny][nx].m_idx) {
200 #ifdef JP
201             msg_format("%s%sが立ちふさがっている!", tm_idx ? "別の" : "", m_ptr->ml ? "モンスター" : "何か");
202 #else
203             msg_format("There is %s in the way!", m_ptr->ml ? (tm_idx ? "another monster" : "a monster") : "someone");
204 #endif
205         } else if (!player_bold(player_ptr, ty, tx)) {
206             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
207             msg_format(_("素早く%sの懐に入り込んだ!", "You quickly jump in and attack %s!"), m_name.data());
208         }
209
210         if (!player_bold(player_ptr, ty, tx)) {
211             teleport_player_to(player_ptr, ty, tx, TELEPORT_NONMAGICAL);
212         }
213         moved = true;
214         tmp_mdeath = do_cmd_attack(player_ptr, ny, nx, HISSATSU_NYUSIN);
215
216         break;
217     }
218
219     if (!moved && !player_bold(player_ptr, ty, tx)) {
220         teleport_player_to(player_ptr, ty, tx, TELEPORT_NONMAGICAL);
221     }
222
223     if (mdeath) {
224         *mdeath = tmp_mdeath;
225     }
226     return true;
227 }
228
229 /*!
230  * @brief 盗賊と忍者における不意打ち
231  * @param player_ptr プレイヤーへの参照ポインタ
232  * @param pa_ptr 直接攻撃構造体への参照ポインタ
233  */
234 void process_surprise_attack(PlayerType *player_ptr, player_attack_type *pa_ptr)
235 {
236     auto *r_ptr = &monraces_info[pa_ptr->m_ptr->r_idx];
237     if (!has_melee_weapon(player_ptr, enum2i(INVEN_MAIN_HAND) + pa_ptr->hand) || player_ptr->is_icky_wield[pa_ptr->hand]) {
238         return;
239     }
240
241     int tmp = player_ptr->lev * 6 + (player_ptr->skill_stl + 10) * 4;
242     if (player_ptr->monlite && (pa_ptr->mode != HISSATSU_NYUSIN)) {
243         tmp /= 3;
244     }
245     if (has_aggravate(player_ptr)) {
246         tmp /= 2;
247     }
248     if (r_ptr->level > (player_ptr->lev * player_ptr->lev / 20 + 10)) {
249         tmp /= 3;
250     }
251
252     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
253     if (pa_ptr->m_ptr->is_asleep() && pa_ptr->m_ptr->ml) {
254         /* Can't backstab creatures that we can't see, right? */
255         pa_ptr->backstab = true;
256     } else if ((ninja_data && ninja_data->s_stealth) && (randint0(tmp) > (r_ptr->level + 20)) &&
257                pa_ptr->m_ptr->ml && !r_ptr->resistance_flags.has(MonsterResistanceType::RESIST_ALL)) {
258         pa_ptr->surprise_attack = true;
259     } else if (pa_ptr->m_ptr->is_fearful() && pa_ptr->m_ptr->ml) {
260         pa_ptr->stab_fleeing = true;
261     }
262 }
263
264 void print_surprise_attack(player_attack_type *pa_ptr)
265 {
266     if (pa_ptr->backstab) {
267         msg_format(_("あなたは冷酷にも眠っている無力な%sを突き刺した!", "You cruelly stab the helpless, sleeping %s!"), pa_ptr->m_name);
268     } else if (pa_ptr->surprise_attack) {
269         msg_format(_("不意を突いて%sに強烈な一撃を喰らわせた!", "You make surprise attack, and hit %s with a powerful blow!"), pa_ptr->m_name);
270     } else if (pa_ptr->stab_fleeing) {
271         msg_format(_("逃げる%sを背中から突き刺した!", "You backstab the fleeing %s!"), pa_ptr->m_name);
272     } else if (!pa_ptr->monk_attack) {
273         msg_format(_("%sを攻撃した。", "You hit %s."), pa_ptr->m_name);
274     }
275 }
276
277 /*!
278  * @brief 盗賊と忍者における不意打ちのダメージ計算
279  * @param player_ptr プレイヤーへの参照ポインタ
280  * @param pa_ptr 直接攻撃構造体への参照ポインタ
281  */
282 void calc_surprise_attack_damage(PlayerType *player_ptr, player_attack_type *pa_ptr)
283 {
284     if (pa_ptr->backstab) {
285         pa_ptr->attack_damage *= (3 + (player_ptr->lev / 20));
286         return;
287     }
288
289     if (pa_ptr->surprise_attack) {
290         pa_ptr->attack_damage = pa_ptr->attack_damage * (5 + (player_ptr->lev * 2 / 25)) / 2;
291         return;
292     }
293
294     if (pa_ptr->stab_fleeing) {
295         pa_ptr->attack_damage = (3 * pa_ptr->attack_damage) / 2;
296     }
297 }
298
299 /*!
300  * @brief 速駆け処理
301  * @param player_ptr プレイヤーへの参照ポインタ
302  * @return 常にTRUE
303  */
304 bool hayagake(PlayerType *player_ptr)
305 {
306     PlayerEnergy energy(player_ptr);
307     if (player_ptr->action == ACTION_HAYAGAKE) {
308         set_action(player_ptr, ACTION_NONE);
309         energy.reset_player_turn();
310         return true;
311     }
312
313     auto *g_ptr = &player_ptr->current_floor_ptr->grid_array[player_ptr->y][player_ptr->x];
314     auto *f_ptr = &terrains_info[g_ptr->feat];
315
316     if (f_ptr->flags.has_not(TerrainCharacteristics::PROJECT) || (!player_ptr->levitation && f_ptr->flags.has(TerrainCharacteristics::DEEP))) {
317         msg_print(_("ここでは素早く動けない。", "You cannot run in here."));
318     } else {
319         set_action(player_ptr, ACTION_HAYAGAKE);
320     }
321
322     energy.reset_player_turn();
323     return true;
324 }
325
326 /*!
327  * @brief 超隠密状態をセットする
328  * @param set TRUEならば超隠密状態になる。
329  * @return ステータスに影響を及ぼす変化があった場合TRUEを返す。
330  */
331 bool set_superstealth(PlayerType *player_ptr, bool set)
332 {
333     bool notice = false;
334
335     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
336     if (!ninja_data || player_ptr->is_dead) {
337         return false;
338     }
339
340     if (set) {
341         if (!ninja_data->s_stealth) {
342             if (player_ptr->current_floor_ptr->grid_array[player_ptr->y][player_ptr->x].info & CAVE_MNLT) {
343                 msg_print(_("敵の目から薄い影の中に覆い隠された。", "You are mantled in weak shadow from ordinary eyes."));
344                 player_ptr->monlite = player_ptr->old_monlite = true;
345             } else {
346                 msg_print(_("敵の目から影の中に覆い隠された!", "You are mantled in shadow from ordinary eyes!"));
347                 player_ptr->monlite = player_ptr->old_monlite = false;
348             }
349
350             notice = true;
351             ninja_data->s_stealth = true;
352         }
353     } else {
354         if (ninja_data->s_stealth) {
355             msg_print(_("再び敵の目にさらされるようになった。", "You are exposed to common sight once more."));
356             notice = true;
357             ninja_data->s_stealth = false;
358         }
359     }
360
361     if (!notice) {
362         return false;
363     }
364
365     RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
366     if (disturb_state) {
367         disturb(player_ptr, false, false);
368     }
369
370     return true;
371 }
372
373 /*!
374  * @brief 忍術の発動 /
375  * do_cmd_cast calls this function if the player's class is 'ninja'.
376  * @param player_ptr プレイヤーへの参照ポインタ
377  * @param spell 発動する特殊技能のID
378  * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
379  */
380 bool cast_ninja_spell(PlayerType *player_ptr, MindNinjaType spell)
381 {
382     POSITION x = 0, y = 0;
383     DIRECTION dir;
384     PLAYER_LEVEL plev = player_ptr->lev;
385     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
386     switch (spell) {
387     case MindNinjaType::DARKNESS_CREATION:
388         (void)unlite_area(player_ptr, 0, 3);
389         break;
390     case MindNinjaType::DETECT_NEAR:
391         if (plev > 44) {
392             wiz_lite(player_ptr, true);
393         }
394
395         detect_monsters_normal(player_ptr, DETECT_RAD_DEFAULT);
396         if (plev > 4) {
397             detect_traps(player_ptr, DETECT_RAD_DEFAULT, true);
398             detect_doors(player_ptr, DETECT_RAD_DEFAULT);
399             detect_stairs(player_ptr, DETECT_RAD_DEFAULT);
400         }
401
402         if (plev > 14) {
403             detect_objects_normal(player_ptr, DETECT_RAD_DEFAULT);
404         }
405
406         break;
407     case MindNinjaType::HIDE_LEAVES:
408         teleport_player(player_ptr, 10, TELEPORT_SPONTANEOUS);
409         break;
410     case MindNinjaType::KAWARIMI:
411         if (ninja_data && !ninja_data->kawarimi) {
412             msg_print(_("敵の攻撃に対して敏感になった。", "You are now prepared to evade any attacks."));
413             ninja_data->kawarimi = true;
414             RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
415         }
416
417         break;
418     case MindNinjaType::ABSCONDING:
419         teleport_player(player_ptr, player_ptr->lev * 5, TELEPORT_SPONTANEOUS);
420         break;
421     case MindNinjaType::HIT_AND_AWAY:
422         if (!hit_and_away(player_ptr)) {
423             return false;
424         }
425
426         break;
427     case MindNinjaType::BIND_MONSTER:
428         if (!get_aim_dir(player_ptr, &dir)) {
429             return false;
430         }
431
432         (void)stasis_monster(player_ptr, dir);
433         break;
434     case MindNinjaType::ANCIENT_KNOWLEDGE:
435         return ident_spell(player_ptr, false);
436     case MindNinjaType::FLOATING:
437         set_tim_levitation(player_ptr, randint1(20) + 20, false);
438         break;
439     case MindNinjaType::HIDE_FLAMES:
440         fire_ball(player_ptr, AttributeType::FIRE, 0, 50 + plev, plev / 10 + 2);
441         teleport_player(player_ptr, 30, TELEPORT_SPONTANEOUS);
442         set_oppose_fire(player_ptr, (TIME_EFFECT)plev, false);
443         break;
444     case MindNinjaType::NYUSIN:
445         return rush_attack(player_ptr, nullptr);
446     case MindNinjaType::SYURIKEN_SPREADING: {
447         for (int i = 0; i < 8; i++) {
448             OBJECT_IDX slot;
449
450             for (slot = 0; slot < INVEN_PACK; slot++) {
451                 if (player_ptr->inventory_list[slot].bi_key.tval() == ItemKindType::SPIKE) {
452                     break;
453                 }
454             }
455
456             if (slot == INVEN_PACK) {
457                 if (!i) {
458                     msg_print(_("くさびを持っていない。", "You have no Iron Spikes."));
459                 } else {
460                     msg_print(_("くさびがなくなった。", "You have no more Iron Spikes."));
461                 }
462
463                 return false;
464             }
465
466             (void)ThrowCommand(player_ptr).do_cmd_throw(1, false, slot);
467             PlayerEnergy(player_ptr).set_player_turn_energy(100);
468         }
469
470         break;
471     }
472     case MindNinjaType::CHAIN_HOOK:
473         (void)fetch_monster(player_ptr);
474         break;
475     case MindNinjaType::SMOKE_BALL:
476         if (!get_aim_dir(player_ptr, &dir)) {
477             return false;
478         }
479
480         fire_ball(player_ptr, AttributeType::OLD_CONF, dir, plev * 3, 3);
481         break;
482     case MindNinjaType::SWAP_POSITION:
483         project_length = -1;
484         if (!get_aim_dir(player_ptr, &dir)) {
485             project_length = 0;
486             return false;
487         }
488
489         project_length = 0;
490         (void)teleport_swap(player_ptr, dir);
491         break;
492     case MindNinjaType::EXPLOSIVE_RUNE:
493         create_rune_explosion(player_ptr, player_ptr->y, player_ptr->x);
494         break;
495     case MindNinjaType::HIDE_MUD:
496         (void)set_pass_wall(player_ptr, randint1(plev / 2) + plev / 2, false);
497         set_oppose_acid(player_ptr, (TIME_EFFECT)plev, false);
498         break;
499     case MindNinjaType::HIDE_MIST:
500         fire_ball(player_ptr, AttributeType::POIS, 0, 75 + plev * 2 / 3, plev / 5 + 2);
501         fire_ball(player_ptr, AttributeType::HYPODYNAMIA, 0, 75 + plev * 2 / 3, plev / 5 + 2);
502         fire_ball(player_ptr, AttributeType::CONFUSION, 0, 75 + plev * 2 / 3, plev / 5 + 2);
503         teleport_player(player_ptr, 30, TELEPORT_SPONTANEOUS);
504         break;
505     case MindNinjaType::PURGATORY_FLAME: {
506         int num = damroll(3, 9);
507         for (int k = 0; k < num; k++) {
508             AttributeType typ = one_in_(2) ? AttributeType::FIRE : one_in_(3) ? AttributeType::NETHER
509                                                                               : AttributeType::PLASMA;
510             int attempts = 1000;
511             while (attempts--) {
512                 scatter(player_ptr, &y, &x, player_ptr->y, player_ptr->x, 4, PROJECT_NONE);
513                 if (!player_bold(player_ptr, y, x)) {
514                     break;
515                 }
516             }
517
518             project(player_ptr, 0, 0, y, x, damroll(6 + plev / 8, 10), typ, (PROJECT_BEAM | PROJECT_THRU | PROJECT_GRID | PROJECT_KILL));
519         }
520
521         break;
522     }
523     case MindNinjaType::ALTER_EGO:
524         set_multishadow(player_ptr, 6 + randint1(6), false);
525         break;
526     default:
527         msg_print(_("なに?", "Zap?"));
528         break;
529     }
530     return true;
531 }